分析
针对项目的一些背景和环境的分析:
- 该项目采用的是oracle数据,生产上的数据采用专线物理隔离的形式隔离开。
- 系统的使用方为内部工作人员,是小范围的操作,使用用户量不高于3000人。
- 目前不考虑权限的问题,但是后面功能扩展可能需要用到。
- 每次登录token的有效时间在1小时左右,并不需要控制单点登录功能
因此:
- 决定采用前后端分离,后端采用springboot+maven;
- 利用mybatis plus连接oralce,数据库连接池采用druid;
- 鉴权采用springSecurity+jwtToken的形式,json解析用fastjson;
- 本来也就我一个人开发,但是还是撸上了swagger对接口进行说明,便于后期管理与维护;
- 并不会与外部系统做交互,所以不用考虑暴露接口问题。
预备:
- 如果后面访问量上去,可以撸上redis;
- 利用redis可以做单点登录控制。
正式工作
一、初始化springboot项目
1.在idea里面初始化spingboot项目非常方便,网上的参考资料很多,不懂的读者随便百度一下都可以初始化一个maven管理的springboot项目。
- 根据前面提到的引入相应的maven配置
org.springframework.boot
spring-boot-starter-security
com.alibaba
druid-spring-boot-starter
1.1.14
io.springfox
springfox-swagger2
2.6.1
com.baomidou
mybatis-plus-boot-starter
3.1.0
com.baomidou
mybatis-plus
3.1.0
com.baomidou
mybatis-plus-generator
3.1.0
io.jsonwebtoken
jjwt
0.7.0
关于maven的介绍,不清楚的读者可以参考我之前的博客:https://www.jianshu.com/p/477ad2e14150
二、搭建数据库连接
DataSourceConfig
@Configuration
@Slf4j
@Order(1)
public class DataSourceConfig {
@Autowired
private DruidConfig druidConfig;
// 如果不是 druid 的 datasource, 直接用下面这个方式就可以, 但是 SpringBoot 默认还不支持 druid
// @Bean(name = "hkb2bDataSource")
// @ConfigurationProperties(prefix = "datasource.hkb2b")
// public DataSource hkb2bDataSource() {
// return DataSourceBuilder.create().build();
// }
@Bean(name = "demoDataSource")
public DataSource demoDataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(druidConfig.getUrl());
datasource.setUsername(druidConfig.getUsername());
datasource.setPassword(druidConfig.getPassword());
datasource.setDriverClassName(druidConfig.getDriverClassName());
datasource.setInitialSize(druidConfig.getInitialSize());
datasource.setMinIdle(druidConfig.getMinIdle());
datasource.setMaxActive(druidConfig.getMaxActive());
datasource.setMaxWait(druidConfig.getMaxWait());
datasource.setTimeBetweenEvictionRunsMillis(druidConfig.getTimeBetweenEvictionRunsMillis());
datasource.setMinEvictableIdleTimeMillis(druidConfig.getMinEvictableIdleTimeMillis());
datasource.setValidationQuery(druidConfig.getValidationQuery());
datasource.setTestWhileIdle(druidConfig.isTestWhileIdle());
datasource.setTestOnBorrow(druidConfig.isTestOnBorrow());
datasource.setTestOnReturn(druidConfig.isTestOnReturn());
datasource.setPoolPreparedStatements(druidConfig.isPoolPreparedStatements());
datasource.setMaxPoolPreparedStatementPerConnectionSize(
druidConfig.getMaxPoolPreparedStatementPerConnectionSize());
try {
datasource.setFilters(druidConfig.getFilters());
} catch (SQLException e) {
log.error("druid configuration initialization filter", e);
}
datasource.setConnectionProperties(druidConfig.getConnectionProperties());
return datasource;
}
// @Primary
// @Bean(name = "demoJdbcTemplate")
// public NamedParameterJdbcTemplate demoJdbcTemplate() {
// return new NamedParameterJdbcTemplate(demoDataSource());
// }
@Configuration
@ConfigurationProperties(prefix = "datasource.demo")
@Data
public class DruidConfig {
// http://www.voidcn.com/blog/blueheart20/article/p-6181465.html
// http://my.oschina.net/letao/blog/518012
// https://github.com/alibaba/druid/wiki/配置_DruidDataSource参考配置
private String url;
private String username;
private String password;
private String driverClassName;
private int initialSize;
private int minIdle;
private int maxActive;
private int maxWait;
private int timeBetweenEvictionRunsMillis;
private int minEvictableIdleTimeMillis;
private String validationQuery;
private boolean testWhileIdle;
private boolean testOnBorrow;
private boolean testOnReturn;
private boolean poolPreparedStatements;
private int maxPoolPreparedStatementPerConnectionSize;
private String filters;
private String connectionProperties;
}
}
MybatisPlusConfig
/**
* @author wyk on 2019/02/28
*/
@Configuration
@Slf4j
public class MybatisPlusConfig {
//@Qualifier(value = "demoDataSource")
@Autowired
DataSource dataSource;
@Value("${mybatis-plus.global-config.db-config.capital-mode}")
private boolean capitalMode;
@Value("${mybatis-plus.mapper-locations}")
private String mapperLocations;
@Value("${mybatis-plus.type-aliases-package}")
private String typeAliasesPackage;
@Value("${mybatis-plus.global-config.db-config.logic-delete-value}")
private String delete;
@Value("${mybatis-plus.global-config.db-config.logic-not-delete-value}")
private String notDelete;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory createSqlSessionFactoryBean() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
sqlSessionFactoryBean.setPlugins(new PaginationInterceptor[]{this.paginationInterceptor()});
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
sqlSessionFactoryBean.setConfiguration(configuration);
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setDbConfig(this.globalConfiguration());
globalConfig.setSqlInjector(new LogicSqlInjector());
sqlSessionFactoryBean.setGlobalConfig(globalConfig);
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
/**/
performanceInterceptor.setMaxTime(1000);
/**/
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
/**
* mybatis-plus分页插件
*/
private PaginationInterceptor paginationInterceptor() {
PaginationInterceptor page = new PaginationInterceptor();
page.setDialectType("oracle");
return page;
}
private GlobalConfig.DbConfig globalConfiguration() {
GlobalConfig.DbConfig configuration = new GlobalConfig.DbConfig();
//主键策略
configuration.setIdType(IdType.INPUT);
//字段策略
configuration.setFieldStrategy(FieldStrategy.NOT_EMPTY);
//数据库大写 下划线转换
configuration.setCapitalMode(capitalMode);
configuration.setLogicDeleteValue(delete);
configuration.setLogicNotDeleteValue(notDelete);
configuration.setKeyGenerator(new OracleKeyGenerator());
return configuration;
}
}
三、搭建springSecurity权限拦截
SecurityConfig
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//@Autowired
//UserDetailService userDetailService;
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.httpBasic()// httpBasic 登录
http.formLogin()// 表单登录 来身份认证
.loginPage("/login") //
.failureUrl("/login?error")
.permitAll()// 自定义登录页面
.and()
.authorizeRequests()// 对请求授权
.antMatchers(
"/login/*","/test/*").permitAll()// 这些页面不需要身份认证,其他请求需要认证
.antMatchers("/admin/*").hasAuthority(TYPE_ADMIN)
.antMatchers("/normal/*").hasAnyAuthority(TYPE_ADMIN,TYPE_NORMAL)
.anyRequest() // 任何请求
.authenticated()//; // 都需要身份认证
.and()
.csrf().disable()// 禁用跨站攻击
.logout().logoutUrl("/logout").permitAll();
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
String jwtToken = request.getHeader("jwtToken");
Assert.notNull(jwtToken,CodeDefault.LOGIN);
});
// 关闭跨域拦截
http.csrf().disable();
// 关闭不允许在frame中访问的拦截
http.headers().frameOptions().disable();
// 单用户最大登陆数
http.sessionManagement().maximumSessions(1);
TokenLoginFilter tokenLoginFilter = new TokenLoginFilter();
Map tokenLoginStrategys = this.getApplicationContext()
.getBeansOfType(TokenLoginStrategy.class);
ArrayList tokenLoginStrategysList = new ArrayList<>(tokenLoginStrategys.values());
tokenLoginFilter.setTokenLoginStrategys(tokenLoginStrategysList);
http.addFilterBefore(tokenLoginFilter, UsernamePasswordAuthenticationFilter.class);
}
}
四、增加swagger
/**
* @author wyk on 2019/02/28
*/
@Configuration
@EnableSwagger2
@Profile({"dev","sit"})//生产和预发布环境不开启
public class SwaggerConfig {
// @Value("${spring.profiles.active}")
// private String active;
@Bean
public Docket createRestApi() {
//生产和预发布环境不开启
// if (Constant.ACTIVE_PROD.equals(active) || Constant.ACTIVE_PROP.equals(active)) {
// return null;
// }
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 指定 package,避免展现 spring-boot-starter-actuator 引入的 log-file-mvc-endpoint、health-mvc-endpoint 等
.apis(RequestHandlerSelectors.basePackage("com.psbc.wyk.eirs"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("SpringBoot 示例框架")
.description("SpringBoot 示例框架")
.contact(new Contact("文宇坤", "yk.wen", "[email protected]"))
.version("1.0").build();
}
}
五、完成
一个基本的框架就完成了,读者可以从github上下载我搭建的一个demo:
https://github.com/YukunWen/dangjian
后续工作只需要往里面添加业务代码即可。
参考网址:
- jwtToken官网:https://jwt.io/
请翻墙后访问,国内和国外看到的界面不完全一致 - springSecurity:http://blog.didispace.com/springbootsecurity/
- mybatis-plus:https://mp.baomidou.com/
- swagger:https://swagger.io/tools/swagger-ui/