1.安全控制Spring Security
1.1 了解Spring Security
1.1.1 什么是Spring Security
Spring Security是专门针对基于Spring的项目的安全框架。
主要有两个概念:认证和授权。认证即确认用户可以访问当前系统,授权即确认用户在当前系统下所拥有的功能权限
1.1.2 Spring Security的配置
开启Spring Security过滤器的支持,只需要extends AbstractSecurityWebApplicationInitializer开启过滤器的支持
配置:在配置类上注解@EnableWebSecurity,并让这个类继承WebSecurityConfigurerAdapter。通过写configure方法来配置相关的安全配置
1.1.3 用户认证
认证需要我们有一套用户数据的来源,而授权则是对于某个用户有相应的角色权限。通过重写configure方法实现定制。
1.1.3.1 内存中的用户
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("xp").password("xp").roles("ROLE_ADMIN")
.and()
.withUser("yxx").password("yxx").roles("ROLE_USER");
}
1.1.3.2 jdbc中的用户
@Autowired
javax.sql.DataSource dataSource;
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
此处的Spring Security是默认的数据库结构的。还可以自动以我们的查询用户和权限的SQL
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select username,password from myusers where username = ?")
.authoritiesByUsernameQuery("select username,role from roles where username = ? ");
}
1.1.3.3 通用的用户
自定义实现UserDetailsService接口
public class CustomUserService implements UserDetailsService {
@Autowired
SysUserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
SysUser user = userRepository.findByUserName(username);
ArrayList<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(user.getUsername(),user.getPassword(),authorities);
}
}
@Autowired
UserDetailsService customUserService(){
return new CustomUserService();
}
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(customUserService());
}
1.1.3.4 请求授权
Spring Security通过重写实现请求拦截。
protected void configure(HttpSecurity http) throws Exception {}
Spring Security通过以下匹配器来匹配请求路径:
antMatchers:Ant风格的路径匹配
regexMatchers:正则表达式匹配路径
anyRequest:匹配所有请求路径
匹配完路径之后,需要对当前用户的信息进行安全处理,处理方法如下:
access(String):EL表达式为true可以访问
anonymous():匿名可以访问
denyAll():用户不能访问
fullyAuthenticated():用户完全认证可访问(非remember me下自动登录)
hasAnyAuthority(String ...):如果用户有参数,则起着任意权限可以访问
hasAuthority(String):有参数,则可以访问
hasAnyRole(String ...):如果用户有参数,则起着任意角色可以访问
hasRole(String):如果用户有参数,则可以访问
hasIpAddress(String):如果用户来自参数中的IP可以访问
permitAll():所有人可以访问
rememberMe():允许通过remember-me登录的用户访问
authenticated():用户登录后可访问
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()//开始请求权限配置
.antMatchers("/admin/**").hasRole("ROLE_ADMIN")///admin/**路径下拥有ROLE_ADMIN角色的用户可以访问
.antMatchers("/user/**").hasAnyAuthority("ROLE_ADMIN","ROLE_USER")///user/**路径下,拥有ROLE_ADMIN或ROLE_USER都可以访问
.anyRequest().authenticated();//其余所有请求都需要登录后才能访问
}
1.1.3.5 定制登录行为
重写configure来定制登录行为
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()//通过formLogin定制登录操作
.loginPage("/login")
.defaultSuccessUrl(".index")
.failureUrl("/login?error")
.permitAll()
.and()
.rememberMe()//cookie存储用户信息
.tokenValiditySeconds(1209600)//cookie有效时间
.key("myKey")//cookie中的私钥
.and()
.logout()
.logoutUrl("/custom-logout")
.logoutSuccessUrl("/logout-success")
.permitAll();
}
1.2 Spring Boot的支持
主要通过SecurityAutoConfiguration和SecurityProperties来完成配置
当我们需要自己拓展时,只需要配置类继承WebSecurityConfigurerAdapter类即可,无需使用@EnableWebSecurity
1.3 实战
用Spring Boot下的Spring Security的配置,完成简单的认证授权的功能。通过Spring Data Jpa获取用户数据,页面模板用Thymeleaf(我的这个例子没有跑通,选择正确的账户也没能进,不知道为什么)
package com.xupeng.xupeng20191202V3.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class SysRole {
@Id
@GeneratedValue
private Long id;
//角色名称
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.xupeng.xupeng20191202V3.domain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javassist.SerialVersionUID;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
//实现了UserDetails后,我们的用户实体即为SpringSecurity所使用的的用户
public class SysUser implements UserDetails {
private static final long SerialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
//配置用户和角色多对多的关系
@ManyToMany(cascade={CascadeType.REFRESH},fetch=FetchType.EAGER)
private List<SysRole> roles;
@Override
//重写该方法,将用户的角色作为权限
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
List<SysRole> roles = this.getRoles();
for(SysRole role : roles){
auths.add(new SimpleGrantedAuthority(role.getName()));
}
return auths;
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
public static long getSerialversionuid() {
return SerialVersionUID;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.xupeng.xupeng20191202V3.domain;
public class Msg {
private String title;
private String content;
private String etraInfo;
public Msg(String title,String content,String etraInfo) {
super();
this.title = title;
this.content = content;
this.etraInfo = etraInfo;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getEtraInfo() {
return etraInfo;
}
public void setEtraInfo(String etraInfo) {
this.etraInfo = etraInfo;
}
}
package com.xupeng.xupeng20191202V3.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.xupeng.xupeng20191202V3.domain.SysUser;
public interface SysUserRepository extends JpaRepository<SysUser, Long> {
SysUser findByUsername(String username);
}
package com.xupeng.xupeng20191202V3.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.xupeng.xupeng20191202V3.dao.SysUserRepository;
import com.xupeng.xupeng20191202V3.domain.SysUser;
//自定义需实现UserDetailsService接口
public class CustomUserService implements UserDetailsService {
@Autowired
SysUserRepository userRepository;
//重写loadUserByUsername方法获得用户
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
SysUser user = userRepository.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("用户名不存在");
}
return user;//当前用户实现了UserDetails接口,可直接返回给Spring Security
}
}
package com.xupeng.xupeng20191202V3.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@SuppressWarnings("deprecation")
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
package com.xupeng.xupeng20191202V3.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.xupeng.xupeng20191202V3.service.CustomUserService;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
UserDetailsService customUserService(){
return new CustomUserService();
}
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(customUserService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.permitAll()
.and()
.logout().permitAll();
}
}
package com.xupeng.xupeng20191202V3.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.xupeng.xupeng20191202V3.domain.Msg;
@Controller
public class HomeController {
@RequestMapping("/")
public String index(Model model){
Msg msg = new Msg("测试标题", "测试内容", "额外信息,只对管理员显示");
model.addAttribute("msg",msg);
return "home";
}
}
package com.xupeng.xupeng20191202V3;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Xupeng20191202V3Application {
public static void main(String[] args) {
SpringApplication.run(Xupeng20191202V3Application.class, args);
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"><!-- SpringSecurity的标签支持 -->
<head>
<meta charset="UTF-8" />
<title sec:authentication="name"></title><!-- 获取当前用户的用户名 -->
<link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet"/>
<link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
<style type="text/css">
body{
padding-top: 50px;
}
.starter-template{
padding:40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}">首页</a></li>
</ul>
</div>
</nav>
<div class="container">
<div class="starter-template">
<h1 th:text="${msg.title}"></h1>
<p class="bg-primary" th:text="${msg.content}"></p>
<div sec:authorize="hasRole('ROLE_ADMIN')"><!-- 管理员显示这个 -->
<p class="bg-info" th:text="${msg.etraInfo}"></p>
</div>
<div sec:authorize="hasRole(ROLE_USER)"><!-- 普通用户显示这个 -->
<p class="bg-info">无更多信息显示</p>
</div>
<form th:action="@{/logout}" method="post">
<input type="submit" class="btn btn-primary" value="注销" /><!-- 注销路径 -->
</form>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>登录页面</title>
<link th:href="@{bootstrap/css/bootstrap.min.css}" rel="stylesheet" />
<link th:href="@{bootstrap/css/bootstrap-theme.min.css}" rel="stylesheet"/>
<style type="text/css">
body{
padding-top: 50px;
}
.starter-template{
padding:40px 15px;
text-align: center;
}
</style>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Spring Security演示</a>
</div>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/}">首页</a></li>
</ul>
</div>
</nav>
<div class="container">
<div class="starter-template">
<p th:if="${param.logout}" class="bg-warning">已成功注销</p>
<p th:if="${param.error}" class="bg-danger">${"param.error"}有错误,请重试</p>
<h2>使用账号密码登录</h2>
<form name="form" th:action="@{/login}" method="POST">
<div class="form-group">
<label for="username">账号</label>
<input type="text" class="form-control" name="username" value="" placeholder="账号" />
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" name="password" placeholder="密码" />
</div>
<input type="submit" id="login" value="login" class="btn btn-primary" />
</form>
</div>
</div>
</body>
</html>
2.批处理
2.1.1 批处理Spring Batch
Spring Batch是用来处理大量数据操作的一个框架,主要用来读取大量的数据,然后进行一定处理后输出成指定的形式
2.1.2 Spring Batch主要组成
JobRepository :用来注册Job容器
JobLauncher :用来启动Job的接口
Job :我们要实际执行的任务,包含一个或多个Step
Step :Step步骤包含ItemReader、ItemProcessor、ItemWriter
ItemReader :读取数据的接口
ItemProcessor :处理数据的接口
ItemWriter :输出数据的接口
上述主要组成部分只需要注册成Spring的Bean即可。若想开启批处理的支持还需要在配置类上使用@EnableBatchProcessing
package com.xupeng.xupeng20191203.config;
import javax.sql.DataSource;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import com.xupeng.xupeng20191203.domain.Person;
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Bean
public JobRepository jobRepository(DataSource dataSource,PlatformTransactionManager transactionManager) throws Exception{
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
jobRepositoryFactoryBean.setDataSource(dataSource);
jobRepositoryFactoryBean.setTransactionManager(transactionManager);
jobRepositoryFactoryBean.setDatabaseType("oracle");
return jobRepositoryFactoryBean.getObject();
}
@Bean
public SimpleJobLauncher jobLauncher(DataSource dataSource,PlatformTransactionManager transactionManager) throws Exception{
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));
return jobLauncher;
}
@Bean
public org.springframework.batch.core.Job impJob(JobBuilderFactory jobs,Step s1){
return jobs.get("importJob")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory,ItemReader<Person> reader,ItemWriter<Person> writer,ItemProcessor<Person, Person> processor){
return stepBuilderFactory
.get("step1")
.<Person,Person>chunk(65000)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public ItemReader<Person> reader(){
return reader;
}
@Bean
public ItemProcessor<Person, Person> processor(){
return processor;
}
@Bean
public ItemWriter<Person> writer(DataSource dataSource){
return writer;
}
}
2.1.3 Job监听
若需要监听我们的Job的执行情况,则定义一个类实现JobExecutionListener,并在定义Job的Bean上绑定该监听器
package com.xupeng.xupeng20191203.listener;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
public class MyJobListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
// TODO Auto-generated method stub
//job开始前
}
@Override
public void afterJob(JobExecution jobExecution) {
// TODO Auto-generated method stub
//job开始后
}
}
@Bean
public MyJobListener myJobListener(){
return new MyJobListener();
}
2.1.4 数据读取
Spring Batch为我们提供了大量的ItemReader的实现,用来读取不同的数据来源
2.1.5 数据处理及校验
数据处理和校验都要通过ItemProcess接口实现来完成
2.1.5.1 数据处理
数据处理只需要实现ItemProcess接口,重写其process方法。方法传入的参数是从ItemReader读取到的数据,返回的数据给ItemWriter
package com.xupeng.xupeng20191203.process;
import org.springframework.batch.item.ItemProcessor;
import com.xupeng.xupeng20191203.domain.Person;
public class MyItemProcess implements ItemProcessor<Person, Person> {
@Override
public Person process(Person person) throws Exception {
String name = person.getName().toUpperCase();
person.setName(name);
return person;
}
}
2.1.5.2 数据校验
我们以JSR-303的注解来校验ItemReader读取到的数据是否满足要求
首先实现ValidatingItemProcessor接口
package com.xupeng.xupeng20191203.process;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.batch.item.validator.ValidationException;
import com.xupeng.xupeng20191203.domain.Person;
public class MyItemProcess extends ValidatingItemProcessor<Person> {
@Override
public Person process(Person person) throws ValidationException {
super.process(person);
return person;
}
}
定义校验器,实现的Validator接口来于Spring,使用JSR-303的Validator来校验
package com.xupeng.xupeng20191203.process;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
import org.springframework.beans.factory.InitializingBean;
public class MyBeanValidator<T> implements Validator<T>,InitializingBean {
private javax.validation.Validator validator;
@Override
public void afterPropertiesSet() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
@Override
public void validate(T value) throws ValidationException {
Set<ConstraintViolation<T>> constraintViolations = validator.validate(value);
if(constraintViolations.size()>0){
StringBuilder message = new StringBuilder();
for(ConstraintViolation<T> constraintViolation:constraintViolations){
message.append(constraintViolation.getMessage()+"\n");
}
throw new ValidationException(message.toString());
}
}
}
在定义我们的MyItemProcessor时必须将MyBeanValidator设置进去
@Bean
public ItemProcessor<Person, Person> processor(){
MyItemProcess process = new MyItemProcess();
process.setValidator(myBeanValidator());
return process;
}
public Validator<Person> myBeanValidator() {
return new MyBeanValidator<Person>();
}
2.1.6 数据输出
Spring Batch为我们提供了大量的ItemWriter的实现,用来将数据输出到不同的目的地
2.1.7 计划任务
Spring Batch的任务是通过JobLauncher的run方法来执行的,因此只需要在普通的计划任务方法中执行JobLauncher的run方法即可。不要忘记@EnableScheduling开启计划任务的支持
package com.xupeng.xupeng20191203.service;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@Service
public class ScheduledTaskService {
@Autowired
JobLauncher jobLauncher;
@Autowired
Job impJob;
public JobParameters jobParameters;
@Scheduled(fixedRate=5000)
public void execute() throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException{
jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters();
jobLauncher.run(impJob, jobParameters);
}
}
2.1.8 参数后置设置
我们在ItemReader和ItemWriter的Bean在定义的时候,参数已经硬编码在Bean的初始化中
@Bean
public ItemReader<Person> reader(){
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
reader.setResource(new ClassPathResource("people.csv"));
return reader;
}
若想实现参数后置绑定,我们可以在JobParameters中绑定参数,在Bean定义的时候用一个特殊的Bean生命周期注解@StepScope,然后通过@Value注入此参数
@Scheduled(fixedRate=5000)
public void execute() throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException{
String path = "people.csv";
jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis())
.addString("input.file.name", path)
.toJobParameters();
jobLauncher.run(impJob, jobParameters);
}
@Bean
@StepScope
public ItemReader<Person> reader(@Value("#{jobParameters['input.file.name']}") String pathToFile){
FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
reader.setResource(new ClassPathResource(pathToFile));
return reader;
}
2.2 Spring Boot的支持
2.2.1 Spring Boot为我们自动初始化了Spring Batch存储批处理记录的数据库,且当我们程序启动时,会自动执行我们定义的Job的Bean
2.2.2 Spring Boot提供的对Spring Batch的属性
spring.batch.job.names=job1,job2 #启动时要执行的job,默认执行全部
spring.batch.job.enabled=true #是否自动执行定义的Job,默认是
spring.batch.initializer.enabled=true #是否初始化Spring Batch的数据库,默认是
spring.batch.schema=classpath:org/springframework/batch/core/schema-@@platform@@.sql #指定SQL脚本的路径
spring.batch.table-prefix=BATCH_ #设置数据库表的前缀,默认是BATCH_ 开头。