您的位置:首页 > 编程语言 > Java开发

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

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息