在实际开发中,我们会对独立的模块进行封装,使用微服务来承载这个模块的所有功能。
那么,如何从零搭建一个微服务呢?
本文建立的微服务包含以下几点基础能力:
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日),备注微服务初始化和邮箱,即可发至邮箱),如有需要可留言联系作者,请尊重作者时间和精力的耗费,见谅!