话不多说:先看 pom.xml 将来有可以修改为 gradle 来管理
4.0.0 com.xdf newcrm war 1.0-SNAPSHOT com.xdf 91-master 1.0.0.RELEASE UTF-8 UTF-8 1.8 org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.1 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web com.alibaba druid 1.0.29 com.xdf oracle6 1.0 org.apache.poi poi 3.15 net.sourceforge.jexcelapi jxl 2.6.12 com.sun.mail javax.mail org.freemarker freemarker org.springframework.boot spring-boot-configuration-processor org.springframework.boot spring-boot-starter-data-jpa org.aspectj aspectjweaver 1.8.9 com.github.pagehelper pagehelper 5.0.0 junit junit 4.11 test org.springframework.boot spring-boot-devtools true com.alibaba fastjson 1.2.6 com.google.code.gson gson 2.8.5 org.apache.httpcomponents httpclient 4.3.5 org.apache.httpcomponents httpmime 4.3.5 org.apache.httpcomponents httpcore 4.3.2 org.jsoup jsoup 1.11.2 dom4j dom4j 1.6 org.apache.tomcat.embed tomcat-embed-jasper javax.servlet jstl net.sf.json-lib json-lib 2.4 jdk15 compile com.belerweb pinyin4j 2.5.0 org.springframework.boot spring-boot-maven-plugin src/main/java **/*.xml src/main/resources
其中部分jar 属于内部仓库 封装的一些源码,企业用户可查看
先看一下目录结构
看一下配置文件:
在resources 目录下
主体文件为:application.xml
根据不同的环境
在编译时 根据命令产生不同 的war 文件
logging: config: classpath:logback.xml path: d:/logs server: port: 80 session-timeout: 60 spring: profiles: active: dev mvc: view: prefix: /WEB-INF/web/ suffix: .jsp jpa: show-sql: true mybatis: mapperLocations: classpath:/com/xdf/newcrm/mapper/*.xml typeAliasesPackage: com.xdf.newcrm.mapper mapperScanPackage: com.xdf.newcrm.mapper configLocation: classpath:/mybatis-config.xml oracle: datasource: readSize: 2 #读库个数 type: com.alibaba.druid.pool.DruidDataSource mapperLocations: classpath:/com/xdf/newcrm/mapper/*.xml configLocation: classpath:/mybatis-config.xml write: url: jdbc:oracle:thin:@110.86.22.10:210:orcl username: **** password: *** driver-class-name: oracle.jdbc.driver.OracleDriver read01: url: jdbc:oracle:thin:@110.86.22.10:210:orcl username: **** password: ***** driver-class-name: oracle.jdbc.driver.OracleDriver read02: url: jdbc:oracle:thin:@110.86.22.10:210:orcl username: **** password: **** driver-class-name: oracle.jdbc.driver.OracleDriver
我们看下 研发环境 application-dev.xml
env: u2AuthorizeUrl: http://testu2.staff.xdf.cn/index.aspx u2AccessTokenUrl: http://testu2.staff.xdf.cn/apis/OAuth.ashx u2LogoutUrl: http://testu2.staff.xdf.cn/Logout.aspx clientId: 9688 clientSecret: u2test-bd848c88-4b2e-4209-b228-47e33da8fa5c callbackUrl: http://deviteach.staff.xdf.cn/callback_login logoffCallbackUrl: /exit.jsp u2ApiUrl: http://testu2.staff.xdf.cn/apis/usersv2.ashx u2LoginUrl: http://testu2.staff.xdf.cn/i/ys/index.aspx u2AppID: 90101 u2AppKey: u2testAppKey#$vs baseUrl: http://testiteach.staff.xdf.cn domain: /newcrm
application-prod.xml的内容与application-dev.xml 类似
就不在列举
application.xml 文件 作为主体文件,主要描述公共的内容
异同的内容放在其各自的文件中
在spring boot 2.0中的变化和 1.X有这明显的变化
介绍一下 mybatis 多数据源的配置
package com.xdf.newcrm.config.dbconfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; /** * 数据库源配置 * @author nieshoujun * */ @Configuration public class DataSourceConfiguration { private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class); @Value("${oracle.datasource.type}") private Class extends DataSource> dataSourceType; /** * 写库 数据源配置 * @return */ @Bean(name = "writeDataSource") @Primary @ConfigurationProperties(prefix = "oracle.datasource.write") public DataSource writeDataSource() { log.info("-------------------- writeDataSource init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } /** * 有多少个从库就要配置多少个 * @return */ @Bean(name = "readDataSource01") @ConfigurationProperties(prefix = "oracle.datasource.read01") public DataSource readDataSourceOne() { log.info("-------------------- read01 DataSourceOne init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean(name = "readDataSource02") @ConfigurationProperties(prefix = "oracle.datasource.read02") public DataSource readDataSourceTwo() { log.info("-------------------- read02 DataSourceTwo init ---------------------"); return DataSourceBuilder.create().type(dataSourceType).build(); } }
这里面的环境变量
采用层级的方式来读取
@Value("${oracle.datasource.type}")
DataSourceContextHolder.java
package com.xdf.newcrm.config.dbconfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 本地线程,数据源上下文 * @author niejun * */ public class DataSourceContextHolder { private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class); //线程本地环境 private static final ThreadLocallocal = new ThreadLocal (); public static ThreadLocal getLocal() { return local; } /** * 读库 */ public static void setRead() { local.set(DataSourceType.read.getType()); log.info("数据库切换到读库..."); } /** * 写库 */ public static void setWrite() { local.set(DataSourceType.write.getType()); log.info("数据库切换到写库..."); } public static String getReadOrWrite() { return local.get(); } public static void clear(){ local.remove(); } }
package com.xdf.newcrm.config.dbconfig; public enum DataSourceType { read("read", "从库"), write("write", "主库"); private String type; private String name; DataSourceType(String type, String name) { this.type = type; this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.xdf.newcrm.config.dbconfig; import com.github.pagehelper.PageHelper; import com.xdf.newcrm.common.util.SpringContextUtil; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; 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.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; @Configuration @AutoConfigureAfter(DataSourceConfiguration.class) @MapperScan(basePackages="com.xdf.newcrm.mapper") public class MybatisConfiguration { private static Logger log = LoggerFactory.getLogger(MybatisConfiguration.class); @Value("${oracle.datasource.readSize}") private String readDataSourceSize; //XxxMapper.xml文件所在路径 @Value("${oracle.datasource.mapperLocations}") private String mapperLocations; // 加载全局的配置文件 @Value("${oracle.datasource.configLocation}") private String configLocation; @Autowired @Qualifier("writeDataSource") private DataSource writeDataSource; @Autowired @Qualifier("readDataSource01") private DataSource readDataSource01; @Autowired @Qualifier("readDataSource02") private DataSource readDataSource02; @Bean(name="sqlSessionFactory") public SqlSessionFactory sqlSessionFactorys() throws Exception { log.info("-------------------- sqlSessionFactory init ---------------------"); try { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); // sessionFactoryBean.setDataSource(roundRobinDataSouce); sessionFactoryBean.setDataSource(roundRobinDataSouceProxy()); // 读取配置 sessionFactoryBean.setTypeAliasesPackage("com.xdf.newcrm.bean"); //设置mapper.xml文件所在位置 Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations); sessionFactoryBean.setMapperLocations(resources); //设置mybatis-config.xml配置文件位置 sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); //添加分页插件、打印sql插件 Interceptor[] plugins = new Interceptor[]{new SqlPrintInterceptor()}; sessionFactoryBean.setPlugins(plugins); return sessionFactoryBean.getObject(); } catch (IOException e) { log.error("mybatis resolver mapper*xml is error",e); return null; } catch (Exception e) { log.error("mybatis sqlSessionFactoryBean create error",e); return null; } } /** * 分页插件 * @return */ @Bean public PageHelper pageHelper() { PageHelper pageHelper = new PageHelper(); Properties p = new Properties(); p.setProperty("offsetAsPageNum", "true"); p.setProperty("rowBoundsWithCount", "true"); p.setProperty("reasonable", "true"); p.setProperty("returnPageInfo", "check"); p.setProperty("params", "count=countSql"); pageHelper.setProperties(p); return pageHelper; } /** * 把所有数据库都放在路由中 * @return */ @Bean(name="roundRobinDataSouceProxy") public AbstractRoutingDataSource roundRobinDataSouceProxy() { Map
package com.xdf.newcrm.config.dbconfig; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.mapping.ParameterMode; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.type.TypeHandlerRegistry; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.regex.Matcher; /** * MyBatis 将mybatis要执行的sql拦截打印出来 * * @since 1.0.0 */ @Intercepts ({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class SqlPrintInterceptor implements Interceptor { private static Log logger = LogFactory.getLog(SqlPrintInterceptor.class); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameterObject = null; if (invocation.getArgs().length > 1) { parameterObject = invocation.getArgs()[1]; } long start = System.currentTimeMillis(); Object result = invocation.proceed(); String statementId = mappedStatement.getId(); BoundSql boundSql = mappedStatement.getBoundSql(parameterObject); Configuration configuration = mappedStatement.getConfiguration(); String sql = getSql(boundSql, parameterObject, configuration); long end = System.currentTimeMillis(); long timing = end - start; if(logger.isInfoEnabled()){ logger.info("执行sql耗时:" + timing + " ms" + " - id:" + statementId + " - Sql:" ); logger.info(" "+sql); } return result; } @Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { } private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) { String sql = boundSql.getSql().replaceAll("[\\s]+", " "); ListparameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } sql = replacePlaceholder(sql, value); } } } return sql; } private String replacePlaceholder(String sql, Object propertyValue) { String result; if (propertyValue != null) { if (propertyValue instanceof String) { result = "'" + propertyValue + "'"; } else if (propertyValue instanceof Date) { result = "'" + DATE_FORMAT.format(propertyValue) + "'"; } else { result = propertyValue.toString(); } } else { result = "null"; } return sql.replaceFirst("\\?", Matcher.quoteReplacement(result)); } }
到此为止多数据源部分的配置完毕
下面介绍一下,关于安全部分
Spring boot 2.0 集成了 spring Security 框架
其原理和shiro 很相似
在每次调用时根据提供的权限资源 做认证
配置了额外的拦截器
package com.xdf.newcrm.config; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * Created by niejun on 2017/12/29. */ @Component public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException { resp.setStatus(HttpServletResponse.SC_FORBIDDEN); resp.setContentType("application/json;charset=UTF-8"); PrintWriter out = resp.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush(); out.close(); } }
package com.xdf.newcrm.config; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Iterator; /** * Created by niejun on 2017/12/28. */ @Component public class UrlAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collectioncollection) throws AccessDeniedException, AuthenticationException { Iterator iterator = collection.iterator(); while (iterator.hasNext()) { ConfigAttribute ca = iterator.next(); //当前请求需要的权限 String needRole = ca.getAttribute(); if ("ROLE_LOGIN".equals(needRole)) { if (authentication instanceof AnonymousAuthenticationToken) { throw new BadCredentialsException("未登录"); } else return; } //当前用户所具有的权限 Collection extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)) { return; } } } return; // throw new AccessDeniedException("权限不足!"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class> aClass) { return true; } }
package com.xdf.newcrm.config; import java.util.Collection; import com.xdf.grow.common.util.UserUtils; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; /** * Created by niejunon 2017/12/28. */ @Component public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { /* @Autowired MenuService menuService;*/ AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public CollectiongetAttributes(Object o) throws IllegalArgumentException { //获取请求地址 String requestUrl = ((FilterInvocation) o).getRequestUrl(); if ("/login_p".equals(requestUrl)) { return null; } /* List
package com.xdf.newcrm.config; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import com.xdf.grow.common.util.DateConverter; /** * Created by niejun on 2018/5/30. */ @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new DateConverter()); } @Bean public ExecutorService executorService() { return Executors.newCachedThreadPool(); } }
package com.xdf.newcrm.config; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import com.fasterxml.jackson.databind.ObjectMapper; import com.xdf.grow.common.util.UserUtils; /** * Created by niejun on 2018/5/30. */ @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { /* @Autowired HrService hrService;*/ @Autowired UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource; @Autowired UrlAccessDecisionManager urlAccessDecisionManager; @Autowired AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { /* auth.userDetailsService(hrService).passwordEncoder(new BCryptPasswordEncoder());*/ } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/static/**","/login_p","/index"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessor() { @Override public O postProcess(O o) { o.setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource); o.setAccessDecisionManager(urlAccessDecisionManager); return o; } }).and().formLogin().loginPage("/login_p").loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password").permitAll().failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); StringBuffer sb = new StringBuffer(); sb.append("{\"status\":\"error\",\"msg\":\""); if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) { sb.append("用户名或密码输入错误,登录失败!"); } else if (e instanceof DisabledException) { sb.append("账户被禁用,登录失败,请联系管理员!"); } else { sb.append("登录失败!"); } sb.append("\"}"); out.write(sb.toString()); out.flush(); out.close(); } }).successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); ObjectMapper objectMapper = new ObjectMapper(); String s = "{\"status\":\"success\",\"msg\":" + objectMapper.writeValueAsString(UserUtils.getCurrentUser()) + "}"; out.write(s); out.flush(); out.close(); } }).and().logout().permitAll().and().csrf().disable().exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler); } }
以上几个类主要介绍了 框架结构中的安全部分如何定义
接下来说一下
自定义异常
本文主要介绍一下 关于和框架相关的自定义异常,如果在程序执行过程中 出现了任何的异常
继承
HandlerExceptionResolver
会监听到异常后,并补获 根据 自己定义的 json 格式输出
package com.xdf.newcrm.exception; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; /** * Created by niejunon 2018/1/2. */ @Component public class CustomExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse response, Object o, Exception e) { ModelAndView mv = new ModelAndView(new MappingJackson2JsonView()); Mapmap = new HashMap<>(); map.put("status", "error"); if (e instanceof DataIntegrityViolationException) { map.put("msg", "该角色尚有关联的资源或用户,删除失败!"); }else{ map.put("msg", "操作失败!"); } mv.addAllObjects(map); return mv; } }
自此项目中几个比较核心的部分都已经叙述完毕
关于jpa 的内容 如何使用 将在下一篇博文中去阐释。