/* * 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.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.task.entity.TaskResidencePermitApplyEntity; 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 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; } Sms serviceOne = iSmsService.getOne(Wrappers.lambdaQuery().eq(Sms::getStatus, 2)); if (serviceOne == null) { logger.error("未找到状态为2的Sms服务"); return false; } SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.lambdaQuery() .eq(SmsTemplateEntity::getId, serviceOne.getTemplateId())); if (smsTemplateEntity == null) { logger.error("未找到对应的短信模板"); return false; } //发送的手机号 List phonesList = new ArrayList<>(); Map phoneMap = new HashMap(); phoneMap.put("phone", phone); List 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.lambdaQuery().eq(Sms::getStatus, 2)); if (serviceOne == null) { logger.error("未找到状态为2的Sms服务"); return false; } SmsTemplateEntity smsTemplateEntity = iSmsTemplateService.getOne(Wrappers.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 phonesList = new ArrayList<>(); Map phoneMap = new HashMap(); phoneMap.put("phone", phone); List 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 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 params = new HashMap(); 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 params) { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : params.entrySet()) { sb.append("&").append(entry.getKey()).append("=").append(entry.getValue()); } return sb.toString().substring(1); } protected static String buildUrlQuery(Map params) { StringBuilder sb = new StringBuilder(); for (Map.Entry 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); } }