eyb:创建邮件发送消息到员工账套功能实现(五)


目录:

(1)创建邮件发送服务项目

(2)邮箱发送功能实现 

(3)生产端可靠性投递方案介绍

(4)开启消息回调机制 

(5)生产端消息投递可靠性实现

(6)消费端幂等性操作

(7)工资账套功能实现

(8)获取所有员工账套

(9)员工账套功能实现


(1)创建邮件发送服务项目

邮件发送在公司使用非常频繁的,比如说新员工入职啊,我们会给新员工发送邮件,说一些基本信息工号啊、职称啊等,我们也要实现以下邮件服务,需要准备前期准备工作,去开通一个SMTP

使用163邮箱:

整个项目发送邮件用到了RubbitMQ消息队列,将发送的邮件交给RubbitMQ去处理

创建新项目用来发送邮件:

创建Maven 骨架:

 

pom.xml:加入依赖:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <artifactId>yeb</artifactId>
        <groupId>com.xxxx</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>yeb-mail</artifactId>



    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--rabbitmq依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!--mail 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <!--thymeleaf依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--server依赖-->
        <dependency>
            <groupId>com.xxxx</groupId>
            <artifactId>yun-server</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    </dependencies>


</project>

 application.xml配置文件:

server:
  port: 8082

spring:
  # 邮件配置
  mail:
    # 邮件服务器地址
    host: smtp.163.com
    # 协议smtp
    protocol: smtp
    # 编码格式
    default-encoding: UTF-8
    # 授权码(在邮箱开通时获取)
    password: CGDJPKQYLKUWXVYR
    # 发送者邮箱地址
    username: qq1142846803
    # 端口
    port: 25
  # rabbitmq配置
  rabbitmq:
    # 用户名
    username: guest
    # 密码
    password: guest
    # 服务器地址
    host: 192.168.23.129
    port: 5672
    listener:
      simple:
        acknowledge-mode: manual
  redis:
    timeout: 10000ms
    host: 192.168.23.129
    port: 6379
    database: 0 # 选择哪个库,默认0库
    lettuce:
      pool:
        max-active: 1024 # 最大连接数,默认 8
        max-wait: 10000ms # 最大连接阻塞等待时间,单位毫秒,默认 -1
        max-idle: 200 # 最大空闲连接,默认 8
        min-idle: 5

mail.html:thylemeaf模板

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>入职欢迎邮件</title>
</head>
<body>
    欢迎 <span th:text="${name}"></span> 加入 XXXX 大家庭,您的入职信息如下:
    <table border="1">
        <tr>
            <td>姓名</td>
            <td th:text="${name}"></td>
        </tr>
        <tr>
            <td>职位</td>
            <td th:text="${posName}"></td>
        </tr>
        <tr>
            <td>职称</td>
            <td th:text="${joblevelName}"></td>
        </tr>
        <tr>
            <td>部门</td>
            <td th:text="${departmentName}"></td>
        </tr>
    </table>
    <p>我们公司的工作忠旨是严格,创新,诚信,您的加入将为我们带来新鲜的血液,带来创新的思维,以及为
        我们树立良好的公司形象!希望在以后的工作中我们能够齐心协力,与时俱进,团结协作!同时也祝您在本公
        司,工作愉快,实现自己的人生价值!希望在未来的日子里,携手共进!</p>
</body>
</html>

创建启动类:

package com.xxxx.mail;

import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MailApplication {
    public static void main(String[] args) {
        SpringApplication.run(MailApplication.class,args);
    }
}

(2)邮箱发送功能实现 

在yun-server项目中pom.xml:加入rabbitmq依赖:

 将rabbitmq的配置复制到yun-server的配置文件中:

在EmployeeServiceImpl:在添加员工的方法中,添加发送信息:

  

在yeb-mail:创建

MailReceiver:

package com.xxxx.mail;

import com.rabbitmq.client.Channel;
import com.xxxx.server.pojo.Employee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
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.io.IOException;
import java.util.Date;

/**
 * 消息接收者
 *
 * @author zhanglishen
 */
@Component
public class MailReceiver {
    //准备日志到时候打印日志
    public static final Logger logger = LoggerFactory.getLogger(MailReceiver.class);

    @Autowired
    private JavaMailSender javaMailSender;
    @Autowired
    private MailProperties mailProperties;
    @Autowired
    private TemplateEngine templateEngine;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 邮件发送
     */
    @RabbitListener(queues = "mail.welcome") //监听
    public void handler(Employee employee) {

        MimeMessage msg = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(msg);

        try {
            //发件人
            helper.setFrom(mailProperties.getUsername());
            //收件人
            helper.setTo(employee.getEmail());
            //主题
            helper.setSubject("入职欢迎邮件");
            //发送日期
            helper.setSentDate(new Date());
            //内容
            Context context = new Context();
            context.setVariable("name", employee.getName());
            context.setVariable("posName", employee.getPosition().getName());
            context.setVariable("joblevelName", employee.getJoblevel().getName());
            context.setVariable("departmentName", employee.getDepartment().getName());
            String mail = templateEngine.process("mail", context);
            helper.setText(mail, true);
            //发送邮件
            javaMailSender.send(msg);
            logger.info("邮件发送成功");

        } catch (MessagingException e) {
            logger.error("邮件发送失败====>{}", e.getMessage());
        }
    }


}

 启动类:

package com.xxxx.mail;

//import com.xxxx.server.pojo.MailConstants;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MailApplication {
    public static void main(String[] args) {
        SpringApplication.run(MailApplication.class,args);
    }

    //创建队列
    @Bean
    public Queue queue(){
        return new Queue("mail.welcome");
    }
}

运行yun-server项目:添加员工里面,email更改成自己的邮箱

 

 数据库多了一个员工:

同时把邮件发送给对应的1142846803@qq.com

登录自己的qq邮箱:即发来一条信息

 (3)生产端可靠性投递方案介绍

上面使用RabbitMQ进行了邮箱的发送,既然项目中用到了RubbitMQ,它有它的优点,比如说:解耦、异步、流量消峰等等,既然我们要去用就要考虑额外的东西,比如说消息的可靠性,什么是消息的可靠性呢?从两方面去讲:第一个你怎么保证生产端的可靠性投递,就是确保生产端真真正正的投递队列,甚至给消费者消费了   第二个:就是消费者怎么去做逆等性的保证,就是我们用RabbitMQ无可避免的无法保证一条消息重复多发的情况,那么怎么保证我们只消费一条消息,那么重复多个的就去做丢弃或其他操作

第一个:生产端怎么去做的可靠性投递、从以下几点:

 1.去考虑消息的成功发出  2.RubbitMQ的节点成功的接收到了消息,也就是说队列成功的接收了消息 3.保证发送端能够收到发送节点的收到应答 

我们需要去完善消息的补偿机制:市面上主流的两种方案:

方案1:需要过多的操作数据库 

采用方案1:

(4)开启消息回调机制 

实现生产端的消息保障:消息落库-状态打标

首先看一下数据库的表:为消息准备的表:t_mail_log:

 

定义对应的常量:在pojo中创建爱你MailConstants类:

package com.xxxx.server.pojo;

/**
 * 消息状态
 */

public class MailConstants {
    //消息投递中
    public static final Integer DELIVERING = 0;
    //消息投递成功
    public static final Integer SUCCESS = 1;
    //消息投递失败
    public static final Integer FAILURE = 2;
    //最大重试次数
    public static final Integer MAX_TRY_COUNT = 3;
    //消息超时时间
    public static final Integer MSG_TIMEOUT = 1;
    //队列
    public static final String MAIL_QUEUE_NAME = "mail.queue";
    //交换机
    public static final String MAIL_EXCHANGE_NAME = "mail.exchange";
    //路由键
    public static final String MAIL_ROUTING_KEY_NAME = "mail.routing.key";
}

 修改添加员工的方法:在EmployeeServiceImpl:

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.EmployeeMapper;
import com.xxxx.server.mapper.MailLogMapper;
import com.xxxx.server.pojo.*;
import com.xxxx.server.service.IEmployeeService;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MailLogMapper mailLogMapper;

    //获取所有员工(分页)
    @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;
    }

    //获取工号(最大)
    @Override
    public RespBean maxWorkId() {
        List<Map<String, Object>> maps = employeeMapper.selectMaps(new QueryWrapper<Employee>().select("max(workID)"));

        return RespBean.success(null,String.format("%08d", Integer.parseInt( maps.get(0).get("max(workID)").toString())+1));
    }

    //添加员工
    @Override
    public RespBean addEmp(Employee employee) {
        //处理合同,保留两位小数
        //开始时间
        LocalDate beginContract = employee.getBeginContract();
        //结束时间
        LocalDate endContract = employee.getEndContract();
        //计算差值
        long days = beginContract.until(endContract, ChronoUnit.DAYS);
        //设置保留两位小数
        DecimalFormat decimalFormat=new DecimalFormat("##.00");
        //设置
        employee.setContractTerm(Double.parseDouble(decimalFormat.format(days/365.00)));
        if (1==employeeMapper.insert(employee)){

            //获取员工对象
            Employee emp=employeeMapper.getEmployee(employee.getId()).get(0);

            //数据库记录发送的消息
            String msgId = UUID.randomUUID().toString();
            MailLog mailLog = new MailLog();
            mailLog.setMsgId(msgId);
            mailLog.setEid(employee.getId());//员工id
            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_TIMEOUT));//重试时间
            mailLog.setCreateTime(LocalDateTime.now());
            mailLog.setUpdateTime(LocalDateTime.now());
            mailLogMapper.insert(mailLog);

            //发送信息
            //路由key
            rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,MailConstants.MAIL_ROUTING_KEY_NAME,emp,new CorrelationData(msgId));
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败!");
    }

    //查询员工
    @Override
    public List<Employee> getEmployee(Integer id) {
        return employeeMapper.getEmployee(id);
    }
}

 修改yeb-mail:项目接收消息

修改:启动类创建的队列,改为使用常量

package com.xxxx.mail;

import com.xxxx.server.pojo.MailConstants;
import org.springframework.amqp.core.Queue;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MailApplication {
    public static void main(String[] args) {
        SpringApplication.run(MailApplication.class,args);
    }

    //创建队列
    @Bean
    public Queue queue(){
        return new Queue(MailConstants.MAIL_QUEUE_NAME);
    }
}

MailReceiver:监听也改为常量 

 

在yun-server:配置一下RubbitMQ,在发送里面配置:编写配置类:

RubbitMQConfig:

package com.xxxx.server.config;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.xxxx.server.pojo.MailConstants;
import com.xxxx.server.pojo.MailLog;
import com.xxxx.server.service.IMailLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;

/**
 * RabbitMQ配置类
 */

@Configuration
public class RabbitMQConfig {
    //打印日志
    public static final Logger logger = LoggerFactory.getLogger(RabbitMQConfig.class);
    @Autowired
    private CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    private IMailLogService mailLogService;

    @Bean
    public RabbitTemplate rabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
        /**
         * 消息确认回调,确认消息是否到达broker
         * data:消息唯一标识
         * ack:确认结果
         * cause:失败原因
         */
        rabbitTemplate.setConfirmCallback((data,ack,cause)->{
            String msgId = data.getId();
            System.out.println("RabbitMQConfig: msgId = " + msgId);
            if (ack){
                logger.info("{}==========>消息发送成功",msgId);
                mailLogService.update(new UpdateWrapper<MailLog>().set("status",1).eq("msgId",msgId));
            }else {
                logger.info("{}==========>消息发送失败",msgId);
            }
        });
        /**
         * 消息失败回调,比如router不到queue时回调
         * msg:消息主题
         * repCode:响应码
         * repText:响应描述* exchange:交换机
         * * routingKey:路由键
         * */
        rabbitTemplate.setReturnCallback((msg,repCode,repText,exchange,routingKey)->{
            //这里只是打印结果不做处理
            logger.info("{}=======================>消息发送到queue时失败",msg.getBody());
        });
        return rabbitTemplate;
    }

    //创建队列
    @Bean
    public Queue queue() {
        return new Queue(MailConstants.MAIL_QUEUE_NAME, true);
    }

    //创建交换机
    @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)生产端消息投递可靠性实现

上面消息确认回调,失败回调已经完成了,那我们数据库中有两个状态,一个是投递中,一个是确认成功,接下来我们来写重试:重新去投递我们的消息,这时候我们用到定时任务了

创建类MailTask:

package com.xxxx.server.task;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.xxxx.server.pojo.Employee;
import com.xxxx.server.pojo.MailConstants;
import com.xxxx.server.pojo.MailLog;
import com.xxxx.server.service.IEmployeeService;
import com.xxxx.server.service.IMailLogService;
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 zahnglishen
 * @since 1.0.0
 */
@Component
public class MailTask {
    @Autowired
    private IMailLogService mailLogService;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private IEmployeeService employeeService;

    /**
     * 邮件发送定时任务
     * 10秒一次
     */
    @Scheduled(cron = "0/10 * * * * ?")
    public void mailTask() {
        //状态为0且重试时间小于当前时间的才需要重新发送
        List<MailLog> list = mailLogService.list(new QueryWrapper<MailLog>().eq("status", 0).lt("tryTime", LocalDateTime.now()));
        list.forEach(mailLog -> {
            //重试次数超过3次,更新为投递失败,不再重试
            if (3 <= mailLog.getCount()) {
                mailLogService.update(new UpdateWrapper<MailLog>().set("status", 2).eq("msgId", mailLog.getMsgId()));
            }
            //更新重试次数,更新时间,重试时间
            mailLogService.update(new UpdateWrapper<MailLog>().set("count", mailLog.getCount() + 1).set("updateTime", LocalDateTime.now())
                    .set("tryTime", LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT)).eq("msgId", mailLog.getMsgId()));
            Employee employee = employeeService.getEmployee(mailLog.getEid()).get(0);
            System.out.println("MailTask: employee = " + employee);
            System.out.println("MailTask: msgId: " + mailLog.getMsgId());
            //发送消息
            rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,MailConstants.MAIL_ROUTING_KEY_NAME,employee,new CorrelationData(mailLog.getMsgId()));
        });
    }

}

在主启动类:加入注解:开启定时任务

package com.xxxx.server;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/*
* 启动类
* */
@SpringBootApplication
@MapperScan("com.xxxx.server.mapper")
@EnableScheduling //开启定时任务
public class YebApplication {
    public static void main(String[] args) {
        SpringApplication.run(YebApplication.class,args);
    }
}

从新启动项目发送:

成功发送消息之后:在生产端绑定了交换机,路由键

 

 数据库表多了一条数据:

测试失败:

把EmployeeServiceImpl:添加发放中发送消息的交换机改错,换成字符串:

现在交换机不一样了,就对应不上对应的路由的名字,也找不到队列,第一次就会发送失败,发送失败会经过定时任务,定时任务的交换机是对的就能找到正确的队列,就能重试成功

在次添加数据:

第一次消息发送失败:

数据库:status是0

 会走定时任务:重新发送:

数据库:

重试发送成功状态:status变成1 了 count=1重试了一次 

 把重试也发送失败:更改交换机的名字:

再次添加员工:

数据库:

 会重试好几次都是失败:

 数据库:重试了4次,没有发送成功,把status改成2

 (6)消费端幂等性操作

生产端对应的解决了,现在处理消费端,消费端需要处理的就是幂等性,什么是幂等性呢:就是说我有可能会同时投递id是一样的消息,就是说一条消息呢会投递多次,这个在RubbitMQ中只能说尽量的去避免,比如说上面做的生产端的消息投递,用了定时任务,打个比方我现在发了一条消息,消息处理投递中,刚发完我们这里还没监听到回调的时候,还没去变更状态的时候,我们的定时任务就抓到这条消息,它的状态是0,它会重新走定时任务重新发送,这个时候会发送两次,消息是一样的id也是一样的,这样有两条消息,我们消费端是消费一次还是两次呢?所以需要做幂等性的校验,遇到重复的提交,消费者遇到重复的消息进来的时候消费者如何进行幂等性的校验,让我只会消费一条消息:市面上有两种幂等性操作:第一种是惟一的id+指纹码 惟一的id用主键的自增id  指纹码用MMSjid,它也是一个UUid

首先消息过来的时候呢,我会把这个消息消费之前先去数据库查一下,根据自增id+mmsjid去数据库查有没有这条数据,如果有数据,说说明已经消费过了,如果没有这条数据,那我才去进行消费,消费的同时呢会把这条数据放到数据库里面,同时数据库里面会有专门的字段去放唯一id+指纹码的

第二种;是redis的原子性,它也需要考虑问题,比如业务数据你要不要入库呢,要入库的话就要考虑数据库和缓存如何去做原子性,Redis可以做原子性,Reids和数据库合在一起如何去做原子性的一个判断,如果不需要入库,就需要考虑如何设置定时同步的一个策略,这也是需要考虑的

我们的项目使用第一种:

我们不使用mysql,因为有写入比拟,我们使用缓存,使用缓存也不去高原子性,就是正常的把mmsjid就是UUid生成的惟一的存到Redis里面去,每次取消费之前呢,去redis去查询一下有没有这个id,如果有这个id,说说明已经消费过了,如果没有这条id,那我正常的去消费,消费的同时把数据存到Redis里面去,需要加入Redis依赖,RubbitMQ 需要手动确认了,不能用以前自定确认了

在yeb-mail:配置文件加入 

更改MailReceiver:进行幂等性判断

package com.xxxx.mail;

import com.rabbitmq.client.Channel;
import com.xxxx.server.pojo.Employee;

import com.xxxx.server.pojo.MailConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
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.io.IOException;
import java.util.Date;

/**
 * 消息接收者
 *
 * @author zhanglishen
 */
@Component
public class MailReceiver {
    //准备日志到时候打印日志
    public static final Logger logger = LoggerFactory.getLogger(MailReceiver.class);

    @Autowired
    private JavaMailSender javaMailSender;
    @Autowired
    private MailProperties mailProperties;
    @Autowired
    private TemplateEngine templateEngine;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 邮件发送
     */
    @RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME) //监听
    public void handler(Message message,Channel channel) {
        Employee employee = (Employee) message.getPayload();
        MessageHeaders headers = message.getHeaders();
        //消息序号
        long tag = (long) headers.get(AmqpHeaders.DELIVERY_TAG);
        String msgId = (String) headers.get("spring_returned_message_correlation");

        //使用hash
        HashOperations hashOperations = redisTemplate.opsForHash();



        try {
            if (hashOperations.entries("mail_log").containsKey(msgId)) {
                //redis中包含key,说明消息已经被消费
                logger.error("消息已经被消费========>{}", msgId);
                /**
                 * 手动确认消息
                 * tag:消息序号
                 * multiple:是否多条
                 */
                channel.basicAck(tag, false);
                return;
            }

            MimeMessage msg = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);
            //发件人
            helper.setFrom(mailProperties.getUsername());
            //收件人
            helper.setTo(employee.getEmail());
            //主题
            helper.setSubject("入职欢迎邮件");
            //发送日期
            helper.setSentDate(new Date());
            //内容
            Context context = new Context();
            context.setVariable("name", employee.getName());
            context.setVariable("posName", employee.getPosition().getName());
            context.setVariable("joblevelName", employee.getJoblevel().getName());
            context.setVariable("departmentName", employee.getDepartment().getName());
            String mail = templateEngine.process("mail", context);
            helper.setText(mail, true);
            //发送邮件
            javaMailSender.send(msg);
            logger.info("邮件发送成功");
            //将消息id存入redis
            hashOperations.put("mail_log", msgId, "OK");
            System.out.println("MailReceiver: redis---> msgId = " + msgId);
            //手动确认消息
            channel.basicAck(tag, false);

        } catch (Exception e) {
            /**
             * 手动确认消息
             * tag:消息序号
             * multiple:是否多条
             * requeue:是否退回到队列
             */
            try {
                channel.basicNack(tag, false, true);
            } catch (IOException ioException) {
                //ioException.printStackTrace();
                logger.error("邮件确认失败=====>{}", ioException.getMessage());
            }
            logger.error("邮件发送失败====>{}", e.getMessage());
        }
    }


}

 测试把:UUid写死,发送两条数据

添加数据:

 

 redis客户端:有了数据

 删掉数据库刚才存入的数据,再一次添加数据:

Redis已经有了数据:报消息已经被消费 ,不会进行2次消费,会给你这样的一个提示

 (7)工资账套功能实现

在Salary类加入时间格式化:

 SalaryController:

package com.xxxx.server.controller;


import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.Salary;
import com.xxxx.server.service.ISalaryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Api(tags = "SalaryController")
@RestController
@RequestMapping("/salary/sob")
public class SalaryController {
    @Autowired
    private ISalaryService salaryService;

    @ApiOperation(value = "获取所有工资套账")
    @GetMapping("/")
    public List<Salary> getAllSalary(){
        return salaryService.list();
    }

    @ApiOperation(value = "添加工资套账")
    @PostMapping("/")
    public RespBean addSalary(@RequestBody Salary salary){
        salary.setCreateDate(LocalDateTime.now());
        if (salaryService.save(salary)){
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败");
    }

    @ApiOperation(value = "删除工资套账")
    @DeleteMapping("/{id}")
    public RespBean deleteSalary(@PathVariable Integer id){
        if (salaryService.removeById(id)){
            return RespBean.success("删除成功");
        }
        return RespBean.error("删除失败");
    }

    @ApiOperation(value = "更新工资套账")
    @PutMapping("/")
    public RespBean updateSalary(@RequestBody Salary salary){
        if (salaryService.updateById(salary)){
            return RespBean.success("更新成功");
        }
        return RespBean.error("更新失败");
    }

}

 

获取所有工资账套:

添加工资账套:

 数据库t_salary表:

第5条是新加的:

更新工资账套:

把刚新加的数据基础工资改为6000 bonus:600 

 数据库:

删除:

 数据库:

在员工表t_employee 里面有字段salaryId工资账套id,我们现在的工资账套,也就是给不同的员工设置基本的工资,相当于工资模板,你这个员工是这个工资模板,那个员工是那个工资模板,然后再进行微调,所里t_employee涉及到了外键

 接下来去讲员工账套,也就是一个员工对应着一个工资账套,这样的一个功能

(8)获取所有员工账套

员工表关联一个外键id salaryid,就有对应的工资,什么是员工账套呢,也就是说你这个员工对应的工资模板是什么,就给你法多少工资,这就用到外键的查询了

在controller目录下创建:SalarySobCfgController类:

package com.xxxx.server.controller;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.xxxx.server.pojo.Employee;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.RespPageBean;
import com.xxxx.server.pojo.Salary;
import com.xxxx.server.service.IEmployeeService;
import com.xxxx.server.service.ISalaryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 员工套账
 *
 * @author zhanglishen
 */

@Api(tags = "SalarySobCfgController")
@RequestMapping("/salary/sobcfg")
@RestController
public class SalarySobCfgController {
    @Autowired
    private ISalaryService salaryService;
    @Autowired
    private IEmployeeService employeeService;

    @ApiOperation(value = "获取所有员工套账")
    @GetMapping("/")   //用到分页需要返回:RespPageBean
    public RespPageBean getEmployeeWithSalary(@RequestParam(defaultValue = "1") Integer currentPage,@RequestParam(defaultValue = "10") Integer size){
        return employeeService.getEmployeeWithSalary(currentPage,size);
    }

    @ApiOperation(value = "获取所有工资套账")
    @GetMapping("/salaries")
    public List<Salary> getAllSalary(){
        return salaryService.list();
    }

    @ApiOperation(value = "更新员工套账")
    @PutMapping("/")
    public RespBean updateEmployeeSalary(Integer eid,Integer sid){
        if (employeeService.update(new UpdateWrapper<Employee>().set("salaryId",sid).eq("id",eid))){
            return RespBean.success("更新成功");
        }
        return RespBean.error("更新失败");
    }
}

IEmployeeService:接口实现方法获取所有工资账套

package com.xxxx.server.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.xxxx.server.pojo.Employee;
import com.xxxx.server.pojo.RespBean;
import com.xxxx.server.pojo.RespPageBean;

import java.time.LocalDate;
import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface IEmployeeService extends IService<Employee> {

    //获取所有员工(分页)
    RespPageBean getEmployeeByPage(Integer currentPage, Integer size, Employee employee, LocalDate[] beginDateScope);

    //获取工号(最大)
    RespBean maxWorkId();

    //添加员工
    RespBean addEmp(Employee employee);

    //查询员工
    List<Employee> getEmployee(Integer id);

    //获取所有员工账套
    RespPageBean getEmployeeWithSalary(Integer currentPage, Integer size);
}

实现类:

package com.xxxx.server.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xxxx.server.mapper.EmployeeMapper;
import com.xxxx.server.mapper.MailLogMapper;
import com.xxxx.server.pojo.*;
import com.xxxx.server.service.IEmployeeService;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private MailLogMapper mailLogMapper;

    //获取所有员工(分页)
    @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;
    }

    //获取工号(最大)
    @Override
    public RespBean maxWorkId() {
        List<Map<String, Object>> maps = employeeMapper.selectMaps(new QueryWrapper<Employee>().select("max(workID)"));

        return RespBean.success(null,String.format("%08d", Integer.parseInt( maps.get(0).get("max(workID)").toString())+1));
    }

    //添加员工
    @Override
    public RespBean addEmp(Employee employee) {
        //处理合同,保留两位小数
        //开始时间
        LocalDate beginContract = employee.getBeginContract();
        //结束时间
        LocalDate endContract = employee.getEndContract();
        //计算差值
        long days = beginContract.until(endContract, ChronoUnit.DAYS);
        //设置保留两位小数
        DecimalFormat decimalFormat=new DecimalFormat("##.00");
        //设置
        employee.setContractTerm(Double.parseDouble(decimalFormat.format(days/365.00)));
        if (1==employeeMapper.insert(employee)){

            //获取员工对象
            Employee emp=employeeMapper.getEmployee(employee.getId()).get(0);

            //数据库记录发送的消息
            String msgId = UUID.randomUUID().toString();
            MailLog mailLog = new MailLog();
            mailLog.setMsgId(msgId);
            mailLog.setEid(employee.getId());//员工id
            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_TIMEOUT));//重试时间
            mailLog.setCreateTime(LocalDateTime.now());
            mailLog.setUpdateTime(LocalDateTime.now());
            mailLogMapper.insert(mailLog);

            //发送信息
            //路由key
            rabbitTemplate.convertAndSend(MailConstants.MAIL_QUEUE_NAME,MailConstants.MAIL_ROUTING_KEY_NAME,emp,new CorrelationData(msgId));
            return RespBean.success("添加成功");
        }
        return RespBean.error("添加失败!");
    }

    //查询员工
    @Override
    public List<Employee> getEmployee(Integer id) {
        return employeeMapper.getEmployee(id);
    }

    //获取所有员工账套
    @Override
    public RespPageBean getEmployeeWithSalary(Integer currentPage, Integer size) {
        //开启分页
        Page<Employee> page=new Page<>(currentPage,size);
        IPage<Employee> employeeIPage=employeeMapper.getEmployeeWithSalary(page);
        RespPageBean respPageBean=new RespPageBean(employeeIPage.getTotal(),employeeIPage.getRecords());
        return respPageBean;
    }
}

 EmployeeMapper:接口:

package com.xxxx.server.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xxxx.server.pojo.Employee;
import org.apache.ibatis.annotations.Param;

import java.time.LocalDate;
import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author zhanglishen
 * @since 2022-08-05
 */
public interface EmployeeMapper extends BaseMapper<Employee> {

    //获取所有员工分页
    IPage<Employee> getEmployeeByPage(Page<Employee> page, @Param("employee") Employee employee,@Param("beginDateScope") LocalDate[] beginDateScope);

    //查询员工
    List<Employee> getEmployee(Integer id);

    //获取所有员工账套
    IPage<Employee> getEmployeeWithSalary(Page<Employee> page);
}

Employee类加上工资账套属性: 

 EmployeeMapper.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.xxxx.server.mapper.EmployeeMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.xxxx.server.pojo.Employee">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="gender" property="gender" />
        <result column="birthday" property="birthday" />
        <result column="idCard" property="idCard" />
        <result column="wedlock" property="wedlock" />
        <result column="nationId" property="nationId" />
        <result column="nativePlace" property="nativePlace" />
        <result column="politicId" property="politicId" />
        <result column="email" property="email" />
        <result column="phone" property="phone" />
        <result column="address" property="address" />
        <result column="departmentId" property="departmentId" />
        <result column="jobLevelId" property="jobLevelId" />
        <result column="posId" property="posId" />
        <result column="engageForm" property="engageForm" />
        <result column="tiptopDegree" property="tiptopDegree" />
        <result column="specialty" property="specialty" />
        <result column="school" property="school" />
        <result column="beginDate" property="beginDate" />
        <result column="workState" property="workState" />
        <result column="workID" property="workID" />
        <result column="contractTerm" property="contractTerm" />
        <result column="conversionTime" property="conversionTime" />
        <result column="notWorkDate" property="notWorkDate" />
        <result column="beginContract" property="beginContract" />
        <result column="endContract" property="endContract" />
        <result column="workAge" property="workAge" />
        <result column="salaryId" property="salaryId" />
    </resultMap>

    <resultMap id="EmployeeInfo" type="com.xxxx.server.pojo.Employee" extends="BaseResultMap">
        <association property="nation" javaType="com.xxxx.server.pojo.Nation">
            <id column="nid" property="id" />
            <result column="nname" property="name" />
        </association>
        <association property="politicsStatus" javaType="com.xxxx.server.pojo.PoliticsStatus">
            <id column="pid" property="id" />
            <result column="pname" property="name" />
        </association>
        <association property="department" javaType="com.xxxx.server.pojo.Department">
            <id column="did" property="id" />
            <result column="dname" property="name" />
        </association>
        <association property="joblevel" javaType="com.xxxx.server.pojo.Joblevel">
            <id column="jid" property="id" />
            <result column="jname" property="name" />
        </association>
        <association property="position" javaType="com.xxxx.server.pojo.Position">
            <id column="posid" property="id" />
            <result column="posname" property="name" />
        </association>
    </resultMap>

    <resultMap id="EmployeeWithSalary" type="com.xxxx.server.pojo.Employee" extends="BaseResultMap">
        <association property="salary" javaType="com.xxxx.server.pojo.Salary">
            <id column="sid" property="id"/>
            <result column="sbasicSalary" property="basicSalary"/>
            <result column="sbonus" property="bonus"/>
            <result column="slunchSalary" property="lunchSalary"/>
            <result column="strafficSalary" property="trafficSalary"/>
            <result column="sallSalary" property="allSalary"/>
            <result column="spensionBase" property="pensionBase"/>
            <result column="spensionPer" property="pensionPer"/>
            <result column="smedicalBase" property="medicalBase"/>
            <result column="smedicalPer" property="medicalPer"/>
            <result column="saccumulationFundBase"
                    property="accumulationFundBase"/>
            <result column="saccumulationFundPer"
                    property="accumulationFundPer"/>
            <result column="sname" property="name"/>
        </association>
        <association property="department" javaType="com.xxxx.server.pojo.Department">
            <result column="dname" property="name"/>
        </association>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, gender, birthday, idCard, wedlock, nationId, nativePlace, politicId, email, phone, address, departmentId, jobLevelId, posId, engageForm, tiptopDegree, specialty, school, beginDate, workState, workID, contractTerm, conversionTime, notWorkDate, beginContract, endContract, workAge, salaryId
    </sql>

    <!--获取所有员工(分页)-->
    <select id="getEmployeeByPage" resultMap="EmployeeInfo">
        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.departmentId = d.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.departmentId != null">
            AND e.departmentId = #{employee.departmentId}
        </if>
        <if test="null != employee.engageForm and '' != employee.engageForm">
            AND e.engageForm = #{employee.engageForm}
        </if>
        <if test="null != beginDateScope and 2 == beginDateScope.length">
            AND e.beginDate between #{beginDateScope[0]} and #{beginDateScope[1]}
        </if>
        ORDER BY e.id
    </select>

    <!--查询员工-->
    <select id="getEmployee" resultMap="EmployeeInfo">
        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.departmentId = d.id
        and e.posId = pos.id
        <if test="null != id">
            and e.id = #{id}
        </if>
        order by e.id
    </select>

    <!--获取所有员工套账-->
    <select id="getEmployeeWithSalary" resultMap="EmployeeWithSalary">
        SELECT
            e.*,
            td.`name` as dname,
            s.id AS sid,
            s.`name` AS sname,
            s.basicSalary AS sbasicSalary,
            s.trafficSalary AS strafficSalary,
            s.lunchSalary AS slunchSalary,
            s.bonus AS sbonus,
            s.allSalary AS sallSalary,
            s.pensionPer AS spensionPer,
            s.pensionBase AS spensionBase,
            s.medicalPer AS smedicalPer,
            s.medicalBase AS smedicalBase,
            s.accumulationFundPer AS saccumulationFundPer,
            s.accumulationFundBase AS saccumulationFundBase
        FROM
            t_employee as e
            LEFT JOIN t_salary as s ON e.salaryId = s.id
            LEFT JOIN t_department as td ON e.departmentId = td.id
        ORDER BY e.id
    </select>

</mapper>

(9)员工账套功能实现

测试:

获取所有员工账套 

 

更新员工账套:

 更新完之后:salaryid变为1了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵俺第一专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值