SpringBoot第七第八第九章第作业记录

第七章 自定义用户认证 - 内存身份认证

内存身份认证是一种自定义用户认证方式,指的是在程序运行时使用缓存或内存中的用户信息来进行身份认证。这种方式相对于传统的基于数据库的身份认证来说,具有以下几点优势:

1. 速度更快:由于用户信息存储在内存中,不需要每次都从数据库中查询,因此身份认证的速度更快。

2. 可以支持临时用户:有些应用场景下,可能需要支持没有在数据库中注册的临时用户,这时候内存身份认证就更加适合,只需要在内存中存储临时用户信息即可。

3. 更容易实现单点登录:内存身份认证可以更容易地实现单点登录,只需要将用户信息存储在一个中央缓存中并共享就可以了。

需要注意的是,内存身份认证也存在一些潜在的问题,比如缓存数据的安全性和可靠性可能不够高,需要进行有效的保护和备份。

package com.example.demo.Security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
* Qingke
* 2023/10/14
* 16:51
**/
@Controller
public class FilmeController {
private String TAG = "FilmeController";
//影片详情页
@GetMapping("/detail/{type}/{path}")
public String toDetail(@PathVariable("type") String type, @PathVariable("path") String path) {
String value = "detail/" + type + "/" + path;
System.out.println(TAG + "===toDetail===" + value);
return value;
}
}

package com.example.demo.Security;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
* Qingke
* 2023/10/15
* 16:57
**/
@EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密码需要设置编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//使用用户内存信息,作为测试使用
auth.inMemoryAuthentication().passwordEncoder(encoder)
.withUser("GRAB").password(encoder.encode("grab")).roles("common")
.and()
.withUser("管子").password(encoder.encode("tube")).roles("vip");
}
}

第七章 自定义用户认证 - JDBC身份认证

1. 自定义用户认证:指通过自定义实现的认证方式来验证用户身份,与系统默认的认证方式不同。

2. JDBC身份认证:指使用JDBC(Java Database Connectivity)技术,通过访问数据库中存储的用户信息进行身份认证。

3. 意义:使用自定义用户认证可以增强系统的安全性和灵活性,因为不同的企业或个人有不同的安全策略和认证机制需求,自定义用户认证可以满足这些需求。而使用JDBC身份认证可以通过连接数据库的方式实现更加安全和可靠的用户身份认证,减少潜在的安全风险。

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.sql.DataSource;

/**
* Qingke
* 2023/10/15
* 17:26
**/

@EnableWebSecurity//开启MVC security安全支持
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//密码需要设置编码器
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//使用JBDC进行身份认证
String userSQL = "select username,password,valid from t_customer " +
"where username = ?";
String authoritySQL = "select c.username,a.authority from t_customer c,t_authority a," +
"t_customer_authority ca where ca.customer_id=c.id " +
"and ca.authority_id=a.id and c.username =?";
auth.jdbcAuthentication().passwordEncoder(encoder)
.dataSource(dataSource)
.usersByUsernameQuery(userSQL)
.authoritiesByUsernameQuery(authoritySQL);
}
}

第七章 自定义用户认证 - UserDetailsService 身份认证

1. UserDetailsService是Spring Security框架中用于身份认证的接口,可以通过实现该接口来自定义用户认证逻辑。
2. UserDetailsService的主要作用是根据用户名获取用户信息,包括密码、角色、权限等,以供Spring Security进行身份认证和权限控制。
3. 通过自定义UserDetailsService,我们可以实现更灵活、更安全的用户认证逻辑,例如使用自己的加密算法、从其他数据源获取用户信息等。
4. UserDetailsService的实现需实现loadUserByUsername方法,其返回值需要实现UserDetails接口,包括用户基本信息、角色、权限等。
5. UserDetailsService是Spring Security实现认证和授权的一个重要组件,其良好的设计和实现能够提升系统的安全性和可靠性。

package com.example.demo.Security;

import javax.persistence.*;
import java.io.Serializable;

/**
* Qingke
* 2023/10/15
* 17:41
**/
@Entity(name = "t_authority")//设置ORM实体类,并指定映射的表名
public class Authority implements Serializable {
@Id //表明映射对应的主键id
@GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键自增策略
private Integer id;
private String authority;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getAuthority() {
return authority;
}

public void setAuthority(String authority) {
this.authority = authority;
}

@Override
public String toString() {
return "Authority{" +
"id=" + id +
", authority='" + authority + '\'' +
'}';
}
}

package com.example.demo.Security;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
* Qingke
* 2023/10/15
* 17:43
**/
public interface AuthorityRepository extends JpaRepository<Authority,Integer> {
@Query(value = "select a.* from t_customer c,t_authority a,t_customer_authority ca where ca.customer_id=c.id and ca.authority_id=a.id and c.username=?1", nativeQuery = true)
List<Authority> findAuthoritiesByUsername(String username);

}

package com.example.demo.Security;

import javax.persistence.*;
import java.io.Serializable;

/**
* Qingke
* 2023/10/15
* 17:39
**/
@Entity(name = "t_customer")//设置ORM实体类,并指定映射的表名
public class Customer implements Serializable {
@Id //表明映射对应的主键id
@GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键自增策略
private Integer id;
private String username;
private String password;
private byte valid;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public byte getValid() {
return valid;
}

public void setValid(byte valid) {
this.valid = valid;
}

@Override
public String toString() {
return "Customer{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", valid=" + valid +
'}';
}
}

package com.example.demo.Security;

import org.springframework.data.jpa.repository.JpaRepository;

/**
* Qingke
* 2023/10/15
* 17:40
**/
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
// 可以直接使用JPA生成的实现
// @Query(value = "select c.* from t_customer c where c.username=?1", nativeQuery = true)
Customer findByUsername(String username);
}

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* Qingke
* 2023/10/15
* 17:45
**/
@Service
public class CustomerService {

@Autowired
private CustomerRepository customerRepository;

@Autowired
private AuthorityRepository authorityRepository;

@Autowired
private RedisTemplate redisTemplate;

private String cacheName = "customer::";


// 用户名查询用户
public Customer getCustomer(String username) {
Customer customer = null;
Object o = redisTemplate.opsForValue().get("customer_" + username);
if (o != null) {
customer = (Customer) o;
} else {
customer = customerRepository.findByUsername(username);
if (customer != null) {
redisTemplate.opsForValue().set("customer_" + username, customer);
}
}
return customer;
}

//业务控制:使用唯一用户权限
public List<Authority> getCustomerAuthority(String username) {
List<Authority> authorities = null;
Object o = redisTemplate.opsForValue().get("authorities_" + username);
if (o != null) {
authorities = (List<Authority>) o;
} else {
authorities = authorityRepository.findAuthoritiesByUsername(username);
if (authorities.size() > 0) {
redisTemplate.opsForValue().set("authorities_" + username, authorities);
}
}
return authorities;
}
}

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;

/**
* Qingke
* 2023/10/15
* 17:48
**/
@EnableWebSecurity
public class RedisSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServicelmpl userDetailsServicelmpl;
@Autowired
private DataSource dataSource;

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 选择定义密码加密算法
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();


// 内存认证使用这个密码加密
// auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
// 注册一个身份,并且密码需要加密
// .withUser("GRAB").password(bCryptPasswordEncoder.encode("grab")).roles("common")
// .and()
// .withUser("管子").password(bCryptPasswordEncoder.encode("tube")).roles("vip");



// 使用JDBC认证
// 查询客户
//使用JBDC进行身份认证
// String userSQL = "select username,password,valid from t_customer " +
// "where username = ?";
// String authoritySQL = "select c.username,a.authority from t_customer c,t_authority a," +
// "t_customer_authority ca where ca.customer_id=c.id " +
// "and ca.authority_id=a.id and c.username =?";
// auth.jdbcAuthentication().passwordEncoder(bCryptPasswordEncoder)
// .dataSource(dataSource)
// .usersByUsernameQuery(userSQL)
// .authoritiesByUsernameQuery(authoritySQL);



// 使用UserDetailsServicelmpl进行身份验证
auth.userDetailsService(userDetailsServicelmpl).passwordEncoder(bCryptPasswordEncoder);
}
}

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
* Qingke
* 2023/10/15
* 17:47
**/
@Service
public class UserDetailsServicelmpl implements UserDetailsService {

@Autowired
private CustomerService customerService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

// 获取用户信息
Customer customer = customerService.getCustomer(username);
// 获取权限
List<Authority> authorityList = customerService.getCustomerAuthority(username);
// 信息权限封装
List<SimpleGrantedAuthority> simpleGrantedAuthorityList = authorityList.stream()
.map(authority -> new SimpleGrantedAuthority(authority
.getAuthority())).collect(Collectors.toList());
// 返回的封装的userdatails用户详情类
if (customer != null) {
UserDetails userDetails = new User(customer.getUsername(), customer.getPassword(), simpleGrantedAuthorityList);
return userDetails;
} else {
throw new UsernameNotFoundException("用户不存在");
}
}
}

第七章 自定义用户登录和退出

1. 自定义用户登录指在网站或应用程序中实现自己的用户登录系统,而不依赖于第三方平台或者框架提供的用户系统。通过自定义用户登录,可以更好地控制用户的访问权限和安全性。

2. 自定义用户退出指用户在完成相关操作后,主动退出当前账号。这可以保障用户的隐私安全,同时帮助用户更好地管理自己的账号信息。

3.自定义用户登录可以提高网站或应用程序的安全性。因为自己的用户登录系统可以采用更加安全的加密方式来保存用户密码和账号信息,从而避免敏感信息被泄露。

4.自定义用户登录可以提高用户的使用体验。通过自定义登录系统,可以更加方便地实现单点登录、多种登录方式等功能,从而提高用户登录的便捷性。

5.自定义用户退出可以保障用户的隐私安全。因为用户主动退出账号可以避免他人未经授权访问自己的个人账号信息,从而减少安全隐患。

6.自定义用户退出可以提高网站或应用程序的管理效率。通过自定义退出系统,可以更加方便地管理用户的访问权限和账号信息,从而更好地维护网站或应用程序的安全和管理。

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
* Qingke
* 2023/10/15
* 17:45
**/
@Service
public class CustomerService {

@Autowired
private CustomerRepository customerRepository;

@Autowired
private AuthorityRepository authorityRepository;

@Autowired
private RedisTemplate redisTemplate;


// 用户名查询用户
public Customer getCustomer(String username) {
Customer customer = null;
Object o = redisTemplate.opsForValue().get("customer_" + username);
if (o != null) {
customer = (Customer) o;
} else {
customer = customerRepository.findByUsername(username);
if (customer != null) {
redisTemplate.opsForValue().set("customer_" + username, customer);
}
}
return customer;
}

//业务控制:使用唯一用户权限
public List<Authority> getCustomerAuthority(String username) {
List<Authority> authorities = null;
Object o = redisTemplate.opsForValue().get("authorities_" + username);
if (o != null) {
authorities = (List<Authority>) o;
} else {
authorities = authorityRepository.findAuthoritiesByUsername(username);
if (authorities.size() > 0) {
redisTemplate.opsForValue().set("authorities_" + username, authorities);
}
}
return authorities;
}
}

package com.example.demo.Security;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
* Qingke
* 2023/10/14
* 16:51
**/
@Controller
public class FilmeController {
private String TAG = "FilmeController";
//影片详情页
@GetMapping("/detail/{type}/{path}")
public String toDetail(@PathVariable("type") String type, @PathVariable("path") String path) {
String value = "detail/" + type + "/" + path;
System.out.println(TAG + "===toDetail===" + value);
return value;
}
@GetMapping("/userLogin")
public String toLoginPage(){
return "login";
}

@GetMapping("/loginError")
String loginerror(){
return "login/loginerror";
}

@GetMapping("/index2")
String index2(){
return "login/index2";
}
}

package com.example.demo.Security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;

/**
* Qingke
* 2023/10/15
* 17:48
**/
@EnableWebSecurity
public class RedisSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServicelmpl userDetailsServicelmpl;
@Autowired
private DataSource dataSource;

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 选择定义密码加密算法
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();


// 内存认证使用这个密码加密
// auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder)
// 注册一个身份,并且密码需要加密
// .withUser("GRAB").password(bCryptPasswordEncoder.encode("grab")).roles("common")
// .and()
// .withUser("管子").password(bCryptPasswordEncoder.encode("tube")).roles("vip");


// 使用JDBC认证
// 查询客户
//使用JBDC进行身份认证
// String userSQL = "select username,password,valid from t_customer " +
// "where username = ?";
// String authoritySQL = "select c.username,a.authority from t_customer c,t_authority a," +
// "t_customer_authority ca where ca.customer_id=c.id " +
// "and ca.authority_id=a.id and c.username =?";
// auth.jdbcAuthentication().passwordEncoder(bCryptPasswordEncoder)
// .dataSource(dataSource)
// .usersByUsernameQuery(userSQL)
// .authoritiesByUsernameQuery(authoritySQL);


// 使用UserDetailsServicelmpl进行身份验证
auth.userDetailsService(userDetailsServicelmpl).passwordEncoder(bCryptPasswordEncoder);
}


@Override
protected void configure(HttpSecurity http)throws Exception{
http.authorizeRequests().antMatchers("/").permitAll().antMatchers("/login/**").permitAll()//对login.html文件进行统一放行
.antMatchers("/detail/common/**").hasAnyRole("common","vip")//放行common用户和vip用户访问
.antMatchers("/detail/vip/**").hasAnyRole("vip")//只放行VIP用户访问
.anyRequest().authenticated();

//自定义用户登录控制
http.formLogin().loginPage("/userLogin").permitAll()//登录用户的跳转页面
.usernameParameter("name").passwordParameter("pwd")//用户名密码
.defaultSuccessUrl("/index2")//登录成功后跳转
.failureUrl("/loginError");//登录失败后跳转
//自定义用户退出控制
http.logout().logoutUrl("/mylogout").logoutSuccessUrl("/");
}
}

package com.example.demo.Thymeleaf.web;

/**
* Qingke
* 2023/9/21
* 13:08
**/

import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
@Configuration
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//用户请求/admin开头路径,判断用户是否登录
String uri = request.getRequestURI();
Object loginUser = request.getSession().getAttribute("loginUser");
if (uri.startsWith("/admin") && null == loginUser) {
response.sendRedirect("/toLoginPage");
return false;
}
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
//向request域中存放当前年份用于页面动态展示
request.setAttribute("currentYear", Calendar.getInstance().get(Calendar.YEAR));
request.setAttribute("currentMonth", Calendar.getInstance().get(Calendar.MONTH));
request.setAttribute("currentDay", Calendar.getInstance().get(Calendar.DAY_OF_MONTH));
request.setAttribute("currentHour", Calendar.getInstance().get(Calendar.HOUR_OF_DAY));
request.setAttribute("currentMinute", Calendar.getInstance().get(Calendar.MINUTE));
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}

package com.example.demo.Thymeleaf.web;

/**
* Qingke
* 2023/9/21
* 12:43
**/
import com.example.demo.Thymeleaf.MyLocalResovel;
import com.example.demo.Thymeleaf.web.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//自定义的国际化组件
@Bean
public LocaleResolver localeResolver(){
return new MyLocalResovel();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//请求toLoginPage映射路径或者logi.html页面都会自动映射到login.html页面
registry.addViewController("/toLoginPage").setViewName("login");
registry.addViewController("/login2.html").setViewName("login");
}
@Autowired
private MyInterceptor myInterceptor;
//添加拦截器管理
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor)
.addPathPatterns("/**")//拦截所有路径请求
.excludePathPatterns("/login2.html");//路径请求放行处理
}

}

https://github.com/QingkeQAQ/zuoye/commit/c3a7275ec5b1ae4b6f792cbeed60ec0a2430b6b9

第八章 springBoot整合RabbitMQ

1. RabbitMQ是一种消息队列服务,可以实现应用程序之间的异步通信,提高系统的可靠性和可拓展性。

2. SpringBoot是一种快速开发框架,提供了很多便利性的特性,例如自动配置、简化开发流程等。

3. SpringBoot整合RabbitMQ可以让开发人员更加轻松地在应用程序中使用消息队列服务,提高应用程序的灵活性和可靠性。

4. SpringBoot提供了丰富的注解和配置,可用于定义和配置RabbitMQ的队列、交换器、绑定等。

5. 上述整合可以让开发人员轻松实现消息的生产和消费,提高系统的可用性和性能。

package com.example.demo.RabbitMQ;

import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* Qingke
* 2023/10/25
* 8:51
**/
@Configuration
public class RabbitMQConfig {
// 配置消息转换器,这里使用Jackson2JsonMessageConverter将消息转换为JSON格式
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}

// 1. 定义一个fanout类型的交换器
@Bean
public Exchange fanout_exchange(){
// 创建并返回一个fanout交换器,名称为"fanout_exchange"
return ExchangeBuilder.fanoutExchange("fanout_exchange").build();
}



// 2. 定义两个默认持久化队列,分别用于处理email和sms消息
@Bean
public Queue fanout_queue_email(){
// 创建并返回一个名为"fanout_queue_email"的队列,该队列用于处理email消息
return new Queue("fanout_queue_email");
}

@Bean
public Queue fanout_queue_qq(){
// 创建并返回一个名为"fanout_queue_qq"的队列,该队列用于处理sms消息
return new Queue("fanout_queue_qq");
}


@Bean
public Queue fanout_queue_wechat(){
// 创建并返回一个名为"fanout_queue_sms"的队列,该队列用于处理sms消息
return new Queue("fanout_queue_wechat");
}


// 3. 将队列分别与交换器进行绑定
@Bean
public Binding bindingEmail(){
// 创建并返回一个将"fanout_queue_email"与"fanout_exchange"绑定的操作
// 由于是fanout交换器,不使用路由键,因此使用空字符串 ""
return BindingBuilder.bind(fanout_queue_email()).to(fanout_exchange()).with("").noargs();
}
@Bean
public Binding bindingQQ(){
// 创建并返回一个将"fanout_queue_qq"与"fanout_exchange"绑定的操作
// 同样,由于是fanout交换器,不使用路由键,因此使用空字符串 ""
return BindingBuilder.bind(fanout_queue_qq()).to(fanout_exchange()).with("").noargs();
}
@Bean
public Binding bindingWeChat(){
// 创建并返回一个将"fanout_queue_sms"与"fanout_exchange"绑定的操作
// 同样,由于是fanout交换器,不使用路由键,因此使用空字符串 ""
return BindingBuilder.bind(fanout_queue_wechat()).to(fanout_exchange()).with("").noargs();
}

}

package com.example.demo.RabbitMQ;

import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

/**
* Qingke
* 2023/10/25
* 8:51
**/
@Service
public class RabbitMQService {
/**
* Publish/Subscribe工作接收、处理邮件业务
*/
@RabbitListener(bindings = @QueueBinding(value =
@Queue("fanout_queue_email"), exchange =
@Exchange(value = "fanout_exchange", type = "fanout")))
public void psubConsumerEmailAno(User user) {
// 当fanout交换器发布消息到fanout_queue_email队列时,这个方法将被触发
// 用于处理邮件业务消息,参数 user 包含消息内容
System.out.println("邮件业务接收到消息: " + user);
}

//@RabbitListener(queues = "fanout_queue_email")
//public void psubConsumerEmail(Message message) {
// byte[] body = message.getBody();
// String s = new String(body);
// System.out.println("邮件业务接收到消息: "+s);
//}

/**
* Publish/Subscribe工作接收、处理短信业务
*/

@RabbitListener(bindings = @QueueBinding(value =
@Queue("fanout_queue_qq"), exchange =
@Exchange(value = "fanout_exchange", type = "fanout")))
public void psubConsumerQQAno(User user) {
// 当fanout交换器发布消息到fanout_queue_qq队列时,这个方法将被触发
// 用于处理短信业务消息,参数 user 包含消息内容
System.out.println("QQ业务接收到消息: " + user);
}

/**
* Publish/Subscribe工作接收、处理短信业务
*/
@RabbitListener(bindings = @QueueBinding(value =
@Queue("fanout_queue_wechat"), exchange =
@Exchange(value = "fanout_exchange", type = "fanout")))
public void psubConsumerWechatAno(User user) {
// 当fanout交换器发布消息到fanout_queue_wechat队列时,这个方法将被触发
// 用于处理短信业务消息,参数 user 包含消息内容
System.out.println("微信业务接收到消息: " + user);
}


//@RabbitListener(queues = "fanout_queue_sms")
//public void psubConsumerSms(Message message) {
// byte[] body = message.getBody();
// String s = new String(body);
// System.out.println("短信业务接收到消息: "+s);
//}

/**
* 路由模式消息接收、处理error级别日志信息
*/

@RabbitListener(bindings = @QueueBinding(value =
@Queue("routing_queue_error"), exchange =
@Exchange(value = "routing_exchange", type = "direct"),
key = "error_routing_key"))
public void routingConsumerError(String message) {
// 当error_routing_key路由键的消息发布到routing_queue_error队列时,这个方法将被触发
// 用于处理error级别日志消息
System.out.println("接收到error级别日志消息: " + message);
}

/**
* 路由模式消息接收、处理info、error、warning级别日志信息
*/

@RabbitListener(bindings = @QueueBinding(value =
@Queue("routing_queue_all"), exchange =
@Exchange(value = "routing_exchange", type = "direct"),
key = {"error_routing_key", "info_routing_key", "warning_routing_key"}))
public void routingConsumerAll(String message) {
// 当error_routing_key、info_routing_key或warning_routing_key的消息发布到routing_queue_all队列时,这个方法将被触发
// 用于处理info、error、warning等级别日志消息
System.out.println("接收到info、error、warning等级别日志消息: " + message);
}

/**
* 通配符模式消息接收、进行邮件业务订阅处理
*/

@RabbitListener(bindings = @QueueBinding(value =
@Queue("topic_queue_email"), exchange =
@Exchange(value = "topic_exchange", type = "topic"),
key = "info.#.email.#"))
public void topicConsumerEmail(User user) {
// 当满足通配符规则的消息发布到topic_queue_email队列时,这个方法将被触发
// 用于处理邮件订阅需求处理消息
System.out.println("接收到邮件订阅需求处理消息: " + user);
}

/**
* 通配符模式消息接收、进行短信业务订阅处理
*/

@RabbitListener(bindings = @QueueBinding(value =
@Queue("topic_queue_qq"), exchange =
@Exchange(value = "topic_exchange", type = "topic"),
key = "info.#.qq.#"))
public void topicConsumerQQ(User user) {
// 当满足通配符规则的消息发布到topic_queue_sms队列时,这个方法将被触发
// 用于处理短信订阅需求处理消息
System.out.println("接收到QQ订阅需求处理消息: " + user);
}

@RabbitListener(bindings = @QueueBinding(value =
@Queue("topic_queue_wechat"), exchange =
@Exchange(value = "topic_exchange", type = "topic"),
key = "info.#.wechat.#"))
public void topicConsumerWechat(User user) {
// 当满足通配符规则的消息发布到topic_queue_wechat队列时,这个方法将被触发
// 用于处理短信订阅需求处理消息
System.out.println("接收到微信订阅需求处理消息: " + user);
}

}

package com.example.demo.RabbitMQ;

/**
* Qingke
* 2023/10/25
* 8:50
**/
public class User {
private Integer id;
private String username;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
'}';
}
}

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@EnableCaching
@ImportResource("classpath:xmlpropertoes.xml")//加载自定义xml配置文件位置
public class Demo1Application {

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

}

#RabbitMQ
# 配置RabbitMQ消息中间件连接配置
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#配置RabbitMQ虚拟主机路径/,默认可以省略
spring.rabbitmq.virtual-host=/

package com.example.demo;

import com.example.demo.RabbitMQ.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* Qingke
* 2023/10/25
* 8:53
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMqTest {
@Autowired
private AmqpAdmin amqpAdmin;
@Autowired
private RabbitTemplate rabbitTemplate;

// 此方法用于配置 RabbitMQ 的交换器和队列
@Test
public void amqpAdmin() {
// 1. 定义一个名为 "fanout_exchange" 的 fanout 类型的交换器
amqpAdmin.declareExchange(new FanoutExchange("fanout_exchange"));

// 2. 定义三个默认持久化队列,分别用于处理 email、qq 和 wechat 消息
amqpAdmin.declareQueue(new Queue("fanout_queue_email"));
amqpAdmin.declareQueue(new Queue("fanout_queue_qq"));
amqpAdmin.declareQueue(new Queue("fanout_queue_wechat"));

// 3. 将队列分别与交换器进行绑定
amqpAdmin.declareBinding(new Binding("fanout_queue_email", Binding.DestinationType.QUEUE, "fanout_exchange", "", null));
amqpAdmin.declareBinding(new Binding("fanout_queue_qq", Binding.DestinationType.QUEUE, "fanout_exchange", "", null));
amqpAdmin.declareBinding(new Binding("fanout_queue_wechat", Binding.DestinationType.QUEUE, "fanout_exchange", "", null));
}

// 此方法用于发布消息到 fanout 类型的交换器
@Test
public void psubPublisherAll() {
User user = new User();
user.setId(1);
user.setUsername("石头");
// 发布 user 对象消息到名为 "fanout_exchange" 的 fanout 交换器
rabbitTemplate.convertAndSend("fanout_exchange", "", user);
//给所有队列发消息
}
@Test
public void topicPublisherQQ() {
User user = new User();
user.setId(1);
user.setUsername("石头");
// (4) 发送同时订阅邮件、qq 和微信的用户信息,路由键为 "info.email.qq.wechat"
rabbitTemplate.convertAndSend("topic_exchange", "info.qq",user );
//单独给qq队列消息
}

@Test
public void topicPublisherEmail() {
User user = new User();
user.setId(1);
user.setUsername("石头");
// (4) 发送同时订阅邮件、qq 和微信的用户信息,路由键为 "info.email.qq.wechat"
rabbitTemplate.convertAndSend("topic_exchange", "info.email",user );
//单独给邮件队列发消息
}

@Test
public void topicPublisherWechat() {
User user = new User();
user.setId(1);
user.setUsername("石头");
// (4) 发送同时订阅邮件、qq 和微信的用户信息,路由键为 "info.email.qq.wechat"
rabbitTemplate.convertAndSend("topic_exchange", "info.wechat",user );
//单独给微信队列发消息
}

@Test
public void topicPublisherQQWechat() {
User user = new User();
user.setId(1);
user.setUsername("石头");
// (4) 发送同时订阅邮件、qq 和微信的用户信息,路由键为 "info.email.qq.wechat"
rabbitTemplate.convertAndSend("topic_exchange", "info.wechat.qq",user );
//只给QQ、微信队列发消息
}


// 此方法用于发布消息到 "routing_exchange" 的路由交换器
@Test
public void routingPublisher() {
// 发布字符串消息到 "routing_exchange",指定路由键为 "error_routing_key"
rabbitTemplate.convertAndSend("routing_exchange", "error_routing_key", "routing send error message");
}

// 此方法用于发布消息到 "topic_exchange" 的主题交换器
}

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

第九章 同步和异步任务

同步任务和异步任务是指程序执行中任务的执行方式。

1. 同步任务

同步任务是指在程序执行过程中,必须等待前一个任务执行完成后,才能执行后面的任务。同步任务按照顺序依次执行,直到所有任务执行完成。

例如,在编写一个网页时,需要加载一个图片。使用同步任务时,页面必须等待图片加载完成后才能继续加载后面的内容。

2. 异步任务

异步任务是指任务在执行过程中,不必等待前一个任务执行完成,可以直接执行后面的任务。异步任务常用于需要时间比较长的操作,如文件读取、网络请求等。

例如,在编写一个网页时,使用异步任务进行图片加载,可以先加载其他的内容,而图片会在后台异步加载,不阻塞页面的其他内容。这样可以提高页面的加载速度和用户体验。

总之,同步任务与异步任务的区别在于任务的执行方式和执行顺序。使用适当的任务执行方式可以提高程序的效率和用户的体验。

package com.example.demo.asynchronization;

/**
* Qingke
* 2023/10/26
* 13:13
**/

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Future;
/**
* 异步控制器类,用于处理异步请求
*/
@RestController
public class AsyncController {

@Autowired
AsyncService myService;

/**
* 处理发送短信请求,计算主流程耗时
*
* @return 成功消息
* @throws Exception 异常
*/
@GetMapping("/sendSMS")
public String sendSMS() throws Exception {
Long startTime = System.currentTimeMillis();
myService.sendSMS();
Long endTime = System.currentTimeMillis();
System.out.println("主流程耗时: " + (endTime - startTime));
return "success";
}

/**
* 处理统计请求,包括异步计算从1到1000和从1000到2000的整数总和
*
* @return 成功消息
* @throws Exception 异常
*/
@GetMapping("/statistics")
public String statistics() throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 启动异步任务,分别计算1-1000和1000-2000的整数总和
Future<Integer> futureA = myService.processA();
Future<Integer> futureB = myService.processB();
// 等待异步任务完成并汇总结果

int total = futureA.get() + futureB.get();
Long startTime = System.currentTimeMillis();
System.out.println("----------------------------");
Long endTime = System.currentTimeMillis();//结束时间(没调用)
System.out.println("开始执行时间" + sdf.format(new Date(startTime)));
System.out.println("异步任务数据统计汇总结果: " + total);
return "success";
}
}

package com.example.demo.asynchronization;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Future;

/**
* Qingke
* 2023/10/26
* 13:14
**/
@Service
public class AsyncService {
@Async
public void sendSMS() throws Exception {
System.out.println("调用短信验证码业务方法...");
Long startTime = System.currentTimeMillis();
Thread.sleep(5000);
Long endTime = System.currentTimeMillis();
System.out.println("短信业务执行完成耗时:" + (endTime - startTime));
}

@Async
public Future<Integer> processA() throws Exception {
int start = 1;
int end = 1000;
int sum = 0;


// 打印开始时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 获取开始时间
long startTime = System.currentTimeMillis();
System.out.println("计算1-1000的值总和开始执行时间: " + sdf.format(new Date(startTime)));

// 计算总和
for (int i = start; i <= end; i++) {
sum += i;
}

// 打印结果
System.out.println("从 " + start + " 到 " + end + " 的整数总和是: " + sum);

// 休眠5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取结束时间
long endTime = System.currentTimeMillis();

// 打印结束时间
System.out.println("计算1-1000的值总和结束时间: " + sdf.format(new Date(endTime)));
return new AsyncResult<Integer>(sum);
}

@Async
public Future<Integer> processB() throws Exception {
int start = 1000;
int end = 2000;
int sum = 0;

// 打印开始时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 获取开始时间
long startTime = System.currentTimeMillis();
System.out.println("计算1000 - 2000的值总和开始执行时间: " + sdf.format(new Date(startTime)));

// 计算总和
for (int i = start; i <= end; i++) {
sum += i;
}

// 打印结果
System.out.println("从 " + start + " 到 " + end + " 的整数总和是: " + sum);

// 获取结束时间
long endTime = System.currentTimeMillis();
System.out.println("计算1000 - 2000的值总和结束时间: " + sdf.format(new Date(endTime)));
return new AsyncResult<Integer>(sum);
}


}

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
* Qingke
* 2023/10/26
* 13:15
**/
@EnableAsync// 启用异步方法支持
@SpringBootApplication
public class AsyncAppication {
public static void main(String[] args) {
SpringApplication.run(AsyncAppication.class, args);
}
}

第九章 定时任务实现

1. 定时任务是指在特定的时间或时间间隔内自动执行特定的操作或脚本。

2. 定时任务的意义在于自动化工作流程,提高工作效率,减少重复性工作的出错概率。

3. 定时任务可用于数据备份、数据清理、自动化测试、文档生成等方面,能够大大提高工作效率。

4. 定时任务的实现需使用特定的工具或编程语言,如Linux中的cron,Java中的Quartz等。

5. 定时任务的编写需要考虑执行时间、执行频率、任务执行过程中的异常处理等问题,且需注意避免对系统性能和资源造成过大的影响。

package com.example.demo.asynchronization;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
* Qingke
* 2023/10/26
* 13:17
**/
@Service
public class ScheduledTaskService {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Integer count1 = 1;
private Integer count2 = 1;
private Integer count3 = 1;

// 使用fixedRate属性,每10秒执行一次任务,任务执行完后休眠5秒
@Async
@Scheduled(fixedRate = 10000)
public void scheduledTaskWithFixedRate() throws InterruptedException {
System.out.println("fixedRate1执行时间:" + dateFormat.format(new Date()));
Thread.sleep(5000); // 代码块休眠5秒
}

// // 使用fixedRate属性,每15秒执行一次任务,任务执行完后休眠15秒
// @Async
// @Scheduled(fixedRate = 15000)
// public void scheduledTaskWithFixedRate2() throws InterruptedException {
// System.out.println("fixedRate2每十五秒执行一次");
// Thread.sleep(15000); // 代码块休眠15秒
// }
//
// // 使用fixedDelay属性,10秒后执行任务,之后每10秒执行一次,任务执行完后休眠5秒
// @Async
// @Scheduled(fixedDelay = 10000)
// public void scheduledTaskWithFixedDelay() throws InterruptedException {
// System.out.println("fixedDelay1执行时间:" + dateFormat.format(new Date()));
// Thread.sleep(5000); // 代码块休眠5秒
// }
//
// // 使用fixedDelay属性,5秒后执行任务,之后每5秒执行一次,任务执行完后休眠5秒
// @Async
// @Scheduled(fixedDelay = 5000)
// public void scheduledTaskWithFixedDelay2() throws InterruptedException {
// System.out.println("fixedDelay2每五秒执行一次");
// Thread.sleep(5000); // 代码块休眠5秒
// }
//
// // 使用initialDelay属性,初始延迟10秒后执行,之后每10秒执行一次,任务执行完后休眠5秒
// @Async
// @Scheduled(initialDelay = 10000, fixedRate = 10000)
// public void scheduledTaskWithInitialDelay() throws InterruptedException {
// System.out.println("initialDelay1执行时间:" + dateFormat.format(new Date()));
// Thread.sleep(5000); // 代码块休眠5秒
// }
//
// // 使用initialDelay属性,初始延迟0秒后执行,之后每5秒执行一次,任务执行完后休眠5秒
// @Async
// @Scheduled(initialDelay = 0, fixedRate = 5000)
// public void scheduledTaskWithInitialDelay2() throws InterruptedException {
// System.out.println("initialDelay2执行时间:" + dateFormat.format(new Date()));
// Thread.sleep(5000); // 代码块休眠5秒
// }

// 使用cron表达式,每分钟执行一次任务
// @Scheduled(cron = "0 * * * * *")
// // 秒、分、小时、日、月、星期 MON-FRI表示周一到周五
// // 0:代表秒,表示在每分钟的第0秒触发任务。
// // *:代表通配符,表示每分钟都匹配。
// public void scheduledTaskCron() {
// System.out.println(String.format("cron第%s次执行,当前时间为:%s", count3++, dateFormat.format(new Date())));
// }
}
 

邮件任务

1. 邮件任务是指通过邮件发送给某个人或团队的工作任务或指令。

2. 邮件任务的意义在于能够在工作中提高任务的透明度和效率,同时也能方便进行跟踪和催促进度。

3. 邮件任务可以明确任务的责任人和截止时间,让接收者能够快速了解任务的内容和要求,并在规定时间内完成任务。

4. 邮件任务可以通过邮件的回复、标记和提醒功能,提高任务的可控性和优先级,以保证任务的及时完成。

5. 邮件任务也可以作为工作记录和沟通的证明,对于管理者来说,能够了解员工的工作情况并及时进行监督和指导。

package com.example.demo.email;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

/**
* Qingke
* 2023/10/27
* 19:12
**/
@Service
public class SendEmailService {
@Autowired
private JavaMailSenderImpl mailSender; // 自动注入邮件发送器
@Value("${spring.mail.username}")
private String from; // 从配置文件中读取发件人邮箱地址

/**
* 发送纯文本邮件
* @param to 收件人地址
* @param subject 邮件标题
* @param text 邮件内容
*/
public void sendSimpleEmail(String to, String subject, String text) {
// 创建纯文本邮件信息对象
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from); // 设置发件人
message.setTo(to); // 设置收件人
message.setSubject(subject); // 设置邮件标题
message.setText(text); // 设置邮件内容
try {
// 发送邮件
mailSender.send(message);
System.out.println("纯文本邮件发送成功");
} catch (MailException e) {
System.out.println("纯文本邮件发送失败 " + e.getMessage());
e.printStackTrace();
}
}

/**
* 发送复杂邮件(包括静态资源和附件)
* @param to 收件人地址
* @param subject 邮件标题
* @param text 邮件内容
* @param filePath 附件地址
* @param rscId 静态资源唯一标识
* @param rscPath 静态资源地址
*/
public void sendComplexEmail(String to, String subject, String text, String filePath, String rscId, String rscPath) {
// 创建复杂邮件信息对象 MimeMessage
MimeMessage message = mailSender.createMimeMessage();
try {
// 使用 MimeMessageHelper 帮助类,设置 multipart 多部分使用为 true
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from); // 设置发件人
helper.setTo(to); // 设置收件人
helper.setSubject(subject); // 设置邮件标题
helper.setText(text, true); // 设置邮件内容,允许 HTML 内容
// 设置邮件静态资源(图片等)
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);
// 设置邮件附件
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
// 发送邮件
mailSender.send(message);
System.out.println("复杂邮件发送成功");
} catch (MessagingException e) {
System.out.println("复杂邮件发送失败 " + e.getMessage());
e.printStackTrace();
}
}

//单独发送图片的方法
public void sendComplexEmailImg(String to, String subject, String text, String rscId, String rscPath) {
// 创建复杂邮件信息对象 MimeMessage
MimeMessage message = mailSender.createMimeMessage();
try {
// 使用 MimeMessageHelper 帮助类,设置 multipart 多部分使用为 true
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from); // 设置发件人
helper.setTo(to); // 设置收件人
helper.setSubject(subject); // 设置邮件标题
helper.setText(text, true); // 设置邮件内容,允许 HTML 内容(未调用)
// 设置邮件静态资源(图片等)
FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);
// 发送邮件
mailSender.send(message);
System.out.println("单独发送图片邮件发送成功");
} catch (MessagingException e) {
System.out.println("单独发送图片邮件发送失败 " + e.getMessage());
e.printStackTrace();
}
}

//单独发送附件的方法
public void sendComplexEmailFile(String to, String subject, String text, String filePath) {
// 创建复杂邮件信息对象 MimeMessage
MimeMessage message = mailSender.createMimeMessage();
try {
// 使用 MimeMessageHelper 帮助类,设置 multipart 多部分使用为 true
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from); // 设置发件人
helper.setTo(to); // 设置收件人
helper.setSubject(subject); // 设置邮件标题
helper.setText(text, true); // 设置邮件内容,允许 HTML 内容
// 设置邮件附件
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
// 发送邮件
mailSender.send(message);
System.out.println("复杂邮件发送成功");
} catch (MessagingException e) {
System.out.println("复杂邮件发送失败 " + e.getMessage());
e.printStackTrace();
}
}

/**
* 发送模板邮件
* @param to 收件人地址
* @param subject 邮件标题
* @param content 邮件内容
*/
public void sendTemplateEmail(String to, String subject, String content) {
MimeMessage message = mailSender.createMimeMessage();
try {
// 使用 MimeMessageHelper 帮助类,设置 multipart 多部分使用为 true
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from); // 设置发件人
helper.setTo(to); // 设置收件人
helper.setSubject(subject); // 设置邮件标题
helper.setText(content, true); // 设置邮件内容,允许 HTML 内容
// 发送邮件
mailSender.send(message);
System.out.println("模板邮件发送成功");
} catch (MessagingException e) {
System.out.println("模板邮件发送失败 " + e.getMessage());
e.printStackTrace();
}
}
}

#邮件任务
# 发件人邮服务器相关配置
spring.mail.host=smtp.qq.com
spring.mail.port=587
# 配置个人QQ账户和密码(密码是加密后的授权码)
spring.mail.username=1939633363@qq.com
spring.mail.password=vigojalrzwqnbfah
spring.mail.default-encoding=UTF-8
# 邮件服务超时时间配置
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=3000
spring.mail.properties.mail.smtp.writetimeout=5000

<!-- 邮件服务依赖启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Spring Boot整合支持的Thymeleaf模板引擎依赖启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

package com.example.demo;

import com.example.demo.email.SendEmailService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

/**
* Qingke
* 2023/10/27
* 19:22
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class EmailApplicationTests {
@Autowired
private SendEmailService sendEmailService; // 自动注入邮件发送服务

@Autowired
private TemplateEngine templateEngine; // 自动注入模板引擎

@Test
public void sendSimpleMailTest() {
String to = "1939633363@qq.com"; // 收件人地址
String subject = "纯文本邮件"; // 邮件标题
String text = "Spring Boot纯文本邮件发送内容测试....."; // 邮件内容
// 发送简单邮件
sendEmailService.sendSimpleEmail(to, subject, text);
}

@Test
public void sendComplexEmailAllTest() {
String to = "1939633363@qq.com"; // 收件人地址
String subject = "复杂"; // 邮件标题
// 定义邮件内容
StringBuilder text = new StringBuilder();
text.append("<html><head></head>");
text.append("<body><h1>祝大家元旦快乐!</h1>");
// cid为固定写法,rscId指定一个唯一标识
String rscId = "img001";
text.append("<img src='cid:" + rscId + "'/></body>");
text.append("</html>");
// 指定静态资源文件和附件路径
String rscPath = "D:\\IDEA\\demo1\\src\\main\\resources\\static\\login\\file\\newyear.jpg";
String filePath = "D:\\IDEA\\demo1\\src\\main\\resources\\static\\login\\file\\元旦放假注意事项.docx";
// 发送复杂邮件
sendEmailService.sendComplexEmail(to, subject, text.toString(), filePath, rscId, rscPath);
}

//单独发送邮件附件的测试方法
@Test
public void sendComplexEmailFileTest() {
String to = "1939633363@qq.com"; // 收件人地址
String subject = "单独发送附件"; // 邮件标题
// 定义邮件内容
StringBuilder text = new StringBuilder();
text.append("<html><head></head>");
// cid为固定写法,rscId指定一个唯一标识
// 指定静态资源文件和附件路径
String filePath = "D:\\IDEA\\demo1\\src\\main\\resources\\static\\login\\file\\元旦放假注意事项.docx";
// 发送复杂邮件
sendEmailService.sendComplexEmailFile(to, subject, text.toString(), filePath);
}

//单独发送邮件图片的测试方法
@Test
public void sendComplexEmailImgTest() {
String to = "1939633363@qq.com"; // 收件人地址
String subject = "单独发送图片 标题"; // 邮件标题
// 定义邮件内容
StringBuilder text = new StringBuilder();
text.append("<html><head></head>");
// cid为固定写法,rscId指定一个唯一标识
String rscId = "img001";
text.append("<img src='cid:" + rscId + "'/></body>");
text.append("</html>");
// 指定静态资源文件和附件路径
String rscPath = "D:\\IDEA\\demo1\\src\\main\\resources\\static\\login\\file\\newyear.jpg";
// 发送复杂邮件
sendEmailService.sendComplexEmailImg(to, subject, text.toString(),rscId, rscPath);
}

@Test
public void sendTemplateEmailTest() {
String to = "1939633363@qq.com"; // 收件人地址
String subject = "模板邮件,标题"; // 邮件标题
// 使用模板邮件定制邮件正文内容
Context context = new Context();
context.setVariable("username", "石头"); // 设置模板变量 username
context.setVariable("code", "456123"); // 设置模板变量 code
// 使用TemplateEngine设置要处理的模板页面
String emailContent = templateEngine.process("email/emailTemplate_vercode", context);
// 发送模板邮件
sendEmailService.sendTemplateEmail(to, subject, emailContent);
}
}

https://github.com/QingkeQAQ/zuoye/commits/tijiao

https://github.com/QingkeQAQ/zuoye/commits/master

https://github.com/QingkeQAQ/zuoye

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值