技术点
功能: 1.登录,每个用户有不同的权限,通过权限来查看各个部门的员工 2.功能: 职称管理、部门管理[存储过程实现部门添加]、员工职位管理、员工工资套管理等 3.添加员工使用rabbitmq及thymeleaf实现邮箱实时发送
1.SpringSecurity 单点登录安全认证及授权 2.google Kaptcha 验证码校验 3.Redis缓存菜单数据 注意: 对菜单做更新删除添加的时候要清空redis中的数据
学习点
1.RBAC 角色访问控制模型的关联查询+子查询+自连接查询+ResultMap映射
注意点: 自连接查询有层级关系的话,子表需要进行如下操作,并且在xml文件通过ResultMap进行映射
SELECT DISTINCT t1.* , t2.id AS id2, t2.component AS component2, t2.enabled AS enabled2, t2.iconCls AS iconCls2, t2.keepAlive AS keepAlive2, t2.name AS name2, t2.parentId AS parentId2, t2.requireAuth AS requireAuth2, t2.path AS path2
完整查询SQL+XML文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ansel.server.mapper.MenuMapper"> <resultMap id="BaseResultMap" type="com.ansel.server.entity.Menu"> <id column="id" property="id" /> <result column="url" property="url" /> <result column="path" property="path" /> <result column="component" property="component" /> <result column="name" property="name" /> <result column="iconCls" property="iconCls" /> <result column="keepAlive" property="keepAlive" /> <result column="requireAuth" property="requireAuth" /> <result column="parentId" property="parentId" /> <result column="enabled" property="enabled" /> </resultMap> <resultMap id="Menus" type="com.ansel.server.entity.Menu" extends="BaseResultMap"> <collection property="children" ofType="com.ansel.server.entity.Menu"> <id column="id2" property="id" /> <result column="url2" property="url" /> <result column="path2" property="path" /> <result column="component2" property="component" /> <result column="name2" property="name" /> <result column="iconCls2" property="iconCls" /> <result column="keepAlive2" property="keepAlive" /> <result column="requireAuth2" property="requireAuth" /> <result column="parentId2" property="parentId" /> <result column="enabled2" property="enabled" /> </collection> </resultMap> <!-- 自关联查询--> <select id="getMenusByAdminId" resultMap="Menus" parameterType="Integer"> SELECT DISTINCT t1.* , t2.id AS id2, t2.component AS component2, t2.enabled AS enabled2, t2.iconCls AS iconCls2, t2.keepAlive AS keepAlive2, t2.name AS name2, t2.parentId AS parentId2, t2.requireAuth AS requireAuth2, t2.path AS path2 FROM t_menu t1 , ( SELECT m.* FROM t_admin a LEFT JOIN t_admin_role ad ON a.id = ad.adminId LEFT JOIN t_menu_role mr ON ad.rid = mr.rid LEFT JOIN t_menu m ON m.id = mr.mid WHERE a.`id`= 1 ) t2 WHERE t1.id = t2.parentId AND t2.enabled = 1 ORDER BY t2.id ASC </select> </mapper>
2.对于不存在数据库的字段,mybatis-plus要加@TableField(exist = flase)
@TableField(exist = false) @ApiModelProperty("角色") private List<Role> roles;
3.本项目授权校验方式
一、数据库中有url 和 role 两个字段 1.url代表可以访问的路径 2.role代表用户的角色
二、配置CustomFilter过滤器可以获取当前的请求
作用: 1.iMenuService 用于获取所有menu的url和t_menu_role表中的角色
SELECT m.*, r.id AS rid, r.name AS `rname`, r.nameZh AS rnameZh FROM t_role r LEFT JOIN t_menu_role mr ON r.id = mr.rid LEFT JOIN t_menu m ON mr.mid = m.id ORDER BY m.id
2.AntPathMatcher antPathMatcher = new AntPathMatcher(); match方法匹配路径是否一样 3.CustomFilter实现FilterInvocationSecurityMetadataSource方法获取请求的到匹配url的地址的角色,返回ConfigAttribute
package com.ansel.server.filter; import com.ansel.server.entity.Menu; import com.ansel.server.entity.Role; import com.ansel.server.service.IMenuService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import java.util.Collection; import java.util.List; /** * @author Ansel Zhong * @title eoffice * @Date 2023/3/3 * @Description 根据url分析请求所需的角色 */ @Component public class CustomFilter implements FilterInvocationSecurityMetadataSource { @Autowired private IMenuService iMenuService; AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { //获取请求的url String requestUrl = ((FilterInvocation) o).getRequestUrl(); List<Menu> menus = iMenuService.getMenuswithRole(); System.out.println("menus-->" + menus); System.out.println("requestUrl-->" + requestUrl); for (Menu menu : menus) { if (menu.getId() != null) { //判断请求url是否与菜单角色匹配 if (antPathMatcher.match(menu.getUrl(),requestUrl)){ //获取角色姓名 String[] str = menu.getRoles().stream() .map(Role::getName).toArray(String[]::new); return SecurityConfig.createList(str); } } } //如果没有就给一个默认的登陆角色 return SecurityConfig.createList("ROLE_LOGIN"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return true; } }
三、配置CustomDecisionManager继承AccessDecisionManager [PS:关于权限设置,是在这里设设置的:]
public UserDetailsService userDetailsService(){ return username -> { Admin admin = adminService.getAdminByUserName(username); if (admin != null) { admin.setRoles(adminService.getRoles(admin.getId())); return admin; } throw new UsernameNotFoundException("用户名或者密码不正确"); }; }
AdminServiceImpl类
//更新security登录用户对象 UsernamePasswordAuthenticationToken passwordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(passwordAuthenticationToken);
此类获取authentication --> getAuthorities()方法获取authorities权限, 通过比对CustomFilter返回的权限来判断改用户是否有权限
package com.ansel.server.filter; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; import java.util.Collection; /** * @author Ansel Zhong * @title eoffice * @Date 2023/3/3 * @Description * 判断用户角色 */ @Component public class CustomDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException { for (ConfigAttribute configAttribute : collection) { //当前url所需要的角色 String needRole = configAttribute.getAttribute(); //判断角色是登陆即可访问的角色,此角色在CustomFilter中设置 if ("ROLE_LOGIN".equals(needRole)){ if (authentication instanceof AnonymousAuthenticationToken){ throw new AccessDeniedException("尚未登陆..."); }else { return; } } // Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); //判断用户角色是否为url所需的角色 for (GrantedAuthority authority : authorities) { if (authority.getAuthority().equals(needRole)){ return; } } } throw new AccessDeniedException("权限不足,请联系管理员"); } @Override public boolean supports(ConfigAttribute configAttribute) { return false; } @Override public boolean supports(Class<?> aClass) { return true; } }
Config类Configure这里新增
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/login", "/logout","/captcha") .permitAll() //除了上面所有请求都拦截 .anyRequest() .authenticated() //TODO --> 动态权限配置 .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O o) { o.setAccessDecisionManager(customDecisionManager); o.setSecurityMetadataSource(customFilter); return o; } }) .and() //禁用缓存 .headers() .cacheControl(); //添加jwt 登录授权过滤器 http .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); //添加自定义未授权登录结果返回 http.exceptionHandling() .accessDeniedHandler(accessDeniedHandler) .authenticationEntryPoint(authorizationEntryPoint); }
4.JsonFormat转换pattern
数据库timestamp, java localdatetime类型json转换
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai") private LocalDateTime createDate;
5.存储过程的创建和调用
-
存储过程就i是具有名字的一段代码,用来完成一个特定的功能
-
创建存储过程保存在数据库的数据字典中
CREATE #[指定当前用户] [DEFINER = {USER | CURRENT_USER}] PROCEDURE 存储过程名字 ({proc_parameter[...]}) proc_parameter: [IN | OUT } INOUT] para_name TYPE characteristic: COMMENT 'string' LANGUAGE SQL routine_body: valid SQL ROUTINE statement BEGIN [statement_list] END
MSQL存储过程的关键语法
DELIMITER $$ / //
声明存储过程
CREATE PROCEDURE demo_in_parameter (in p_in int)
变量赋值:
set @p_in = 1
调用过程
CALL sp_name[(传参)]
-
in: 表示调用者向过程传入值 可以是字面量或者变量
-
out: 返回的值
-
INOUT: 既可以传入又可以传出
简单创建
CREATE PROCEDURE GreetWolrd5() SELECT CONCAT (@greeting2, 'world') SET @greeting2 = 'Hello'; CALL GreetWolrd5();
6.xml中写递归查询案例
<resultMap id="BaseResultMap" type="com.ansel.server.entity.Department"> <id column="id" property="id" /> <result column="name" property="name" /> <result column="parentId" property="parentId" /> <result column="depPath" property="depPath" /> <result column="enabled" property="enabled" /> <result column="isParent" property="isParent" /> </resultMap> <resultMap id="DepartmentWithChildren" type="com.ansel.server.entity.Department" extends="BaseResultMap"> <collection property="children" ofType="com.ansel.server.entity.Department" select="com.ansel.server.mapper.DepartmentMapper.getAllDepartments" column="id"> <!-- 拿children再去调用递归的方法--> </collection> </resultMap> <select id="getAllDepartments" resultMap="DepartmentWithChildren"> select * from t_department where parentId = #{id} </select>
7.模糊查询
SELECT a.*, r.id AS rid, r.name AS rname, r.nameZh AS rnameZh FROM t_admin a LEFT JOIN t_admin_role ar ON a.id = ar.adminId LEFT JOIN t_role r ON r.id = ar.rid WHERE a.id != 1 AND a.name LIKE CONCAT('%',"淑",'%');
8.分页+多条件查询
1.配置类
package com.ansel.server.config; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyBatisPlusConfig { @Bean public PaginationInnerInterceptor paginationInnerInterceptor(){ return new PaginationInnerInterceptor(); } }
2.Service
package com.ansel.server.service.impl; import com.ansel.server.entity.Employee; import com.ansel.server.entity.EmployeeEc; import com.ansel.server.entity.RespPageBean; import com.ansel.server.mapper.EmployeeEcMapper; import com.ansel.server.mapper.EmployeeMapper; import com.ansel.server.service.IEmployeeEcService; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; /** * <p> * 服务实现类 * </p> * * @author ansel * @since 2023-03-02 */ @Service public class EmployeeEcServiceImpl extends ServiceImpl<EmployeeEcMapper, EmployeeEc> implements IEmployeeEcService { @Autowired private EmployeeMapper employeeMapper; @Override public RespPageBean getEmployeeByPage(Integer currentPage,Integer size, Employee employee, LocalDate[] beginDateScope) { Page<Employee> page = new Page<>(currentPage,size); IPage<Employee> employeeByPage = employeeMapper.getEmployeeByPage(page, employee, beginDateScope); RespPageBean respPageBean = new RespPageBean(employeeByPage.getTotal(),employeeByPage.getRecords()); return respPageBean; } }
3.mapper
@Mapper public interface EmployeeMapper extends BaseMapper<Employee> { IPage<Employee> getEmployeeByPage(Page<Employee> page, Employee employee, LocalDate[] beginDateScope); }
4.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ansel.server.mapper.EmployeeMapper"> <resultMap id="BaseResultMap" type="com.ansel.server.entity.Employee"> <id column="id" property="id"></id> <result column="name" property="name"></result> </resultMap> <resultMap id="EmployInfo" type="com.ansel.server.entity.Employee" extends="BaseResultMap"> <association property="nation" javaType="com.ansel.server.entity.Nation"> <id column="nid" property="id"></id> <result column="nname" property="name"></result> </association> <association property="politicsStatus" javaType="com.ansel.server.entity.PoliticsStatus"> <id column="pid" property="id"></id> <result column="pname" property="name"></result> </association> <association property="department" javaType="com.ansel.server.entity.Department"> <id column="did" property="id"></id> <result column="dname" property="name"></result> </association> <association property="joblevel" javaType="com.ansel.server.entity.Joblevel"> <id column="did" property="id"></id> <result column="dname" property="name"></result> </association> <association property="position" javaType="com.ansel.server.entity.Position"> <id column="posid" property="id"></id> <result column="posname" property="name"></result> </association> </resultMap> <!-- 分页获取所有员工--> <select id="getEmployeeByPage" resultMap="EmployInfo"> SELECT e.*, n.id AS nid, n.name AS nname, p.id AS pid, p.name AS pname, d.id AS did, d.name AS dname, j.id AS jid, j.name AS jname, pos.id AS posid, pos.name AS posname FROM t_employee e, t_nation n, t_politics_status p, t_department d, t_joblevel j, t_position pos WHERE e.nationId = n.`id` AND e.`politicId` = p.`id` AND e.`jobLevelId` = j.`id` AND e.posId = pos.`id` <if test="employee.name != null and employee.name != ''"> AND e.name LIKE CONCAT("%", #{employee.name}, '%') </if> <if test="employee.politicId != null"> AND e.`politicId`= #{employee.politicId} </if> <if test="employee.nationId != null"> AND e.`nationId`= #{employee.nationId} </if> <if test="employee.jobLevelId != null"> AND e.`jobLevelId`= #{employee.jobLevelId} </if> <if test="employee.posId != null"> AND e.`posId`= #{employee.posId} </if> <if test="employee.engageForm != null"> AND e.`engageForm`= #{employee.engageForm} </if> <if test="employee.departmentId != null"> AND e.`departmentId`= #{employee.departmentId} </if> <if test="beginDateScope != null and 2 == beginDateScope.length"> AND e.`beginDate` between #{beginDateScope[0]} and #{beginDateScope[1]} </if> ORDER BY e.`id` </select> </mapper>
9.LocalDateTime的一些方法
两个LocalDate的天数相差多少天
@Test public void testUntil() { LocalDate start = LocalDate.of(2023, 3, 5); LocalDate end = LocalDate.of(2023, 9, 1); long until = start.until(end, ChronoUnit.DAYS); System.out.println(until); }
10.发邮件 (注意: 实体类一定要序列化)
导包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
1.打开POP3/SMTP服务 2.启动类:
@Bean public Queue queue(){ return new Queue("mail.welcome"); }
3.controller类
@Autowired RabbitTemplate rabbitTemplate; @ApiOperation(value = "发邮件给员工") @GetMapping("/") @ResponseBody public RespPageBean mail() { //发送信息 Employee employee = new Employee(); employee.setName("ansel"); employee.setEmail("anxelswanz@163.com"); //一定要实现serializable接口 rabbitTemplate.convertAndSend("mail.welcome",employee); return null; }
4.MailReceiver
package com.ansel.server.mail; import com.ansel.server.entity.Employee; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.mail.MailProperties; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.util.Date; /** * @author Ansel Zhong * @title email * @Date 2023/3/6 * @Description 消息接收 */ @Component @Slf4j public class MailReceiver { private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class); @Autowired JavaMailSender javaMailSender; @Autowired private MailProperties properties; @Autowired private TemplateEngine templateEngine; private String DEFAULTEMAIL = "1035205314@qq.com"; @RabbitListener(queues = "mail.welcome") public void handler(Employee employee) throws MessagingException { // employee.setEmail("anxelswanz@163.com"); if (employee.getEmail() != null) { this.DEFAULTEMAIL = employee.getEmail(); } try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage); //发件人 helper.setFrom(properties.getUsername()); helper.setTo(DEFAULTEMAIL); helper.setSubject("入职欢迎"); helper.setSentDate(new Date()); Context context = new Context(); context.setVariable("name", employee.getName()); String mail = templateEngine.process("mail", context); helper.setText(mail, true); //发送邮件 javaMailSender.send(mimeMessage); } catch (MessagingException e) { LOGGER.error("邮件发送失败!--》{}", e.getMessage()); } catch (MailException e) { e.printStackTrace(); } } }
11.消息回调机制确保消息可靠性
上游发送消息,写入数据库,下游收到消息会发confirm信息,如果发送成功,则设置status为1,如果发送失败,则调用定时任务重复上述步骤,如果超过三次,就设置status为2
1.数据库
2.Entity
package com.ansel.server.entity; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.time.LocalDateTime; /** * <p> * * </p> * * @author ansel * @since 2023-03-02 */ @TableName("t_mail_log") public class MailLog implements Serializable { private static final long serialVersionUID = 1L; /** * 消息id */ private String msgId; /** * 接收员工id */ private Integer eid; /** * 状态(0:消息投递中 1:投递成功 2:投递失败) */ private Integer status; /** * 路由键 */ private String routeKey; /** * 交换机 */ private String exchange; /** * 重试次数 */ private Integer count; /** * 重试时间 */ private LocalDateTime tryTime; /** * 创建时间 */ private LocalDateTime createTime; /** * 更新时间 */ private LocalDateTime updateTime; public String getMsgId() { return msgId; } public void setMsgId(String msgId) { this.msgId = msgId; } public Integer getEid() { return eid; } public void setEid(Integer eid) { this.eid = eid; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status = status; } public String getRouteKey() { return routeKey; } public void setRouteKey(String routeKey) { this.routeKey = routeKey; } public String getExchange() { return exchange; } public void setExchange(String exchange) { this.exchange = exchange; } public Integer getCount() { return count; } public void setCount(Integer count) { this.count = count; } public LocalDateTime getTryTime() { return tryTime; } public void setTryTime(LocalDateTime tryTime) { this.tryTime = tryTime; } public LocalDateTime getCreateTime() { return createTime; } public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; } public LocalDateTime getUpdateTime() { return updateTime; } public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } @Override public String toString() { return "MailLog{" + "msgId=" + msgId + ", eid=" + eid + ", status=" + status + ", routeKey=" + routeKey + ", exchange=" + exchange + ", count=" + count + ", tryTime=" + tryTime + ", createTime=" + createTime + ", updateTime=" + updateTime + "}"; } }
3.Controller
@Autowired RabbitTemplate rabbitTemplate; @ApiOperation(value = "发邮件给员工") @GetMapping("/") @ResponseBody public RespPageBean mail() { //发送信息 Employee employee = new Employee(); employee.setName("ansel"); employee.setEmail("anxelswanz@163.com"); employee.setId(1); String msgID = UUID.randomUUID().toString(); MailLog mailLog = new MailLog(); mailLog.setMsgId(msgID); mailLog.setEid(employee.getId()); mailLog.setStatus(0); mailLog.setRouteKey(MailConstants.MAIL_ROUTING_KEY_NAME); mailLog.setExchange(MailConstants.MAIL_EXCHANGE_NAME); mailLog.setCount(0); mailLog.setTryTime(LocalDateTime.now().plusMinutes(MailConstants.MSG_TIME_OUT)); mailLog.setCreateTime(LocalDateTime.now()); mailLog.setUpdateTime(LocalDateTime.now()); mailLogMapper.insert(mailLog); //一定要实现serializable接口 rabbitTemplate.convertAndSend("MailConstants.MAIL_EXCHANGE_NAME", MailConstants.MAIL_ROUTING_KEY_NAME,employee, new CorrelationData(msgID)); return null; }
4.config
package com.ansel.server.config; import com.ansel.server.entity.MailConstants; import com.ansel.server.entity.MailLog; import com.ansel.server.service.IMailLogService; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author Ansel Zhong * @title email * @Date 2023/3/6 * @Description */ @Configuration @Slf4j public class RabbitMQConfig { @Autowired private CachingConnectionFactory cachingConnectionFactory; @Autowired private IMailLogService iMailLogService; @Bean public RabbitTemplate rabbitTemplate(){ RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory); //确认消息是否到达broker /** * data: 消息唯一标识 uuid * ack : 确认结果 * cause: 失败原因 */ rabbitTemplate.setConfirmCallback((data,ack,cause) -> { String msgId = data.getId(); if (ack){ log.info("{}成功", msgId); iMailLogService.update(new UpdateWrapper<MailLog>().set("status",1).eq("msgId", msgId)); }else { log.info("{}发送失败", msgId); } }); /** * 消息失败回调, 比如router步到queue * msg 消息主题 * repCode 响应码 * repText 响应描述 * exchange 交换机 * routingKey 路由键 */ rabbitTemplate.setReturnCallback((msg,repCode, repText, exchange, routingkey)->{ log.info("消息发送queue时失败"); }); return rabbitTemplate; } @Bean public Queue queue(){ return new Queue(MailConstants.MAIL_QUEUE_NAME); } @Bean public DirectExchange directExchange(){ return new DirectExchange(MailConstants.MAIL_EXCHANGE_NAME); } @Bean public Binding binding(){ return BindingBuilder.bind(queue()).to(directExchange()).with(MailConstants.MAIL_ROUTING_KEY_NAME); } }
5.config
# rabbitmq配置 rabbitmq: # 用户名 username: ansel # 密码 password: zrh # 服务器地址 host: 192.168.221.128 # 端口 port: 5672 listener: simple: #开启手动确认 acknowledge-mode: manual publisher-confirm-type: correlated publisher-returns: true
6.定时任务
package com.ansel.server.task; import com.ansel.server.entity.Employee; import com.ansel.server.entity.MailConstants; import com.ansel.server.entity.MailLog; import com.ansel.server.service.IEmployeeEcService; import com.ansel.server.service.IMailLogService; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.bytebuddy.asm.Advice; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.List; /** * @author Ansel Zhong * @title email * @Date 2023/3/6 * @Description 邮件发送服务定时任务 */ @Component public class MailTask { @Autowired private IMailLogService iMailLogService; @Autowired RabbitTemplate rabbitTemplate; /** * 十秒执行一次 */ @Scheduled(cron = "0/10 * * * * ?") public void mailTask(){ List<MailLog> list = iMailLogService.list(new QueryWrapper<MailLog>() .eq("status", 0).lt("tryTime", LocalDateTime.now())); list.forEach(mailLog -> { //如果重试超过三次就更新失败 if (mailLog.getCount() >= 3) { iMailLogService.update(new UpdateWrapper<MailLog>().set("status",2).eq("msgId", mailLog.getMsgId())); } iMailLogService.update(new UpdateWrapper<MailLog>().set("count",mailLog.getCount() + 1).set("updateTime",LocalDateTime.now().plusMinutes(MailConstants.MSG_TIME_OUT))); Employee employee = new Employee(); employee.setId(1); employee.setName("ansel"); employee.setEmail("anxelswanz@163.com"); rabbitTemplate.convertAndSend("MailConstants.MAIL_EXCHANGE_NAME",MailConstants.MAIL_ROUTING_KEY_NAME, employee,new CorrelationData(mailLog.getMsgId())); }); } }
7.消息接收
package com.ansel.server.mail; import com.ansel.server.entity.Employee; import com.ansel.server.entity.MailConstants; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.mail.MailProperties; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Component; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; import java.util.Date; /** * @author Ansel Zhong * @title email * @Date 2023/3/6 * @Description 消息接收 */ @Component @Slf4j public class MailReceiver { private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class); @Autowired JavaMailSender javaMailSender; @Autowired private MailProperties properties; @Autowired private TemplateEngine templateEngine; private String DEFAULTEMAIL = "1035205314@qq.com"; @RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME) public void handler(Employee employee) throws MessagingException { // employee.setEmail("anxelswanz@163.com"); if (employee.getEmail() != null) { this.DEFAULTEMAIL = employee.getEmail(); } try { MimeMessage mimeMessage = javaMailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage); //发件人 helper.setFrom(properties.getUsername()); helper.setTo(DEFAULTEMAIL); helper.setSubject("入职欢迎"); helper.setSentDate(new Date()); Context context = new Context(); context.setVariable("name", employee.getName()); String mail = templateEngine.process("mail", context); helper.setText(mail, true); //发送邮件 javaMailSender.send(mimeMessage); } catch (MessagingException e) { LOGGER.error("邮件发送失败!--》{}", e.getMessage()); } catch (MailException e) { e.printStackTrace(); } } }
12.WebSocket
1.websocket是html5开始提供的全双工通讯协议。 2.WebSocket使的客户端和服务器之间的数据交换变得简单,只需要一次握手。 3.最大的特点就是服务器可以主动向客户端推送消息,客户端也可以以主动向服务器推送消息。真正的双向数据传输。