3 微服务实战系列 - SpringBoot项目实战
2017-11-10 17:50
666 查看
1 环境配置
注册中心(服务治理:注册与发现nginx反向代理) 111.231.112.151:81 111.231.112.151:8001 123.207.218.250:8001 服务网关 111.231.112.151:80 111.231.112.151:8002 123.207.218.250:8002
2 配置
2.1 配置线程池/任务执行器SpringBoot默认单线程执行,需要多线程异步执行,需要@EnableAsync并设置ThreadPoolTaskExecutor,仅仅开启@EnableAsync 1.4.X版本仍是单线程执行,1.5.X会报错提醒设置线程池!
//可以设置执行任务的线程池的数量。默认是单线程。 @Bean public ThreadPoolTaskScheduler getDefaultThreadPoolScheduler(){ ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler(); threadPoolTaskScheduler.setPoolSize(2); threadPoolTaskScheduler.setThreadNamePrefix("scheduleMoon"); return threadPoolTaskScheduler; } //spingBoot默认单线程执行 @Bean public ThreadPoolTaskExecutor createThreadPoolTaskExecutor() { ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(10); threadPoolTaskExecutor.setMaxPoolSize(20); return threadPoolTaskExecutor; }
2.2 配置跨域
private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; } /** * 跨域过滤器 * @return */ @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); // 4 return new CorsFilter(source); }
2.3 配置文件上传
@Bean public MultipartConfigElement multipartConfigElement() { MultipartConfigFactory factory = new MultipartConfigFactory(); //// 设置文件大小限制 ,超了,页面会抛出异常信息,这时候就需要进行异常信息的处理了; factory.setMaxFileSize("400MB"); //KB,MB /// 设置总上传数据总大小 factory.setMaxRequestSize("400MB"); //Sets the directory location where files will be stored. //factory.setLocation("路径地址"); return factory.createMultipartConfig(); }
2.4 配置mybatis
/** * MyBatis 配置 * */ @Configuration @MapperScan(basePackages = "com.ttd.mapper") public class MyBatisConfiguration { private static final Logger logger = LoggerFactory.getLogger(MyBatisConfiguration.class); @Bean public PageHelper pageHelper(DataSource dataSource) { logger.info("注册MyBatis分页插件PageHelper"); PageHelper pageHelper = new PageHelper(); Properties p = new Properties(); p.setProperty("offsetAsPageNum", "true"); p.setProperty("rowBoundsWithCount", "true"); p.setProperty("reasonable", "true"); pageHelper.setProperties(p); return pageHelper; } }
3 多数据源实战
3.1 配置application# \u4e3b\u6570\u636e\u6e90\uff0c\u9ed8\u8ba4\u7684 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://*****/***** spring.datasource.username=root spring.datasource.password=**** # \u66f4\ 4000 u591a\u6570\u636e\u6e90 custom.datasource.names=ds1,ds2 custom.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds1.url=jdbc:mysql://*****/***** custom.datasource.ds1.username=root custom.datasource.ds1.password=***** custom.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver custom.datasource.ds2.url=jdbc:mysql://*****/***** custom.datasource.ds2.username=root custom.datasource.ds2.password=****** # 下面为连接池的补充设置,应用到上面所有数据源中 spring.datasource.initialSize=5 spring.datasource.minIdle=5 spring.datasource.maxActive=20 # 配置获取连接等待超时的时间 spring.datasource.maxWait=60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 spring.datasource.timeBetweenEvictionRunsMillis=60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 spring.datasource.minEvictableIdleTimeMillis=300000 spring.datasource.validationQuery=SELECT 1 FROM DUAL spring.datasource.testWhileIdle=true spring.datasource.testOnBorrow=false spring.datasource.testOnReturn=false # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 spring.datasource.filters=stat,wall,log4j spring.datasource.logSlowSql=true
3.2 注册定义数据源bean
定义bean实现ImportBeanDefinitionRegistrar并在程序入口导入@Import(DynamicDataSourceRegister.class) @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { System.out.println("####加载数据源####"+ defaultDataSource); Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); // 将主数据源添加到更多数据源中 targetDataSources.put("dataSource", defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); // 添加更多数据源 targetDataSources.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } // 创建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("#############################Dynamic DataSource Registry########################"); }
3.3 多数据源aop切换
@Before("@annotation(ds)") public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable { String dsId = ds.name(); if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature()); } else { logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(ds.name()); } } @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, TargetDataSource ds) { logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); }
3.4 druid数据源监控
/** * 注册一个StatViewServlet * #####此配置需要在入口程序添加@ServletComponentScan * @WebServlet(urlPatterns = "/druid/*", * initParams={ * @WebInitParam(name="allow",value="127.0.0.1"),// IP白名单 (没有配置或者为空,则允许所有访问) * @WebInitParam(name="deny",value="192.168.16.111"),// IP黑名单 (存在共同时,deny优先于allow) * @WebInitParam(name="loginUsername",value="wolf"),// 用户名 * @WebInitParam(name="loginPassword",value="wolf"),// 密码 * @WebInitParam(name="resetEnable",value="false")// 禁用HTML页面上的“Reset All”功能 * }) * public class DruidStatViewServlet extends StatViewServlet { * * } * @return */ @Bean public ServletRegistrationBean DruidStatViewServle(){ ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*"); //添加初始化参数:initParams //白名单: // servletRegistrationBean.addInitParameter("allow","127.0.0.1"); //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page. // servletRegistrationBean.addInitParameter("deny","192.168.1.73"); //登录查看信息的账号密码. servletRegistrationBean.addInitParameter("loginUsername", "wolf"); servletRegistrationBean.addInitParameter("loginPassword", "wolf"); //是否能够重置数据. servletRegistrationBean.addInitParameter("resetEnable", "false"); return servletRegistrationBean; } /** * 注册一个:filterRegistrationBean * @WebFilter(filterName = "druidWebStatFilter", urlPatterns = "/*", * initParams = { @WebInitParam(name = "exclusions", value = "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid2/*") // 忽略资源 * }) * public class DruidStatFilter extends WebStatFilter { * * } * @return */ @Bean public FilterRegistrationBean druidStatFilter(){ FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter()); //添加过滤规则. filterRegistrationBean.addUrlPatterns("/*"); //添加不需要忽略的格式信息. filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid2/*"); return filterRegistrationBean; }
4 redis实战
4.1 redis配置/** * 生成key的策略 * * @return */ @Bean public KeyGenerator keyGenerator() { logger.info("########################redis 初始化配置!!!#################"); return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * 管理缓存 * * @param redisTemplate * @return */ @SuppressWarnings("rawtypes") @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); //设置缓存过期时间 // rcm.setDefaultExpiration(60);//秒 //设置value的过期时间 Map<String,Long> map=new HashMap(); map.put("test",60L); rcm.setExpires(map); return rcm; } /** * RedisTemplate配置 * @param factory * @return */ @SuppressWarnings("rawtypes") @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { StringRedisTemplate template = new StringRedisTemplate(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; }
4.2 redis utils操作
@Test public void testRedis() { /*redisUtil.set("testKey", "spring:session:expirations:1499333280000"); System.out.println(redisUtil.get("testKey")); System.out.println(redisUtil.exists("testKey"));*/ }
4.3 redis spring业务缓存
@Service("cacheForemanHouseService") @CacheConfig(cacheNames={"cacheForemanHouse"}) public class CacheForemanHouseService extends BaseService<CacheForemanHouse, Long> { }
5 安全控制
5.1 request入参解密*/ @WebFilter(filterName = "webAPPFilter", urlPatterns ={ "/foremanInfo/*", "/houseInfo/*", "/orderInfo/*", "/userInfo/*"}, initParams={@WebInitParam(name="excluds", value="/druid/*")}) public class WebAPPFilter implements Filter { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final String iv = DigestUtils.sha256Hex("Totodi!@#").substring(0, 16); private String[] excluds = null; @Override public void destroy() { excluds = null; } private void handleParamter(String queryString, Map<String, String[]> retParams) { String[] params = queryString.split("&"); for (int i = 0; i < params.length; i++) { int splitIndex = params[i].indexOf("="); if (splitIndex == -1) { continue; } String key = params[i].substring(0, splitIndex); if (splitIndex < params[i].length()) { String value = params[i].substring(splitIndex + 1); retParams.put(key, new String[] {value}); } } } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse res = (HttpServletResponse) response; HttpServletRequest req = (HttpServletRequest) request; //调试不需要进行加密 if (StringUtils.equalsIgnoreCase(req.getParameter("skipEncryption"), "true") || StringUtils.contains(req.getQueryString(), "skipEncryption") || StringUtils.contains(req.getRequestURI(), "favicon.ico")) { chain.doFilter(req, res); return; } // 解决跨域访问 res.addHeader("Access-Control-Allow-Origin", "*"); res.addHeader("Access-Control-Allow-Methods", "GET"); res.addHeader("Access-Control-Allow-Methods", "POST"); res.addHeader("Access-Control-Allow-Headers", "x-requested-with,content-type"); // 判断是否OPTIONS请求 if (req.getMethod().equals("OPTIONS")) { res.setStatus(HttpStatus.OK.value()); return; } else { try { Map<String, String[]> retParams = Maps.newHashMap(); //request key读写 Map<String, String[]> params = new HashMap<String,String[]>(request.getParameterMap()); for (Entry<String, String[]> item : params.entrySet()) { logger.info(">>>拦截前参数:"+item.getValue()[0]); String newVal = AESUtils.decrypt(item.getValue()[0], iv); logger.info(">>>拦截后参数:"+newVal); handleParamter(newVal, retParams); } //requet body流读写 /* byte[] buf = new byte[1024]; while (req.getInputStream().readLine(buf, 0, 1024) > 0) { String tem = new String(buf); logger.info(">>>拦截前参数:" + tem); String newVal = AESUtils.decrypt(tem, iv); logger.info(">>>拦截后参数:" + newVal); handleParamter(newVal, retParams); }*/ req = new ParameterRequestWrapper((HttpServletRequest)req, retParams); chain.doFilter(req, res); } catch (Exception e) { e.printStackTrace(); } } } @Override public void init(FilterConfig config) throws ServletException { String vals = config.getInitParameter("excluds"); excluds = vals.split(","); } }
5.2 response解密
/** * 输出文本 * @param txt * @param contextType */ protected void write(String txt, String contextType) { try { if(Strings.isNullOrEmpty(txt)){ return; } getResponse().setContentType(contextType); if (StringUtils.isNotBlank(getRequest().getParameter("skipEncryption")) && getRequest().getParameter("skipEncryption").equals("true")) { LOGGER.info("response skipEncryption:" + txt); StreamUtil.writeData(txt.getBytes("UTF-8"), getResponse().getOutputStream()); } else { LOGGER.info("response 明文:" + txt); String encryString = AESUtils.aesEncipherString(AESUtils.KEY, AESUtils.IV, txt); LOGGER.info("response 密文:" + encryString); StreamUtil.writeData(encryString.getBytes("UTF-8"), getResponse().getOutputStream()); } } catch (Exception ex) { throw new RuntimeException(ex); } }
5.3 配置全局拦截器
@Override public void addInterceptors(InterceptorRegistry registry) { // addPathPatterns 用于添加拦截规则 // excludePathPatterns 用户排除拦截 registry.addInterceptor(new GlobalInterceptor()).addPathPatterns("/**").excludePathPatterns("/common/**"); // registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**"); super.addInterceptors(registry); }
5.4 移动端请求生成唯一hash
//移动设备:mac、os、version、timestamp全局hash校验 private void refreHash(ServletContextUtil context, boolean refree) { HttpServletRequest request = context.getRequest(); String mac = context.getParameter("mac"); String os = context.getParameter("os"); String version = context.getParameter("version"); String hashKey = EncryptUtils.encodeMD532(mac + os + version, null); BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext()); RedisUtil redisUtil = (RedisUtil) factory.getBean("redisUtil"); if (refree) { if (redisUtil.exists(hashKey)) { redisUtil.remove(hashKey); logger.info("|----------释放hash-----------"); } } else { redisUtil.set(hashKey, true); logger.info("|----------存储hash-----------"); } } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ServletContextUtil context = ServletContextUtil.getContext(); // 设置本地线程变量 context.setRequest(request); context.setResponse(response); HandlerMethod method = (HandlerMethod) handler; Object[] params = new Object[] { request.getRequestURI(), request.getParameterMap() }; logger.info("request:请求地址:{}-请求参数-{}", params); boolean ret = authentication(method.getMethodAnnotation(Authentication.class), context); if (ret) { refreHash(context, false); } return ret; }
5.5 释放hash
如支付/订单发起申请,由于网络或者被攻击或者延迟未响应,提示客户端请求处理中,同一设备处理挂起操作。
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { ServletContextUtil context = ServletContextUtil.getContext(); refreHash(context, true); }
6 监听器
6.1 spring application contextpublic class ApplicationStartUpListener implements ApplicationListener<ApplicationStartingEvent>{ @Override public void onApplicationEvent(ApplicationStartingEvent event) { PropertyUtil.loadAllProperties(); System.out.println(">>>>>>>>>>>>>>>>>>>>>>ApplicationStartUpListener EXEC"); } }
6.2 servlet context
@WebListener public class MyServletContextListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { System.out.println("ServletContex初始化"); System.out.println(sce.getServletContext().getServerInfo()); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println("ServletContex销毁"); } }
7 请求aop日志切割
@Aspect @Component public class LogOrderAspect { private static final Logger logger = LoggerFactory.getLogger(LogOrderAspect.class); @Resource private LogOrderOperationService logOrderService; @Before("@annotation(orderAspect)") public void gennerateLog(JoinPoint point, LogOrderAnnotation orderAspect) throws Throwable { logger.info(">>>>>>订单操作开始aspect"); ServletContextUtil context = ServletContextUtil.getContext(); HttpServletRequest request = context.getRequest(); LogOrderOperation log = new LogOrderOperation(); log.setAction(orderAspect.action()); log.setUserId(LongUtils.parseLong(request.getParameter("userId"))); Map<String, String[]> params = Maps.newHashMap(request.getParameterMap()); params.put("url", new String[]{request.getRequestURI()}); log.setParams(JSON.toJSONString(params)); log.setCreated(new Date()); logOrderService.insertEntry(log); } @After("@annotation(orderAspect)") public void endLog(JoinPoint point, LogOrderAnnotation orderAspect) { logger.info(">>>>>>订单操作执行完aspect"); } @AfterReturning("@annotation(orderAspect)") public void returnLog(JoinPoint point, LogOrderAnnotation orderAspect) { logger.info(">>>>>>订单操作执行完aspect,return................."); } }
相关文章推荐
- 4 微服务实战系列 - SpringBoot RabbitMQ 实战解决项目中实践
- 【备忘】2017年最新 项目实战 Spring Boot视频教程 微服务整合Mybatis
- CK1956-2017年最新项目实战Spring Boot视频微服务整合Mybatis
- 整合阿里dubbo服务的项目搭建实战(springboot)
- CK1956-2017年最新项目实战Spring Boot视频微服务整合Mybatis
- QiYuAdmin-Quartz定时器动态启停服务(SpringBoot项目实战)
- 基于SpringBoot 2.0正式版的SpringCloud的微服务实战项目搭建
- Spring Boot实战01-创建Spring Boot项目
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:JPA
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战:分布式锁
- [置顶] spring boot项目实战-集合操作
- [置顶] spring boot项目实战之工具篇(json)
- [置顶] spring boot项目实战之公共代码抽取
- 微服务介绍及Asp.net Core实战项目系列之微服务介绍
- [置顶] spring boot项目实战:跨域问题解决
- SpringBoot项目实战--环境搭建
- [置顶] spring boot项目实战:swagger2在线文档
- [置顶] spring boot项目实战:swagger2在线文档