Quellcode durchsuchen

重构短信接口

tsurumure vor 8 Monaten
Ursprung
Commit
dd7094a6c9

+ 1 - 0
db/sys_sms_history.sql

@@ -10,6 +10,7 @@ CREATE TABLE `sys_mobile_sms_history` (
     PRIMARY KEY (`id`),
     `id` BIGINT(10) NOT NULL AUTO_INCREMENT COMMENT 'ID',
     `origin` VARCHAR(20) COMMENT '来源',
+    `phone_area_code` VARCHAR(20) COMMENT '区号/国家码',
     `phone` VARCHAR(20) NOT NULL COMMENT '手机号码',
     `sms_code` VARCHAR(255) COMMENT '验证码',
     `ip` VARCHAR(255) COMMENT 'IP',

+ 8 - 5
src/main/java/com/backendsys/modules/common/config/security/utils/HttpRequestUtil.java

@@ -21,7 +21,7 @@ public class HttpRequestUtil {
     /**
      * 获取HttpServletRequest 对象
      */
-    private HttpServletRequest getRequest() {
+    public HttpServletRequest getRequest() {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
         HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
         return request;
@@ -97,11 +97,14 @@ public class HttpRequestUtil {
      * 获得验证码 RedisKey (ua + ip)
      */
     public String getKaptchaKey() {
-        HttpServletRequest request = getRequest();
-        String ip = CommonUtil.getIpAddr(request);
-        String userAgent = request.getHeader("User-Agent");
-        return "captcha:ua:ip:" + CommonUtil.MD5(ip+userAgent);
+        String ip = getIpAddr();
+        String userAgent = getUa();
+        return "captcha:ua:ip:" + CommonUtil.MD5(ip + userAgent);
     }
 
+    public String getUa() {
+        HttpServletRequest request = getRequest();
+        return request.getHeader("User-Agent");
+    }
 
 }

+ 33 - 0
src/main/java/com/backendsys/modules/sms/controller/SmsController.java

@@ -0,0 +1,33 @@
+package com.backendsys.modules.sms.controller;
+
+import com.backendsys.entity.System.SysSMSDTO;
+import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.sms.entity.Sms;
+import com.backendsys.modules.sms.service.SmsService;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@Validated
+@RestController
+public class SmsController {
+
+    @Autowired
+    private SmsService smsService;
+
+
+
+    /**
+     * 发送短信
+     */
+    @PostMapping("/api/v2/public/system/sms/getSMS")
+    public Result getSMS(@Validated(Sms.Send.class) @RequestBody Sms sms) throws TencentCloudSDKException {
+        return Result.success().put("data", smsService.sendValidCode(sms));
+    }
+
+}

+ 10 - 0
src/main/java/com/backendsys/modules/sms/dao/SmsDao.java

@@ -0,0 +1,10 @@
+package com.backendsys.modules.sms.dao;
+
+import com.backendsys.modules.sms.entity.Sms;
+import com.backendsys.modules.system.entity.SysUser;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface SmsDao extends BaseMapper<Sms> {
+}

+ 29 - 0
src/main/java/com/backendsys/modules/sms/entity/Sms.java

@@ -0,0 +1,29 @@
+package com.backendsys.modules.sms.entity;
+
+import com.backendsys.entity.validator.RangeStringArray;
+import com.baomidou.mybatisplus.annotation.TableName;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+@TableName("sys_mobile_sms_history")
+public class Sms {
+    public static interface Send{}
+    private Long id;
+    @RangeStringArray(message="短信类型有误,范围应是(login:登录, register:注册, forgotPassword: 忘记密码)", value = {"login", "register", "forgotPassword"}, groups = { Send.class })
+    @NotEmpty(message="短信类型不能为空", groups = { Send.class })
+    private String origin;
+    @NotEmpty(message="手机号码不能为空", groups = { Send.class })
+    @Size(max = 20, message = "手机号码长度不超过 {max} 字符", groups = { Send.class })
+    private String phone;
+    @NotNull(message="区号/国家码不能为空", groups = { Send.class })
+    @Max(value = 999999, message = "区号/国家码长度不超过 {value} 字符", groups = { Send.class })
+    private Integer phone_area_code;
+    private Integer sms_code;
+    private String ip;
+    private String ua;
+    private String create_time;
+}

+ 13 - 0
src/main/java/com/backendsys/modules/sms/service/SmsService.java

@@ -0,0 +1,13 @@
+package com.backendsys.modules.sms.service;
+
+import com.backendsys.modules.sms.entity.Sms;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+
+import java.util.Map;
+
+public interface SmsService {
+
+    // 发送验证码 (通用验证码)
+    Map<String, Object> sendValidCode(Sms sms) throws TencentCloudSDKException;
+
+}

+ 87 - 0
src/main/java/com/backendsys/modules/sms/service/impl/SmsServiceImpl.java

@@ -0,0 +1,87 @@
+package com.backendsys.modules.sms.service.impl;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.json.JSONUtil;
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
+import com.backendsys.modules.sms.dao.SmsDao;
+import com.backendsys.modules.sms.entity.Sms;
+import com.backendsys.modules.sms.service.SmsService;
+import com.backendsys.modules.tencent.service.TencentSmsService;
+import com.backendsys.utils.response.ResultEnum;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
+import com.tencentcloudapi.sms.v20210111.models.SendStatus;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class SmsServiceImpl implements SmsService {
+
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
+    @Autowired
+    private TencentSmsService tencentSmsService;
+    @Autowired
+    private SmsDao smsDao;
+
+    // [模板] 通用验证码
+    @Value("${tencent.sms.template-id-of-common}")
+    private String TEMPLATE_ID_COMMON;
+
+    /**
+     * 发送验证码
+     */
+    @Override
+    public Map<String, Object> sendValidCode(Sms sms) throws TencentCloudSDKException {
+
+        String origin = sms.getOrigin();
+        String phone = sms.getPhone();
+        Integer phoneAreaCode = sms.getPhone_area_code();
+
+        // 生成一个6位的随机整数 (验证码) (5分钟失效)
+        Integer smsExpire = 5;
+        Integer smsCode =  RandomUtil.randomInt(100000, 999999);
+        sms.setSms_code(smsCode);
+
+        // 模板参数 {1} 验证码, {2} 过期时间(分钟)
+        // 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空
+        String[] templateParamSet = {String.valueOf(smsCode), String.valueOf(smsExpire)};
+        // 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
+        // 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号
+        String[] phoneNumberSet = {"+" + phoneAreaCode + phone};
+        SendSmsResponse sendSmsResponse = tencentSmsService.send(TEMPLATE_ID_COMMON, templateParamSet, phoneNumberSet);
+
+        if (sendSmsResponse != null) {
+            SendStatus[] sendStatuses = sendSmsResponse.getSendStatusSet();
+            if (sendStatuses.length > 0) {
+                // for (int i = 0; i < sendStatuses.length; ++i) {
+                if (!sendStatuses[0].getCode().equals("Ok")) {
+                    String sendStatusStr = JSONUtil.toJsonStr(sendStatuses[0]);
+                    log.error(sendStatusStr);
+                    throw new CustException("发送失败", ResultEnum.REMOTE_EXCEPTION.getCode(), sendStatuses[1]);
+                }
+            }
+        }
+        // -- 发送成功 --------------------------------------------------
+        // [Redis] 将 来源+手机号码 为标识,将验证码存入缓存
+        String redisKey = "sms-" + origin + "-" + phone;
+        redisUtil.setCacheObject(redisKey, smsCode, smsExpire, TimeUnit.MINUTES);
+
+        // [插入] 新增短信记录
+        sms.setIp(httpRequestUtil.getIpAddr());
+        sms.setUa(httpRequestUtil.getUa());
+        smsDao.insert(sms);
+
+        return Map.of("phone", sms.getPhone());
+    }
+}

+ 13 - 0
src/main/java/com/backendsys/modules/tencent/service/TencentSmsService.java

@@ -0,0 +1,13 @@
+package com.backendsys.modules.tencent.service;
+
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
+
+import java.util.Map;
+
+public interface TencentSmsService {
+
+    // [腾讯云] 发送短信 { 模板ID, 模板参数, 下发手机号码 }
+    SendSmsResponse send(String template_id, String[] templateParamSet, String[] phoneNumberSet) throws TencentCloudSDKException;
+
+}

+ 57 - 0
src/main/java/com/backendsys/modules/tencent/service/impl/TencentSmsServiceImpl.java

@@ -0,0 +1,57 @@
+package com.backendsys.modules.tencent.service.impl;
+
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.tencent.service.TencentSmsService;
+import com.tencentcloudapi.common.Credential;
+import com.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.tencentcloudapi.common.profile.ClientProfile;
+import com.tencentcloudapi.sms.v20210111.SmsClient;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;
+import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class TencentSmsServiceImpl implements TencentSmsService {
+
+    @Value("${tencent.sms.secret-id}")
+    private String SECRET_ID;
+    @Value("${tencent.sms.secret-key}")
+    private String SECRET_KEY;
+    @Value("${tencent.sms.sdk-app-id}")
+    private String SDK_APP_ID;
+    @Value("${tencent.sms.sign-name}")
+    private String SIGN_NAME;
+
+    // [腾讯云] 发送短信 { 模板ID, 模板参数, 下发手机号码 }
+    @Override
+    public SendSmsResponse send(String template_id, String[] templateParamSet, String[] phoneNumberSet) throws TencentCloudSDKException {
+
+        SendSmsRequest req = new SendSmsRequest();
+        req.setSmsSdkAppId(SDK_APP_ID);
+        req.setSignName(SIGN_NAME);
+        req.setTemplateId(template_id);
+        req.setTemplateParamSet(templateParamSet);
+        req.setPhoneNumberSet(phoneNumberSet);
+
+        // [SDK] 发送短信
+        Credential cred = new Credential(SECRET_ID, SECRET_KEY);
+        ClientProfile clientProfile = new ClientProfile();
+        SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile);
+
+        // 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
+        // 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应
+        SendSmsResponse res = client.SendSms(req);
+        return res;
+    }
+
+}

+ 3 - 3
src/main/resources/application-local.yml

@@ -96,9 +96,9 @@ tencent:
     debug: true
     secret-id: AKIDITiApJZjt27AEOhzA92467Nbilw4RyRp
     secret-key: iMqEZ3hVbwUwU3cBrXCpPH27968LUViu
-    sdk-app-id: 1400892090
-    sign-name: 汕头市稻谷文化传媒
-    template-id-of-common: 2136771
+    sdk-app-id: 1400892090            # 短信 - 应用管理 - 应用列表
+    sign-name: 汕头市稻谷文化传媒        # 短信 - 国内/外短信 - 签名管理 - 签名内容
+    template-id-of-common: 2136771   # 短信 - 国内/外短信 - 正文模板管理 - ID (普通短信/通用验证码)
   hunyuan:
     region: ap-guangzhou
     secret-id: AKID3zlNxRjstjnohWFnDUfeVBj3CJH7mFaK

+ 3 - 3
src/main/resources/application-prod.yml

@@ -98,9 +98,9 @@ tencent:
     debug: false
     secret-id: AKIDITiApJZjt27AEOhzA92467Nbilw4RyRp
     secret-key: iMqEZ3hVbwUwU3cBrXCpPH27968LUViu
-    sdk-app-id: 1400892090
-    sign-name: 汕头市稻谷文化传媒
-    template-id-of-common: 2136771
+    sdk-app-id: 1400892090            # 短信 - 应用管理 - 应用列表
+    sign-name: 汕头市稻谷文化传媒        # 短信 - 国内/外短信 - 签名管理 - 签名内容
+    template-id-of-common: 2136771   # 短信 - 国内/外短信 - 正文模板管理 - ID (普通短信/通用验证码)
   hunyuan:
     region: ap-guangzhou
     secret-id: AKID3zlNxRjstjnohWFnDUfeVBj3CJH7mFaK