配置:
server:
port: 8084
#邮件相关配置
spring:
application:
name: subtlechat-mailserver
mail:
host: smtp.qq.com
protocol: smtp
default-encoding: UTF-8
#邮箱名
username:
#申请的授权码
password:
port: 587
properties.mail.stmp.socketFactory.class: javax.net.ssl.SSLSocketFactory
properties.mail.debug: true
#RabbitMQ的配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /subtlechat
# 如果rabbitmq中没有创建这个名字的虚拟主机会报错
#开启手动确认是否消息消费成功
listener:
simple:
acknowledge-mode: manual
prefetch: 100
#Redis的配置
redis:
database: 0
password: redis
host:
port: 6379
rabbitmq配置:
/**
* @author Hai
* @date 2020/10/1 - 18:32
*/
@Configuration
public class RabbitMQConfig {
@Value("${mail.exchange:mail-exchange}")
private String mailExchange;
@Value("${mail.queue.verifyCode:mail-queue-verifyCode}")
private String mailQueueVerifyCode;
@Value("${mail.route.verifyCode:mail-route-verifyCode}")
private String mailRouteVerifyCode;
@Value("${mail.queue.feedback:mail-queue-feedback}")
private String mailQueueFeedback;
@Value("${mail.route.feedback:mail-route-feedback}")
private String mailRouteFeedback;
@Bean
DirectExchange mailExchange(){
return new DirectExchange(mailExchange,true,false);
}
/**
* 验证码消息队列
* @return
*/
@Bean
Queue mailQueueVerifyCode(){
return new Queue(mailQueueVerifyCode,true);
}
@Bean
Binding mailQueueVerifyCodeBinding(){
return BindingBuilder.bind(mailQueueVerifyCode()).to(mailExchange()).with(mailRouteVerifyCode);
}
}
}
自定义rabbitmqtemplate:
/**
* 自定义消息发送RabbitTemplate
* @author Hai
* @date 2020/10/2 - 14:35
*/
@Configuration
public class RabbitMQConfig {
public static final Logger LOGGER= LoggerFactory.getLogger(RabbitMQConfig.class);
@Autowired
CachingConnectionFactory cachingConnectionFactory;
@Autowired
MailSendLogService mailSendLogService;
@Bean
public RabbitTemplate rabbitTemplate(){
RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
//成功投递消息到Broker交换机站点的回调函数
rabbitTemplate.setConfirmCallback((data,ack,cause)->{
String msgId = data.getId();
if(ack){
LOGGER.info(msgId+"消息发送成功");
//修改数据库中的记录,消息发送成功,将status设为1
mailSendLogService.updateMailSendLogStatus(msgId, MailConstants.SUCCESS);
}else{
LOGGER.error(msgId+"消息发送失败!");
}
});
//消息投递到Queue队列失败的回调函数
rabbitTemplate.setReturnCallback((msg,repCode,repText,exchange,routingKey)->{
LOGGER.error(msg.getBody()+"----消息从交换机投递到队列失败!\n错误原因:"+repText);
LOGGER.error("发送错误的交换机:"+exchange+",发生错误的路由key:"+routingKey);
});
return rabbitTemplate;
}
}
验证码工具类
/**
* 生成验证码的工具类
*/
public class VerificationCode {
private int width = 100;// 生成验证码图片的宽度
private int height = 30;// 生成验证码图片的高度
private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" };
private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色
private Random random = new Random();
private String codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private String text;// 记录随机字符串
/**
* 获取一个随意颜色
*
* @return
*/
private Color randomColor() {
int red = random.nextInt(150);
int green = random.nextInt(150);
int blue = random.nextInt(150);
return new Color(red, green, blue);
}
/**
* 获取一个随机字体
*
* @return
*/
private Font randomFont() {
String name = fontNames[random.nextInt(fontNames.length)];
int style = random.nextInt(4);
int size = random.nextInt(5) + 24;
return new Font(name, style, size);
}
/**
* 获取一个随机字符
*
* @return
*/
private char randomChar() {
return codes.charAt(random.nextInt(codes.length()));
}
/**
* 创建一个空白的BufferedImage对象
*
* @return
*/
private BufferedImage createImage() {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setColor(bgColor);// 设置验证码图片的背景颜色
g2.fillRect(0, 0, width, height);
return image;
}
public BufferedImage getImage() {
BufferedImage image = createImage();
Graphics2D g2 = (Graphics2D) image.getGraphics();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
String s = randomChar() + "";
sb.append(s);
g2.setColor(randomColor());
g2.setFont(randomFont());
float x = i * width * 1.0f / 4;
g2.drawString(s, x, height - 8);
}
this.text = sb.toString();
drawLine(image);
return image;
}
/**
* 绘制干扰线
*
* @param image
*/
private void drawLine(BufferedImage image) {
Graphics2D g2 = (Graphics2D) image.getGraphics();
int num = 5;
for (int i = 0; i < num; i++) {
int x1 = random.nextInt(width);
int y1 = random.nextInt(height);
int x2 = random.nextInt(width);
int y2 = random.nextInt(height);
g2.setColor(randomColor());
g2.setStroke(new BasicStroke(1.5f));
g2.drawLine(x1, y1, x2, y2);
}
}
public String getText() {
return text;
}
public static void output(BufferedImage image, OutputStream out) throws IOException {
ImageIO.write(image, "JPEG", out);
}
}
处理发送验证码的消息
package top.javahai.subtlechat.mail.receiver;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.impl.AMQImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Date;
/**
* 处理发送验证码的消息,保证消息不会重复消费
* @author Hai
* @date 2020/10/2 - 23:25
*/
@Component
public class VerifyCodeReceiver {
@Autowired
JavaMailSender javaMailSender;
@Autowired
StringRedisTemplate redisTemplate;
private static final Logger LOGGER= LoggerFactory.getLogger(VerifyCodeReceiver.class);
@RabbitListener(queues = "${mail.queue.verifyCode:mail-queue-verifyCode}")
public void getMessage(Message message, Channel channel) throws IOException {
//获取消息内容
String code = message.getPayload().toString();
//获取消息头,消息标志tag
MessageHeaders headers = message.getHeaders();
Long tag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
//获取消息ID
String msgId = (String) headers.get("spring_returned_message_correlation");
LOGGER.info("【"+msgId+"】-正在处理的消息");
//查看消息是否已消费
if (redisTemplate.opsForHash().entries("mail_log").containsKey(msgId)){
//手动确认消息已消费
channel.basicAck(tag,false);
LOGGER.info("【"+msgId+"】消息出现重复消费");
return;
}
//否则进行消息消费
try{
SimpleMailMessage msg = new SimpleMailMessage();
msg.setSubject("-验证码验证");
msg.setText("本次登录的验证码:"+code);
msg.setFrom("发送者的邮箱地址");
msg.setSentDate(new Date());
msg.setTo("接受者的邮箱地址");
javaMailSender.send(msg);
//消息发送成功,将id放到redis中,不能这样put
//redisTemplate.opsForHash().entries("mail_log").put(msgId,code);
redisTemplate.opsForHash().put("mail_log",msgId,code);
//确认消息消费成功
channel.basicAck(tag,false);
}catch (Exception e){
//不批量处理,将消息重新放回到队列中
channel.basicNack(tag,false,true);
LOGGER.info("【"+msgId+"】消息重新放回到了队列中");
e.printStackTrace();
}
}
}
controller层
@RestController
public class LoginController {
/**
* 获取验证码图片写到响应的输出流中,保存验证码到session
* @param response
* @param session
* @throws IOException
*/
@GetMapping("/verifyCode")
public void getVerifyCode(HttpServletResponse response, HttpSession session) throws IOException {
VerificationCode code = new VerificationCode();
BufferedImage image = code.getImage();
String text = code.getText();
session.setAttribute("verify_code",text);
VerificationCode.output(image,response.getOutputStream());
}
@Autowired
VerifyCodeService verifyCodeService;
/**
* 获取邮箱验证码,并保存到本次会话
* @param session
*/
@GetMapping("/admin/mailVerifyCode")
public RespBean getMailVerifyCode(HttpSession session){
String code = verifyCodeService.getVerifyCode();
//保存验证码到本次会话
session.setAttribute("mail_verify_code",code);
verifyCodeService.sendVerifyCodeMail(code);
return RespBean.ok("验证码已发送到邮箱,请注意查看!");
}
}
}
其中sendVerifyCodeMail方法:
@Override
public void sendVerifyCodeMail(String code) {
//添加消息记录
String msgId = UUID.randomUUID().toString();
MailSendLog mailSendLog = new MailSendLog();
mailSendLog.setMsgId(msgId);
mailSendLog.setContent(code);
mailSendLog.setContentType(MailConstants.VERIFY_CODE_TYPE);
mailSendLog.setCount(1);
mailSendLog.setCreateTime(new Date());
mailSendLog.setTryTime(new Date(System.currentTimeMillis()+1000*10*MailConstants.MEG_TIMEOUT));
mailSendLog.setUpdateTime(new Date());
mailSendLog.setExchange(mailExchange);
mailSendLog.setRouteKey(mailRouteVerifyCode);
mailSendLog.setStatus(MailConstants.DELIVERING);
mailSendLogService.insert(mailSendLog);
rabbitTemplate.convertAndSend(mailExchange,mailRouteVerifyCode,code,new CorrelationData(msgId));
}
验证验证码是否正确
/**
* 拦截登录请求,验证输入的验证码是否正确
* @author Hai
* @date 2020/5/28 - 17:31
*/
@Component
public class VerificationCodeFilter extends GenericFilter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//如果是登录请求则拦截
if ("POST".equals(request.getMethod())&&"/doLogin".equals(request.getServletPath())){
//TODO 不启动验证码
filterChain.doFilter(request,response);
//用户输入的验证码
String code = request.getParameter("code");
//session中保存的验证码
String verify_code = (String) request.getSession().getAttribute("verify_code");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
//验证session中保存是否存在
try {
//验证是否相同
if (!code.toLowerCase().equals(verify_code.toLowerCase())){
//输出json
writer.write(new ObjectMapper().writeValueAsString( RespBean.error("验证码错误!")));
return;
}else {
filterChain.doFilter(request,response);
}
}catch (NullPointerException e){
writer.write(new ObjectMapper().writeValueAsString(RespBean.error("请求异常,请重新请求!")));
}finally {
writer.flush();
writer.close();
}
}
else {
filterChain.doFilter(request,response);
}
}
}