【SpringCloud】微服务初始化

在实际开发中,我们会对独立的模块进行封装,使用微服务来承载这个模块的所有功能。

那么,如何从零搭建一个微服务呢?

本文建立的微服务包含以下几点基础能力:

1)支持SSM框架

2)Redis缓存

3)Oss文件服务

4)全局Controller异常处理

5)全局Controller日志打印(采用AOP技术)

6)拦截器

7)全局跨域处理

8)Mybatis(DAO)自动化生成配置

9)通用工具类

以下为具体代码或者配置:

一、支持SSM框架

通过pom.xml配置来解决(假设服务命为message-service)

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cheng2839.message</groupId>
    <artifactId>message-service</artifactId>
    <name>message-service</name>
    <version>1.0.0.0</version>
    <packaging>jar</packaging>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.6.RELEASE</version>
    </parent>
<!-- 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <mainClass>com.cheng2839.message.MessageApplication</mainClass> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-sleuth</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.5.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version> </dependency> <!-- 添加oracle jdbc driver --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc14</artifactId> <version>10.2.0.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.20</version> </dependency> </dependencies> <profiles> <profile> <id>release</id> <properties> <project.release.version>1.0</project.release.version> </properties> </profile> </profiles> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <distributionManagement> </distributionManagement> <repositories> </repositories> <pluginRepositories> </pluginRepositories> <build> <!-- 添加资源 --> <resources> <resource> <directory>src/main/java</directory> <includes> <!--包含文件夹以及子文件夹下所有资源--> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <!-- src/main/resources下的指定资源放行 --> <includes> <include>**/*.properties</include> <include>**/*.yml</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-archetype-plugin</artifactId> <version>2.2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.2</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <finalName>message-service</finalName> <!--<outputDirectory>../target</outputDirectory>--> <!--<fork>true</fork>--> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.18.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.17</version> </dependency> </dependencies> <configuration> <!--配置文件的路径--> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> <overwrite>true</overwrite> </configuration> </plugin> </plugins> </build> <modules> </modules> </project>

二、Redis缓存

首先,创建redis配置类

package com.cheng2839.message.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport { Logger logger = LoggerFactory.getLogger(RedisConfig.class); @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.database}") private int database; @Value("${spring.redis.timeout}") private String timeout; @Bean JedisConnectionFactory jedisConnectionFactory() { JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setHostName(this.host); factory.setPort(this.port); factory.setPassword(this.password); factory.setDatabase(this.database); logger.info("jedisConnectionFactory:host{},port{},database{}",host,port,this.database); return factory; } @Bean public RedisTemplate redisTemplate() { RedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(jedisConnectionFactory()); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new StringRedisSerializer()); template.setDefaultSerializer(new StringRedisSerializer()); template.afterPropertiesSet(); logger.info("redisTrafficTemplate"); return template; } @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); //默认超时时间,单位秒 cacheManager.setDefaultExpiration(Integer.parseInt(timeout)); //根据缓存名称设置超时时间,0为不超时 Map<String,Long> expires = new ConcurrentHashMap<>(); cacheManager.setExpires(expires); return cacheManager; } }

其次,创建service业务类

package com.cheng2839.message.service.impl;

import com.alibaba.fastjson.JSON;
import com.cheng2839.message.service.RedisService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.ClusterOperations;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.HyperLogLogOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
 */
@Service
public class RedisServiceImpl implements RedisService {
    private static Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;

    /**
     * 保存字符串数据
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public void setForString(String key, Object value) {
        String valueStr = null;
        if (value == null) {
            valueStr = null;
        } else if (value instanceof String) {
            valueStr = value.toString();
        } else {
            valueStr = JSON.toJSONString(value);
        }
        logger.info("缓存数据key:{},value:{}", key, valueStr);
        redisTemplate.opsForValue().set(key, valueStr);
    }

    /**
     * 保存字符串数据,并设置数据有效时间,单位秒
     */
    public void setForString(String key, Object value, long timeout) {
        setForString(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 保存字符串数据,并设置数据有效时间,时间和时间单位自定义
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public void setForString(String key, Object value, long timeout, TimeUnit unit) {
        String valueStr = null;
        if (value == null) {
            valueStr = null;
        } else if (value instanceof String) {
            valueStr = value.toString();
        } else {
            valueStr = JSON.toJSONString(value);
        }
        logger.info("缓存数据timeout:{},unit:{},key:{},value:{}", timeout, unit, key, valueStr);
        redisTemplate.opsForValue().set(key, valueStr, timeout, unit);
    }

    /**
     * 添加map集合字符串数据
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public void multiSetForString(Map<String, Object> map) {
        logger.info("multiSetForString map:{}", JSON.toJSONString(map));
        redisTemplate.opsForValue().multiSet(map);
    }

    /**
     * 获取key对应的字符串数据
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public String getForString(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 获取key对应的数据,并将value(json格式) 转换为对象
     */
    public <T> T getObjectForString(String key, Class<T> clazz) {
        String value = (String) redisTemplate.opsForValue().get(key);
        logger.info("方法getObjectForString, key:{},value:{}", key, value);
        if (StringUtils.isEmpty(value)) {
            return null;
        }

        return JSON.parseObject(value, clazz);
    }

    /**
     * 获取key对应的数据,并将value(json格式) 转换为List对象
     */
    public <T> List<T> getListForString(String key, Class<T> clazz) {
        String value = (String) redisTemplate.opsForValue().get(key);
        logger.info("getListForString, key:{},value:{}", key, value);
        if (StringUtils.isEmpty(value)) {
            return Collections.emptyList();
        }
        return JSON.parseArray(value, clazz);
    }


    /**
     * 查询一组key的数据
     */
    public List<String> multiGetForString(Collection<String> keys) {
        logger.info("multiGetForString, key:{}", keys);
        return redisTemplate.opsForValue().multiGet(keys);
    }


    /**
     * 查询字符串key值的长度
     */
    public Long sizeForString(String key) {
        logger.info("sizeForString, key:{}", key);
        return redisTemplate.opsForValue().size(key);
    }


    /**
     * 删除指定key
     */
    public void delete(Object key) {
        logger.info("delete, key:{}", key);
        redisTemplate.delete(key);
    }

    /**
     * 删除指定key集合
     */
    public void delete(Collection<Object> keys) {
        logger.info("delete, keys:{}", JSON.toJSONString(keys));
        redisTemplate.delete(keys);
    }

    /**
     * 查询key存在
     */
    public Boolean hasKey(Object key) {
        logger.info("hasKey, key:{}", key);
        return redisTemplate.hasKey(key);
    }

    /**
     * 设置keky的过期时间,可以自定义时长  和时间单位
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public Boolean expire(Object key, final long timeout, final TimeUnit unit) {
        logger.info("expire, key:{},timeout:{},unit:{}", key, timeout, unit);
        return redisTemplate.expire(key, timeout, unit);
    }


    /**
     * redis基础接口
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public ValueOperations opsForValue() {
        return redisTemplate.opsForValue();
    }

    /**
     * redis基础接口
     */
    public HashOperations opsForHash() {
        return redisTemplate.opsForHash();
    }

    /**
     * redis基础接口
     */
    public ListOperations opsForList() {
        return redisTemplate.opsForList();
    }

    /**
     * redis基础接口
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public ZSetOperations opsForZSet() {
        return redisTemplate.opsForZSet();
    }

    /**
     * redis基础接口
     */
    public SetOperations opsForSet() {
        return redisTemplate.opsForSet();
    }

    /**
     * redis基础接口
     */
    public ClusterOperations opsForCluster() {
        return redisTemplate.opsForCluster();
    }

    /**
     * redis基础接口
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public GeoOperations opsForGeo() {
        return redisTemplate.opsForGeo();
    }

    /**
     * redis基础接口
     * 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
     */
    public HyperLogLogOperations opsForHyperLogLog() {
        return redisTemplate.opsForHyperLogLog();
    }

}

最后,配置message-service-test.properties或者application.xml文件

spring.redis.database=2
spring.redis.host=192.168.8.123
spring.redis.password=test123456
spring.redis.port=6379
spring.redis.pool.max-active=8
spring.redis.expire=10
spring.redis.max-wait=200
spring.redis.timeout=10000

三、Oss文件服务

四、全局Controller异常处理

在controller包下创建类

package com.cheng2839.message.controller.common;

import com.cheng2839.message.common.model.RsJsonResult;
import com.cheng2839.message.common.enums.BasicErrorEnum;
import com.cheng2839.message.common.exception.BasicException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 原文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
 *
 */
@ControllerAdvice
@ResponseBody
public class MessageExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(MessageExceptionHandler.class);

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public RsJsonResult<String> methodExceptionHandler(MethodArgumentNotValidException exception) {
        String errMsg = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        logger.error("Interface[message] MethodArgumentNotValidException : {}",errMsg);
        return RsJsonResult.error(BasicErrorEnum.BASIC_PARAM_ERROR.getCode(), errMsg);
    }

    @ExceptionHandler(value = BasicException.class)
    public RsJsonResult<String> methodExceptionHandler(BasicException exception) {
        String errMsg = exception.getMessage();
        logger.error("Interface[message] BasicException : {}",errMsg);
        return RsJsonResult.error(BasicErrorEnum.BASIC_ERROR.getCode(), errMsg);
    }

    @ExceptionHandler(value = Exception.class)
    public RsJsonResult<String> methodExceptionHandler(Exception exception) {
        String errMsg = exception.getMessage();
        logger.error("Interface[message] Exception : {}", exception);
        return RsJsonResult.error(BasicErrorEnum.BASIC_ERROR.getCode(), errMsg);
    }

}

五、全局Controller日志打印(采用AOP技术)

 可参考我以前的博文AOP日志拦截器 https://www.cnblogs.com/cheng2839/p/12614108.html

六、拦截器

 拦截器框架如下

package com.cheng2839.message.interceptor;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Component
public class MessageContextInterceptor extends HandlerInterceptorAdapter {

    @Override
    public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        request.setAttribute(START_TIME, System.currentTimeMillis());
        //此处写具体的拦截逻辑
        } catch (Exception e) {
            logger.error("异常url:{}", request.getRequestURI());
            logger.error("初始化用户信息异常,异常:{}", e);
        }
        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        logger.debug("清除信息:{}", MessageContext.getId());
        MessageContext.remove();
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        super.postHandle(request, response, handler, modelAndView);
    }

}

七、全局CORS跨域处理

通过全局配置类实现

package com.cheng2839.message.config;

import com.cheng2839.message.common.Constance;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
* 本文地址:https://www.cnblogs.com/cheng2839/p/14788655.html
*/ @Configuration
public class CorsConfig { private static CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setMaxAge(Constance.NUM_3600); corsConfiguration.addAllowedMethod(HttpMethod.POST); corsConfiguration.addAllowedMethod(HttpMethod.GET); corsConfiguration.addAllowedMethod(HttpMethod.DELETE); corsConfiguration.addAllowedMethod(HttpMethod.PUT); corsConfiguration.addAllowedMethod(HttpMethod.OPTIONS); corsConfiguration.setAllowCredentials(true); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } }

八、Mybatis(DAO)自动化生成配置

九、通用工具类

1)自定义封装ListUtil工具类 https://www.cnblogs.com/cheng2839/p/13853903.html

2)模拟postman开发的Http请求工具类 https://www.cnblogs.com/cheng2839/p/13226309.html

 3)加密解密工具类 https://www.cnblogs.com/cheng2839/p/12659932.html

4)线程池管理工具类 https://www.cnblogs.com/cheng2839/p/12620079.html

十、Mysql和Oracle双数据源无缝切换

参考:https://www.cnblogs.com/cheng2839/p/14144516.html

十一、参数校验

可参考自定义注解校验工具类,大大提高开发效率

或者使用内置的注解

如下Controller

    @PostMapping(value = "/message/copy", produces = "application/json;charset=utf-8")
    public RsJsonResult<String> copy(@RequestBody @Validated(value = {MessageRequest.MessageCopy.class}) MessageRequest request) {
}

参数MessageRequest如下

package com.cheng2839.message.controller.bean;

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

/**
 * @Description https://www.cnblogs.com/cheng2839
 * @Author cheng2839
 * @Date
 * @Version v1.0
 */
@Data
public class MessageRequest {

    @NotNull(message = "Id不能为空", groups = {MessageUpdate.class, MessageCopy.class})
    private Long id;

    private String status;

    @Pattern(message = "名称需1至15个字符", regexp = ".{1,15}", groups = {MessageInsert.class, MessageCopy.class})
    @NotBlank(message = "名称不能为空", groups = {MessageInsert.class, MessageCopy.class})
    private String name;

    private String desc;

    @NotNull(message = "urlId不能为空", groups = {MessageInsert.class})
    private Long urlId;

    public interface MessageInsert{}

    public interface MessageUpdate{}

    public interface MessageCopy{}

}

剩下就可以专心开发业务功能了

备注:该博文技术点可供参考,此为作者原创,版权原因(如需要完整的微服务工程源码,仅需付费39元(现优惠价19元,截止2021年6月30日),备注微服务初始化邮箱,即可发至邮箱),如有需要可留言联系作者,请尊重作者时间和精力的耗费,见谅!

 

____________________________特此,勉励____________________________
本文作者cheng2839
本文链接https://www.cnblogs.com/cheng2839
关于博主:评论和私信会在第一时间回复。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
原文地址:https://www.cnblogs.com/cheng2839/p/14788655.html