/*
|
* Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
|
*
|
* Redistribution and use in source and binary forms, with or without
|
* modification, are permitted provided that the following conditions are met:
|
*
|
* Redistributions of source code must retain the above copyright notice,
|
* this list of conditions and the following disclaimer.
|
* Redistributions in binary form must reproduce the above copyright
|
* notice, this list of conditions and the following disclaimer in the
|
* documentation and/or other materials provided with the distribution.
|
* Neither the name of the dreamlu.net developer nor the names of its
|
* contributors may be used to endorse or promote products derived from
|
* this software without specific prior written permission.
|
* Author: Chill 庄骞 (smallchill@163.com)
|
*/
|
package org.springblade.modules.sms.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import org.apache.commons.lang3.StringUtils;
|
import org.jetbrains.annotations.NotNull;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springblade.common.utils.HttpClientUtils;
|
import org.springblade.common.utils.SpringUtils;
|
import org.springblade.common.utils.UtilRandom;
|
import org.springblade.common.utils.sms.AlipaySignature;
|
import org.springblade.core.redis.cache.BladeRedis;
|
import org.springblade.core.secure.utils.AuthUtil;
|
import org.springblade.modules.resource.entity.Sms;
|
import org.springblade.modules.resource.service.ISmsService;
|
import org.springblade.modules.sms.entity.SmsTemplateEntity;
|
import org.springblade.modules.sms.mapper.SmsTemplateMapper;
|
import org.springblade.modules.sms.service.ISmsSendService;
|
import org.springblade.modules.sms.service.ISmsTemplateService;
|
import org.springblade.modules.sms.entity.SmsRecordEntity;
|
import org.springblade.modules.sms.service.ISmsRecordService;
|
import org.springblade.modules.smsTask.entity.SmsTaskEntity;
|
import org.springblade.modules.smsTask.service.ISmsTaskService;
|
import org.springblade.modules.system.service.IUserService;
|
import org.springblade.modules.system.vo.UserVO;
|
import org.springblade.modules.task.entity.TaskPlaceSelfCheckEntity;
|
import org.springblade.modules.task.entity.TaskResidencePermitApplyEntity;
|
import org.springblade.modules.task.service.ITaskPlaceSelfCheckService;
|
import org.springblade.modules.task.service.ITaskResidencePermitApplyService;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import java.io.UnsupportedEncodingException;
|
import java.net.URLEncoder;
|
import java.text.SimpleDateFormat;
|
import java.time.LocalDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
|
/**
|
* 短信模版表 服务实现类
|
*
|
* @author BladeX
|
* @since 2024-03-06
|
*/
|
@Service
|
public class SmsSendServiceImpl extends ServiceImpl<SmsTemplateMapper, SmsTemplateEntity> implements ISmsSendService {
|
@Autowired
|
private BladeRedis redisTemplate;
|
private static Logger logger = LoggerFactory.getLogger(SmsSendServiceImpl.class);
|
// 确保这些字符串被适当地定义和管理
|
public static final String SMS_VALIDATE_PHONE = "sms:validate:code:";
|
public static final String SMS_VALIDATE_PHONE_NUM = "sms:validate:phone:";
|
private static final String SMS_SEND_FAILED = "短信发送失败!";
|
private static final String MESSAGE_SOP_EQUALS_MES_RESPONSE_CODE = "message_sop_equalsMes_response";
|
private static final DateTimeFormatter SECOND_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
|
@Autowired
|
private ISmsService iSmsService;
|
|
@Autowired
|
private ISmsTemplateService iSmsTemplateService;
|
|
@Override
|
public Boolean sendVerificationCode(String phone) {
|
if (phone == null || phone.length() != 11) {
|
logger.error("手机号格式错误");
|
return false;
|
}
|
|
if (redisTemplate.exists(SMS_VALIDATE_PHONE_NUM + phone)) {
|
logger.error("同一个手机号一分钟内只能发送一条记录");
|
return false;
|
}
|
|
Sms serviceOne = iSmsService.getOne(Wrappers.<Sms>lambdaQuery().eq(Sms::getStatus, 2));
|
if (serviceOne == null) {
|
logger.error("未找到状态为2的Sms服务");
|
return false;
|
}
|
SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.<SmsTemplateEntity>lambdaQuery()
|
.eq(SmsTemplateEntity::getId, serviceOne.getTemplateId()));
|
if (smsTemplateEntity == null) {
|
logger.error("未找到对应的短信模板");
|
return false;
|
}
|
|
//发送的手机号
|
List<Map> phonesList = new ArrayList<>();
|
Map phoneMap = new HashMap();
|
phoneMap.put("phone", phone);
|
List<String> varList = new ArrayList<>();
|
Integer code = UtilRandom.randomCount(111111, 999999);
|
varList.add(code.toString());
|
phoneMap.put("varList", varList);
|
phonesList.add(phoneMap);
|
//短信内容
|
String content = smsTemplateEntity.getContent();
|
//发送时间
|
LocalDateTime nowDateTime = LocalDateTime.now();
|
String send_time = nowDateTime.format(SECOND_FORMATTER);
|
Map params = new HashMap();
|
params.put("phones", phonesList);
|
//短信主题
|
params.put("subject", "对外接口不同内容发送-动态模板");
|
params.put("content", content);
|
//短信模板
|
params.put("template_id", null);
|
// 发送时间
|
params.put("send_time", send_time);
|
//优先级,高级=5,中级=3,低级=1
|
params.put("priority", "1");
|
//不同内容发送类型,1=动态模板发送,2=文件内容发送
|
params.put("type", "1");
|
//创建人主键
|
params.put("sop_create_by", serviceOne.getSmsCode());
|
//短信发送记录
|
saveSmsRecord(phone, serviceOne, code.toString(), content);
|
Boolean aBoolean = sendSmsGet(serviceOne, params);
|
if (aBoolean) {
|
// 将验证码存入redis,其中phone为手机号,code为验证码
|
// SMS_VALIDATE_PHONE 为前缀,通过phone和code在redis中设置一个过期的键值对
|
redisTemplate.setEx(SMS_VALIDATE_PHONE + phone, code, 300L);
|
// 同时,为了记录该手机短时间内已接收的验证码次数,使用另一个键值对进行计数
|
// SMS_VALIDATE_PHONE_NUM 为计数器的前缀,过期时间为60秒
|
redisTemplate.setEx(SMS_VALIDATE_PHONE_NUM + phone, 1, 60L);
|
}
|
return aBoolean;
|
}
|
|
|
/**
|
* @param templateId
|
* @param residenceId
|
* @return
|
*/
|
@Override
|
public Boolean sendNotice(Long templateId, Long residenceId) {
|
|
Sms serviceOne = iSmsService.getOne(Wrappers.<Sms>lambdaQuery().eq(Sms::getStatus, 2));
|
if (serviceOne == null) {
|
logger.error("未找到状态为2的Sms服务");
|
return false;
|
}
|
SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.<SmsTemplateEntity>lambdaQuery()
|
.eq(SmsTemplateEntity::getId, templateId));
|
if (smsTemplateEntity == null) {
|
logger.error("未找到对应的短信模板");
|
return false;
|
}
|
ITaskResidencePermitApplyService bean = SpringUtils.getBean(ITaskResidencePermitApplyService.class);
|
TaskResidencePermitApplyEntity residencePermitApplyEntity = bean.getById(residenceId);
|
String phone = residencePermitApplyEntity.getPhone();
|
//发送的手机号
|
List<Map> phonesList = new ArrayList<>();
|
Map phoneMap = new HashMap();
|
phoneMap.put("phone", phone);
|
List<String> varList = new ArrayList<>();
|
// 设置参数
|
|
varList.add(residencePermitApplyEntity.getName());
|
phoneMap.put("varList", varList);
|
phonesList.add(phoneMap);
|
//短信内容
|
String content = smsTemplateEntity.getContent();
|
//发送时间
|
LocalDateTime nowDateTime = LocalDateTime.now();
|
String send_time = nowDateTime.format(SECOND_FORMATTER);
|
Map params = new HashMap();
|
params.put("phones", phonesList);
|
//短信主题
|
params.put("subject", "对外接口不同内容发送-动态模板");
|
params.put("content", content);
|
//短信模板
|
params.put("template_id", null);
|
// 发送时间
|
params.put("send_time", send_time);
|
//优先级,高级=5,中级=3,低级=1
|
params.put("priority", "1");
|
//不同内容发送类型,1=动态模板发送,2=文件内容发送
|
params.put("type", "1");
|
//创建人主键
|
params.put("sop_create_by", serviceOne.getSmsCode());
|
//短信发送记录
|
saveSmsRecord(phone, serviceOne, residencePermitApplyEntity.getName(), content);
|
Boolean aBoolean = sendSmsGet(serviceOne, params);
|
return aBoolean;
|
}
|
|
/**
|
* @param templateId
|
* @param placeSelfCheckId
|
* @return
|
*/
|
@Override
|
public Boolean sendRectificationNotice(Long templateId, Long placeSelfCheckId) {
|
|
Sms serviceOne = iSmsService.getOne(Wrappers.<Sms>lambdaQuery().eq(Sms::getStatus, 2));
|
if (serviceOne == null) {
|
logger.error("未找到状态为2的Sms服务");
|
return false;
|
}
|
SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.<SmsTemplateEntity>lambdaQuery()
|
.eq(SmsTemplateEntity::getId, templateId));
|
if (smsTemplateEntity == null) {
|
logger.error("未找到对应的短信模板");
|
return false;
|
}
|
ITaskPlaceSelfCheckService bean = SpringUtils.getBean(ITaskPlaceSelfCheckService.class);
|
TaskPlaceSelfCheckEntity placeSelfCheckEntity = bean.getById(placeSelfCheckId);
|
IUserService userService = SpringUtils.getBean(IUserService.class);
|
UserVO userVO = userService.getuserById(placeSelfCheckEntity.getId());
|
String phone = userVO.getPhone();
|
//发送的手机号
|
List<Map> phonesList = new ArrayList<>();
|
Map phoneMap = new HashMap();
|
phoneMap.put("phone", phone);
|
List<String> varList = new ArrayList<>();
|
// 设置参数
|
|
varList.add(userVO.getName());
|
varList.add(placeSelfCheckEntity.getPlaceName());
|
phoneMap.put("varList", varList);
|
phonesList.add(phoneMap);
|
//短信内容
|
String content = smsTemplateEntity.getContent();
|
//发送时间
|
LocalDateTime nowDateTime = LocalDateTime.now();
|
String send_time = nowDateTime.format(SECOND_FORMATTER);
|
Map params = new HashMap();
|
params.put("phones", phonesList);
|
//短信主题
|
params.put("subject", "对外接口不同内容发送-动态模板");
|
params.put("content", content);
|
//短信模板
|
params.put("template_id", null);
|
// 发送时间
|
params.put("send_time", send_time);
|
//优先级,高级=5,中级=3,低级=1
|
params.put("priority", "1");
|
//不同内容发送类型,1=动态模板发送,2=文件内容发送
|
params.put("type", "1");
|
//创建人主键
|
params.put("sop_create_by", serviceOne.getSmsCode());
|
//短信发送记录
|
saveSmsRecord(phone, serviceOne, placeSelfCheckEntity.getPlaceName(), content);
|
Boolean aBoolean = sendSmsGet(serviceOne, params);
|
return aBoolean;
|
}
|
|
@Override
|
public Boolean loginSendVerificationCode(String phone) {
|
if (phone == null || phone.length() != 11) {
|
logger.error("手机号格式错误");
|
return false;
|
}
|
|
if (redisTemplate.exists(SMS_VALIDATE_PHONE_NUM + phone)) {
|
logger.error("同一个手机号一分钟内只能发送一条记录");
|
return false;
|
}
|
|
Sms serviceOne = iSmsService.getOne(Wrappers.<Sms>lambdaQuery().eq(Sms::getStatus, 2));
|
if (serviceOne == null) {
|
logger.error("未找到状态为2的Sms服务");
|
return false;
|
}
|
SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.<SmsTemplateEntity>lambdaQuery()
|
.eq(SmsTemplateEntity::getId, "1783764287897939969"));
|
if (smsTemplateEntity == null) {
|
logger.error("未找到对应的短信模板");
|
return false;
|
}
|
|
//发送的手机号
|
List<Map> phonesList = new ArrayList<>();
|
Map phoneMap = new HashMap();
|
phoneMap.put("phone", phone);
|
List<String> varList = new ArrayList<>();
|
Integer code = UtilRandom.randomCount(111111, 999999);
|
varList.add(code.toString());
|
phoneMap.put("varList", varList);
|
phonesList.add(phoneMap);
|
//短信内容
|
String content = smsTemplateEntity.getContent();
|
//发送时间
|
LocalDateTime nowDateTime = LocalDateTime.now();
|
String send_time = nowDateTime.format(SECOND_FORMATTER);
|
Map params = new HashMap();
|
params.put("phones", phonesList);
|
//短信主题
|
params.put("subject", "对外接口不同内容发送-动态模板");
|
params.put("content", content);
|
//短信模板
|
params.put("template_id", null);
|
// 发送时间
|
params.put("send_time", send_time);
|
//优先级,高级=5,中级=3,低级=1
|
params.put("priority", "1");
|
//不同内容发送类型,1=动态模板发送,2=文件内容发送
|
params.put("type", "1");
|
//创建人主键
|
params.put("sop_create_by", serviceOne.getSmsCode());
|
//短信发送记录
|
saveSmsRecord(phone, serviceOne, code.toString(), content);
|
Boolean aBoolean = sendSmsGet(serviceOne, params);
|
if (aBoolean) {
|
// 将验证码存入redis,其中phone为手机号,code为验证码
|
// SMS_VALIDATE_PHONE 为前缀,通过phone和code在redis中设置一个过期的键值对
|
redisTemplate.setEx(SMS_VALIDATE_PHONE + phone, code, 300L);
|
// 同时,为了记录该手机短时间内已接收的验证码次数,使用另一个键值对进行计数
|
// SMS_VALIDATE_PHONE_NUM 为计数器的前缀,过期时间为60秒
|
redisTemplate.setEx(SMS_VALIDATE_PHONE_NUM + phone, 1, 60L);
|
}
|
return aBoolean;
|
}
|
|
@Override
|
public String checkCode(String phone, String code) {
|
if (StringUtils.isNotBlank(phone)) {
|
if (StringUtils.isBlank(code)) {
|
return "验证码不能为空!";
|
}
|
Object validateCode = redisTemplate.get(SMS_VALIDATE_PHONE + phone);
|
if (validateCode == null) {
|
return "验证码不存在或已过期";
|
}
|
if (!validateCode.toString().equals(code)) {
|
return "验证码错误";
|
}
|
//删除验证码
|
redisTemplate.del(SMS_VALIDATE_PHONE + phone);
|
}
|
return "";
|
}
|
|
@Override
|
public Boolean batchSendNotice(Long smsTaskId) {
|
ISmsTaskService taskService = SpringUtils.getBean(ISmsTaskService.class);
|
SmsTaskEntity smsTaskEntity = taskService.getById(smsTaskId);
|
|
Sms serviceOne = iSmsService.getOne(Wrappers.<Sms>lambdaQuery().eq(Sms::getStatus, 2));
|
if (serviceOne == null) {
|
logger.error("未找到状态为2的Sms服务");
|
return false;
|
}
|
SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.<SmsTemplateEntity>lambdaQuery()
|
.eq(SmsTemplateEntity::getId, smsTaskEntity.getSmsTemplate()));
|
if (smsTemplateEntity == null) {
|
logger.error("未找到对应的短信模板");
|
return false;
|
}
|
|
//发送的手机号
|
List<Map> phonesList = new ArrayList<>();
|
Map phoneMap = new HashMap();
|
// phoneMap.put("phone", phone);
|
List<String> varList = new ArrayList<>();
|
// 设置参数
|
phoneMap.put("varList", varList);
|
phonesList.add(phoneMap);
|
//短信内容
|
String content = smsTemplateEntity.getContent();
|
//发送时间
|
LocalDateTime nowDateTime = LocalDateTime.now();
|
String send_time = nowDateTime.format(SECOND_FORMATTER);
|
Map params = new HashMap();
|
params.put("phones", phonesList);
|
//短信主题
|
params.put("subject", "对外接口不同内容发送-动态模板");
|
params.put("content", content);
|
//短信模板
|
params.put("template_id", null);
|
// 发送时间
|
params.put("send_time", send_time);
|
//优先级,高级=5,中级=3,低级=1
|
params.put("priority", "1");
|
//不同内容发送类型,1=动态模板发送,2=文件内容发送
|
params.put("type", "1");
|
//创建人主键
|
params.put("sop_create_by", serviceOne.getSmsCode());
|
//短信发送记录
|
// saveSmsRecord(phone, serviceOne, placeSelfCheckEntity.getPlaceName(), content);
|
Boolean aBoolean = sendSmsGet(serviceOne, params);
|
return aBoolean;
|
|
}
|
|
/**
|
* 发送短信并获取发送结果。
|
*
|
* @param serviceOne 用于发送短信的服务对象
|
* @param params 发送短信所需的参数映射
|
* @return 如果短信发送成功,则返回true;否则返回false。
|
*/
|
@NotNull
|
private Boolean sendSmsGet(Sms serviceOne, Map params) {
|
try {
|
// 尝试发送短信并解析响应结果
|
String result = sendSms(params, "message.sop.differentMes", serviceOne);
|
JSONObject jsonObject = JSON.parseObject(result);
|
JSONObject messageSopEqualsMesResponse = (JSONObject) jsonObject.get(MESSAGE_SOP_EQUALS_MES_RESPONSE_CODE);
|
if (messageSopEqualsMesResponse.get("code").equals(200)) {
|
// 如果响应代码为200,表示短信发送成功
|
return true;
|
} else {
|
// 记录短信发送失败的日志
|
logger.error("短信发送失败!");
|
}
|
} catch (Exception e) {
|
// 记录发送短信过程中出现的异常
|
logger.error(SMS_SEND_FAILED, e);
|
throw new RuntimeException(e);
|
}
|
// 如果执行到这里,表示短信发送失败或出现异常
|
return false;
|
}
|
|
/**
|
* 保存短信发送记录
|
*
|
* @param phone 接收短信的手机号码
|
* @param serviceOne 发送短信所使用的服务
|
* @param code 短信中的验证码
|
* @param content 短信内容,其中可能包含占位符"#P_1#"用于被验证码替换
|
*/
|
private static void saveSmsRecord(String phone, Sms serviceOne, String code, String content) {
|
// 通过Spring工具类获取短信记录服务的实例
|
ISmsRecordService smsRecordService = SpringUtils.getBean(ISmsRecordService.class);
|
// 创建一个新的短信记录实体
|
SmsRecordEntity smsRecordEntity = new SmsRecordEntity();
|
// 设置短信内容,将占位符"#P_1#"替换为验证码
|
smsRecordEntity.setContent(content.replace("#P_1#", code));
|
// 设置手机号码
|
smsRecordEntity.setPhone(phone);
|
// 设置短信模板ID
|
smsRecordEntity.setTemplateId(Long.valueOf(serviceOne.getTemplateId()));
|
// 设置创建用户ID,来自认证工具类
|
smsRecordEntity.setCreateUser(AuthUtil.getUserId());
|
// 保存短信记录实体
|
smsRecordService.save(smsRecordEntity);
|
}
|
|
public String sendSms(Object bizContent, String method, Sms serviceOne) throws Exception {
|
|
// 公共请求参数
|
Map<String, String> params = new HashMap<String, String>();
|
params.put("app_id", serviceOne.getAccessKey());
|
params.put("method", method);
|
params.put("format", "json");
|
params.put("charset", "utf-8");
|
params.put("sign_type", "RSA2");
|
params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
params.put("version", "1.0");
|
|
// 业务参数
|
params.put("biz_content", JSON.toJSONString(bizContent));
|
String content = AlipaySignature.getSignContent(params);
|
String sign = AlipaySignature.rsa256Sign(content, serviceOne.getSecretKey(), "utf-8");
|
params.put("sign", sign);
|
logger.info("短信请求参数:" + buildParamQuery(params));
|
String responseData = HttpClientUtils.doGet(serviceOne.getRemark(), params);// 发送请求
|
logger.info("短信响应结果:" + responseData);
|
return responseData;
|
}
|
|
protected static String buildParamQuery(Map<String, String> params) {
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
|
}
|
return sb.toString().substring(1);
|
}
|
|
protected static String buildUrlQuery(Map<String, String> params) {
|
StringBuilder sb = new StringBuilder();
|
for (Map.Entry<String, String> entry : params.entrySet()) {
|
try {
|
sb.append("&").append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
} catch (UnsupportedEncodingException e) {
|
e.printStackTrace();
|
}
|
}
|
return sb.toString().substring(1);
|
}
|
}
|