SpringBoot企业级开发(SpringSecurity安全控制+pringBatch批处理+异步消息+系统集成SpringIntegration)

Spring Security

多个过滤器来实现所有安全的功能,只需要注册一个特殊的DelegatingFilterProxy过滤器到WebAppliationInitializer即可

实际使用中需要让自己的Initializer类继承AbstractSecurity WebApplicationInitializer抽象类即可。

AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,并通过onStartup方法调用

①、依赖

spring-boot-starter-data-jpa

spring-boot-starter-security

spring-boot-starter-thymeleaf

ojdbc6

thymeleaf-extras-springsecurity4

②、配置

bash 复制代码
spring.datasource.drivuerClassName=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc\:oracle\:thin\:@localhost\:1521\:xe
spring.datasource.username=boot
spring.datasource.password=boot

logging.level.org.springframework.security=INFO
spring.thymeleaf.cache=false

spring.jpa.hibernate.ddl-auto=update #自动生成用户表、角色表以及关联表
spring.jpa.show-sql=true

将bootstrap.min.css放置在src/main/resources/static/css下,此路径默认不拦截

③、用户和角色

java 复制代码
@Entity
public class SysUser implements UserDetails{//实现该接口,用户实体即为Spring Security使用的用户
	
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue
	private Long id;
	
	private String username;
	private String password;
	
	
	@ManyToManay(cascade = {CascadeType.REFRESH},fetch=FetchType.EAGER)
	private List<SysRole> roles;

	@Override
	public Colletion<? extends GrantedAuthority> getAuthorities(){//将用户的角色作为权限
		
		List<GrantedAuthority> auths = new ArrayList<>();
		List<SysRole> roles = this.getRoles();
		for(SysRole role:roles){
			auths.add(new SimpleGrantedAuthority(role.getName()));
		}
		return false;
	}

	@Override
	public boolean isAccountNonExpired(){
		return true;
	}

	@Override
	public boolean isAccountNonLocked(){
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired(){
		return true;
	}

	@Override
	public boolean isEnabled(){
		return true;
	}

	//省略get\set方法
}
java 复制代码
@Entity
public class SysRole{
	
	@Id
	@GeneratedValue
	private Long id;

	private String name;

	//省略getter、setter方法
}

④、Dao数据访问

java 复制代码
public interface SysUserRepository extends JpaRepository<SysUser,Long>{
	
	SysUser findByUsername(String username);
}

⑤、自定义Service服务

java 复制代码
public class CustomUserService implements UserDetailsService{//自定义需要实现该接口
	
	@Autowired
	SysUserRepository userRepository;

	@Override
	public UserDetails loadUserByUsername(){//获得用户,直接返回给SprngSecurity
		SysUser user = userRepository.findUsername(username);
		if(user == null){
			throw new UsernameNotFoundException("用户名不存在");
		}
		return user;
	}
}

⑥、Configuration配置SpringMVC

注册访问/login转向login.html

java 复制代码
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{
	
	//访问http://localhost:8080 将会自动跳转到登录页面
	@Override
	public void addViewController(ViewControllerRegistry registry){
		registry.addViewController("/login").setViewName("login");
	}
}

扩展Spring Security配置,所有请求需要认证登录后才能方法(登录和注销)

SpringBoot针对Spring Security的自动配置通过SecurityAutoConfiguration和SecurityProperties来配置

SpringBoot做了很多配置,需要扩展配置只需要继承WebSecurityConfigurerAdapter类,无需@EnableWebSecurity注解

java 复制代码
//相关的安全配置
@Configuration
//@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	
	@Bean
	UserDetailsService customUserService(){//注册该bean
		return new CustomUserService();
	}

	/**
		用户认证
		①、添加内存中的用户,并且可以给用户指定角色权限
		auth.inMemoryAuthentication().withUser("wyf").password("wyf")
			.roles("ROLE_ADMIN").and().withUser("wisely").password("wisely").roles("ROLE_USER");

		②、JDBC中的用户直接指定dataSource即可
		这里的SpringSecurity默认了数据库接口,通过jdbcAuthentication源码可以得出JdbcDaoImpl中定义了默认的用户及角色权限
		
		@Autowired
		DataSource dataSource;
		@Override
		protected void configure(AuthenticationManagerBuilder auth)throws Exception{
			auth.jdbcAuthentication().dataource(dataSource);
			//也可以i定义查询用户和权限的SQL
			auth.jdbcAuthentication().dataource(dataSource)
				.usersByUsernameQuery("select username,password,true from myusers where username=?")
				.authoritiesByUsername("select username,role from roles where username=?");
		}
		
	*/
	//③、通用用户,实现UerDetailsService接口,以上JDBC用户和内存用户就是其实现
	@Override
	protected void configure(AuthenticationManagerBuilder auth)throws Exception{
		auth.userDetailsService(customUserServie());//添加自定义认证,注册
	}

	/**
		请求授权,匹配了一下请求路径,需要针对当前用户的信息对请求路径进行安全处理
		Ⅰ、antMatchers 使用Ant风格的路径皮喷
		Ⅱ、regexMatchers 使用正则表达式匹配路径
		Ⅲ、anyRequest 匹配所有路径

		安全处理方法:
		access(String) SpringEL表达式结果为true时可访问
		anonymous() 匿名访问
		denyAll() 用户不能访问
		fullyAuthenticated() 用户完全认证可访问(非remeber me下自动登录)
		hasAnyAuthority(String..) 如果用户有参数,则其中任一权限可访问
		hasAnyRole(String..) 如果用户有参数,则其中任一角色可访问
		hasAuthority(String) 如果用户有参数,则其权限可访问
		hasIpAddress(String) 如果用户来自参数中的IP则可访问
		hasRole(String) 若用户有参数中的角色可访问
		permitAll() 用户可任意访问
		rememberMe() 运行通过remember-me登录的用户访问
		authenticated() 用户登录后可访问

		http.authorizeRequests()
			.antMatchers("/admin/**").hasRole("ROLE_ADMIN")
			.antMatchers("/user/**").hasAnyRole("ROLE_ADMIN","ROLE_USER")
			.anyRequst().authenticated(); //其余所有请求都需要认证登录后才可以访问
	*/
	@Override
	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests()//开始请求权限配置
			.anyRequest().authenticated()//所有请求需要认证登陆后才能访问
			.and()
			.formLogin() //定制登录操作
			.loginPage("/login")//登录页面的访问地址
			.defaultSuccessUrl("/index")//指定登录成功后转向的页面
			.failureUrl("/login?error")//登录失败转向的页面
			.permitAll()//定制登录行为,登录页面可以任意访问
			.and()
			.rememberMe()//开启cookie存储用户信息
				.tokenValiditySeconds(1209600)//指定cookie有效期2个星期
				.key("myKey")//指定cookie中的私钥
			.and()
			.logout()
			.logout("/costom-logout")//指定注销的URL路径
			.logoutSuccessUrl("/logout-success")//指定注销成功后转向的页面
			.permitAll();//定制注销行为,注销请求可任意访问
	}
}

⑦、页面

html 复制代码
<!--Thymeleaf提供了Spring Security的标签支持-->
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">

获得当前用户名

sec:authentication="name"

当前用户觉得为ROLE_ADMIN时才现实标签内容

sec:authorize="hasRole('ROLE_ADMIN')"

注销的默认路径/logout,需要通过POST请求提交

th:action="@{/logout}" method="post"

Spring Batch

处理大量数据的框架,用来读取大量数据,然后进行一定处理后输出成指定的形式

①、依赖

spring-boot-starter-jdbc

spring-boot-starter-batch(因为使用Oracle,所以排除hsqldb)

spring-boot-starter-web

ojdbc6

hibernate-validator 数据校验

数据准备:

src/main/resources/people.csv中添加 jordan,23,非汉族,芝加哥 等多条类似的数据

数据表sql

id name age nation address

②、实体类

java 复制代码
public class Person{
	
	@Size(max=4,min=2) //使用JSR-303校验数据
	private String name;

	private int age;

	private String nation;

	private String address;
	
	//省略get和set方法
}

③、数据处理及校验

java 复制代码
public class CsvIntemProcessor extemds ValidatingItemProcessor<Person>{
	
	@Override
	public Person process(Person item) throws ValidationException{
		super.process(item);//调用自定义校验器
		if(item.getNation().equals("汉族")){
			item.setNation("01");
		}else{
			item.setNation("02");
		}
		return item;
	}
}
java 复制代码
public class CsvBeanValidator<T> implements Validator<T>,InitializingBean{
	
	private javax.validation.Validator validator;

	@Override
	public void afterProperitesSet()throws Exception{//使用JSR-303校验数据
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
		validator = validatorFactory.usingContext().getValidator();
	}

	@Override
	public void validate(T value)throws ValidationException{
		//使用Validator的validate的方法校验数据
		Set<ConstraintViolation<T>> constraintViolations = validator.validate(value);
		if(constraitViolations.size()>0){
			StringBuilder message = new StringBuilder();
			for(ConstraintVilation<T> constraintViolation : constraintViolations){
				message.append(constraintViolation.getMessage() + "\n");
			}
			throw new ValidationException(message.toString());
		}
	}
}

④、Job监听

java 复制代码
public class CsvJobListener implements JobExecutionListener{
	
	long startTime;
	long endTime;

	@Override
	public void beforeJob(JobExecution jobExecution){
		startTime = System.currentTimeMillis();
		System.out.println("任务处理开始");
	}

	@Override
	public void afterJod(JobExecution jobExecution){
		startTime = System.currentTimeMillis();
		System.out.println("任务处理结束");
		System.out.println("耗时:"+(endTime-startTime)+"ms");
	}
}

⑤、配置

  • JobRepository 用来注册Job容器
  • JobLauncher 用来启动Job的接口
  • Job 实际执行的任务,包含一个或多个Step
  • Step 包含ItemReader ItemProcessor和ItemWriter
  • ItemReader 用来读取数据的接口
  • ItemProcessor 用来处理数据的接口
  • ItemWriter 用来输出数据的接口
java 复制代码
@Configuration
@EnableBatchProcessing //开启批处理支持
public class CsvBatchConfig{
	
	@Bean
	public ItemReader<Person> reader()throws Exception{
		
		FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//读取文件
		
		reader.setResource(new ClassPathResource("people.csv"));//设置csv文件路径
		reader.setLineMapper(new DefautLineMapper<Person>(){
			{
				setLineTokenizer(new DelimitedLineTokenizer(){
					{
						setNames(new String[]{"name","age","nation","address"});
					}
				});

				setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
					{
						setTargetType(Person.class);
					}
				});
			}
		});
		
		return reader;
	}

	@Bean
	public ItemProcessor<Person,Person> processor(){
		CsvItemProcessor processor = new CsvItemProcessor();//使用自定义的ItemProcessor的实现CsvItemProcessor
		processor.setValidator(csvBeanValidator());//为processor指定校验器为CsvBeanValidator
		return processor;
	}

	@Bean
	public ItemWriter<Person> write(DataSource dataSource){//SpringBoot让已有的容器bean,注入
		
		JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<>();//使用JDBC批处理的jdbcBatchItemWriter来写数据到数据库
		writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
		String sql = "insert into person"+"(id,name,age,nation,address)"+"values(hibernate_sequence.nextval,:name,:age,:nation,:address)";
		
		wirter.setSql(sql);//设置要执行批处理的SQL语句
		writer.setDataSource(dataSource);
		return writer;
	}

	@Bean
	public JobRepository jobRepository(DataSource dataSource,PlatformTransactionManager transactionManager)throws Exception{
		
		JobRepositoryFactoryBean jobRepositoryFactoryBean = new JopRepositoryFactoryBean();
		jobRepositoryFactoryBean.setDataSource(dataSource);
		jobRepositoryFactoryBean.setTransactionManager(transactionManager);
		jobRepositoryFactoryBean.setDatabaseType("oracle");
		return jobRepositoryFactoryBean.getObject();
	}

	@Bean
	public SimpleJobLauncher jobLauncher(DataSource dataSource,PlatformTransactionManager transactionManager){
		
		SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
		jobLauncher.setJobRepository(jobRepository(dataSource,transactionManager));
		return jobLauncher;
	}

	@Bean
	public Job importJob(JobBuilderFactory jobs,Step s1){
		
		return jobs.get("importJob")
			.incrementer(new RunIdIncrementer())
			.flow(s1)//为Job指定Step
			.end()
			.listener(csvJobListener())//绑定监听器
			.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)//批处理,每次提交65000条数据
			.reader(reader)//为step绑定那个reader
			.processor(processor)//给step绑定processor
			.writer(writer)//给step绑定writer
			.build();
	}

	@Bean
	public CsvJobListener csvJobListener(){
		
		return new CsvJobListener();
	}
	
	@Bean
	public Validator<Person> csvBeanValidator(){
		return new CsvBeanValidator<Person>();
	}
}

⑥、运行

SpringBoot会自动初始化SpringBatch数据库,并将CSV中的数据导入到数据库中

监听器效果

数据已导入且做转换处理

⑦、手动触发任务

注释调CsvBatchConfig类的@Configuration注解,让此类不在起效

新建TriggerBatchConfig配置类,内容同CsvBatchConfig完全保持一致

修改定义ItemReader

java 复制代码
@Bean
@StepScope
public FlatFileItemReader<Person> reader(@Value("#{jobParameters['input.file.name']}") String pathToFile)throws Exception{
	
	FlatFileItemReader<Person> reader = new FlatFileItemReader<>();//
	reader.setResource(new ClassPathResource(pathToFile));//
	reader.setLineMapper(new DefautLineMapper<Person>(){
			{
				setLineTokenizer(new DelimitedLineTokenizer(){
					{
						setNames(new String[]{"name","age","nation","address"});
					}
				});

				setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>(){
					{
						setTargetType(Person.class);
					}
				});
			}
		});
		
		return reader;
}

控制定义

java 复制代码
@RestController
public class DemoController{
	
	@Autowired
	JobLauncher jobLauncher;

	@Autowired
	Job importJob;
	
	public JobParameters jobParameters;

	@RequestMapping("/imp")
	public String imp(String fileName)throws Exception{
		String path = fileName + ".csv";
		jobParameters = new JobParametersBuilder()
			.addLong("time",System.currentTimeMillis())
			.addString("input.file.name",path)
			.toJobParameters();

		jobLauncher.run(importJob,jobParameters);
		return "ok";
	}
 
}

此时如果关闭SpringBoot为我们自动执行了Job的配置,在application.properties里使用如下代码

spring.batch.job.enabled=false

访问http://localhost:8080/imp?fileName=people可获得相同的数据导入效果

异步消息

Spring通过@EnableJms @EnableRabbit开启支持

SpringBoot自动开启了@EnableJms @EnableRabbit

JMS

①、安装ActiveMQ

docker run -d -p 61616:61616 -p 8161:8161 cloudesire/activemq

其中61616是消息代理的端口

8161是ActiveMQ管理界面端口 http://localhost:8161打开ActiveMQ的管理界面 账号密码admin/admin

也可以将ActiveMQ内嵌在程序里依赖activemq-broker

②、依赖

spring-boot-starter

spring-jms

activemq-client

application.properties配置ActiveMQ消息代理

spring.activemq.broker-url=tcp://localhost:61616

③、消息定义

java 复制代码
public class Msg implements MessageCreator{
	
	@Override
	public Message createMessage(Session session) throws JMSException{
		return session.createTextMessage("测试消息");
	}
}

④、消息发送及目的地

java 复制代码
@SpringBootApplication
public class ChApplication implements CommandLineRunner{//SpringBoot提供的接口
	
	@Autowired
	JmsTemplate jmsTemplate;//注入SpringBoot配置好的bean

	public static void main(String[] args){
		SpringApplication.run(ChApplication.class,args);
	}

	@Override
	public void run()throws Exception{
		jmsTemplate.send("my-destination",new Msg());//向my-destination目的地发送Msg的消息
	}
}

⑤、消息监听

java 复制代码
@Component
public class Receiver{
	
	@JmsListener(destination="my-destination")//Spring4.1新特性,监听的目的地,接收消息
	public void receiveMesssage(String message){

		Systemm.out.println("接收到:<" + message +">");
	}
}

⑥、运行

AMQP

①、安装RabbitMQ

先下载安装erlang http://www.erlang.org/download.html

下载RabbitMQ https://www.rabbitmq.com/download.html

docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:3-management

5672属于消息代理的端口

15672属于管理界面的端口 http://localhost:15672 guest/guest

②、依赖

spring-boot-starter-amqp

SpringBoot默认Rabbit注解为localhost、端口号5672

无需为SpringBoot的application.properties配置RabbitMQ的连接信息

③、发送信息及目的地

java 复制代码
@SpringBootApplication
public class ChApplication implements CommandLineRunner{
	
	@Autowired
	RabbitTemplate rabbitTemplate;//注入SpringBoot配置好的

	public static void main(String[] args){
		SpringApplication.run(ChApplication.class,args);
	}

	@Bean
	public Queue wiselyQueue(){
		return new Queue("my-queue");//定义目的地队列
	}

	@Override
	public void run(String... args)throws Exception{
		//向队列my-queue发送消息
		rabbitTemplate.convertAndSend("my-queue","来自RabbitMQ的问候");
	}
	
}

④、消息监听

java 复制代码
@Component
public class Receiver{
	
	@RabbitListener(queues = "my-queue")
	public void receiveMessage(String message){
		System.out.println("Received<" + message + ">");
	}
}

⑤、运行

系统集成Spring Integration

解决不同系统之间交互的问题,通过异步消息驱动来达到系统交互时系统之间的松耦合。

①、读取https://spring.io/blog.atom的新闻聚合文件,atom是一种xml文件,且格式是固定的

将读取到的消息通过分类(Category),将消息转到不同的消息通道

将分类为releases和engineering的消息写入磁盘,将分类为news的消息通过邮件发送

②、依赖

spring-boot-starter-integration

spring-boot-starter-mail

Spring Integration对atom及mail的支持:

spring-integration-feed

spring-integration-mail

②、读取流程(入口类完成)

java 复制代码
@Value("https://spring.io/blog.atom")//获得资源
Resource resource

@Bean(name=PollerMetadata.DEFAULT_POLLER)
public PollerMetadata poller(){//配置默认的轮询方式
	return Pollers.fixedRate(500).get();
}

@Bean
public FeedEntryMessageSource feedMessageSource() throws IOException{//
	
	FeedEntryMessageSource messageSource = new FeedEntryMessageSource(resource.getURL(),"news");
	return messageSource;
}

@Bean
public IntegrationFlow myFlow() throws IOException{
	
	return IntegrationFlows.from(feedMessageSource())//流程从from 方法开始
		.<SyndEntry,String> route(payload ->payload.getCategories().get(0).getname(),//选择路由,消息体payload类型SyndEntry
									mapping ->mapping.channelMapping("releases","releasesChannel")
		.channelMapping("engineering","engineeringChannel")//不同分类的值转向不同的消息通道
		.channelMapping("news","newsChannel"))
		.get();//获得IntegrationFlow实体,配置为Spring的Bean
}

③、release流程

java 复制代码
@Bean
public IntegrationFlow releasesFlow(){
	
	return IntegrationFlows.from(MessageChannels.queue("releasesChannel",10)) //从消息通道releasesChannel开始获取数据
		.<SyndEntry,String> transform(payload->"《" + payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator"))//数据转换
		.handle(Files.outboundAdapter(new File("e:/springblog"))//file出站适配器
		.fileExistsMode(FileExistsMode.APPEND)
		.charset("UTF-8")
		.fileNameGenerator(message -> "releases.txt")
		.get())
		.get();
		
}

④、engineering流程

java 复制代码
@Bean
pubic IntegratonFlow engineeringFlow(){
	
	return IntegrationFlows.from(MessageChannels.queue("engineeringChannel",10))
		.<SyndEntry,String> transform(e->"《"+e.getTitle()+"》"+e.getLink()+getProperty("line.separator"))
		.handle(Files.outboundAdapter(new File("e:/springblog"))
		.fileExistsMode(FileExistsMode.APPEND).charset("UTF-8")
		.fileNameGenerator(message->"engineering.txt").get())
		.get();
}

⑤、news流程

java 复制代码
@Bean
public IntegrationFlow newsFlow(){
	
	return IntegrationFlows.from(MessageChannels.queue("newsChannel",10))
		.<SyndEntry,String> transform(payload->"《"+payload.getTitle()+"》"+payload.getLink()+getProperty("line.separator"))
		.enrichHeaders(Mail.headers() //增加消息头信息
						.subject("来自Spring的新闻")
						.to("wise-man@126.com")
						.from("wisely-man@126.com"))//邮件发送的相关信息
		.handle(Mail.outboundAdapter("smtp.126.com")//邮件发送的出站适配器
						.port(25)
						.protocol("smtp")
						.credentials("wisely-man@126.com","****")
						.javaMailProperties(p->p.put("mail.debug"),false)),
					e->e.id("smtpOut"))
		.get();
}

⑥、运行


release.txt文件内容

邮箱接收结果

相关推荐
手握风云-30 分钟前
数据结构(Java版)第一期:时间复杂度和空间复杂度
java·数据结构
坊钰33 分钟前
【Java 数据结构】时间和空间复杂度
java·开发语言·数据结构·学习·算法
飞升不如收破烂~39 分钟前
Redis的String类型和Java中的String类在底层数据结构上有一些异同点
java·数据结构·redis
苹果酱056743 分钟前
windows安装redis, 修改自启动的redis服务的密码
java·开发语言·spring boot·mysql·中间件
feilieren1 小时前
信创改造 - TongRDS 替换 Redis
java·spring boot·后端
Allen Bright1 小时前
Jedis连接池的操作
java·redis
hani19901 小时前
beikeshop 与swoole结合,让网站打开飞起
后端·swoole
庞传奇1 小时前
【LC】560. 和为 K 的子数组
java·算法·leetcode
knoci2 小时前
【Go】-go中的锁机制
后端·学习·golang
Mike_188702783512 小时前
深入探索Golang的GMP调度机制:源码解析与实现原理
开发语言·后端·golang