3 微服务实战系列 - SpringBoot项目实战

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\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 context
public 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.................");
    }


}

猜你喜欢

转载自blog.csdn.net/wolfjson/article/details/78502107