xin
2 days ago debb7b1ef8bb4e96e09b2d878ad821b4b2f3a110
短信api更新维护(未完成)
3 files renamed
3 files added
4 files modified
378 ■■■■■ changed files
oying-system/src/main/java/com/oying/modules/security/rest/VerificationController.java 4 ●●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/security/utils/SendMessageUtils.java 24 ●●●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/security/utils/WinnerLookEnum.java 2 ●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/security/utils/WinnerLookProperties.java 3 ●●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/winnerlook/client/BaseApi.java 48 ●●●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/winnerlook/client/OkHttpService.java 201 ●●●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/winnerlook/util/Md5Util.java 78 ●●●●● patch | view | raw | blame | history
oying-system/src/main/resources/config/application-dev.yml 8 ●●●● patch | view | raw | blame | history
oying-system/src/main/resources/config/application-prod.yml 8 ●●●● patch | view | raw | blame | history
oying-system/src/main/resources/config/application.yml 2 ●●● patch | view | raw | blame | history
oying-system/src/main/java/com/oying/modules/security/rest/VerificationController.java
@@ -4,8 +4,8 @@
import com.oying.annotation.rest.AnonymousGetMapping;
import com.oying.utils.R;
import com.oying.utils.RedisUtils;
import com.oying.utils.SendMessageUtils;
import com.oying.utils.WinnerLookProperties;
import com.oying.modules.security.utils.SendMessageUtils;
import com.oying.modules.security.utils.WinnerLookProperties;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
oying-system/src/main/java/com/oying/modules/security/utils/SendMessageUtils.java
File was renamed from oying-tools/src/main/java/com/oying/utils/SendMessageUtils.java
@@ -1,6 +1,8 @@
package com.oying.utils;
package com.oying.modules.security.utils;
import cn.hutool.core.util.IdUtil;
import com.oying.exception.BadRequestException;
import com.oying.utils.HttpRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
@@ -24,8 +26,26 @@
@Data
public class SendMessageUtils {
    public static final String SIGN = "【立研】";
    public static final String SIGN = "【哦应科技】";
    public static final String MESSAGE = "您的验证码为:{code},请勿泄露于他人!";
    public static void main(String[] args) {
        System.out.println(IdUtil.simpleUUID());
        Map<String, String> params = new HashMap<>();
        params.put("userCode", "SHOYYJ");
        params.put("userPass", "Oy250928");
        params.put("DesNo", "15213186640");
        params.put("Msg", "【哦应科技】您的验证码为:784121,请勿泄露于他人!");
        params.put("smsType", "101");
        String str = extractWithRegex(HttpRequest.exchangeMsg(HttpMethod.POST, "https://118.178.116.15:8443/winnerrxd/api/trigger/SendMsg", convert(params)));
        if (ObjectUtils.isEmpty(str)) {
            log.error("短信调用异常 {}", str);
            throw new BadRequestException("短信调用异常");
        }
        long i = Long.parseLong(str);
        if (i < 0) {
            throw new BadRequestException(WinnerLookEnum.find(str));
        }
    }
    public static void sendMsg(String url, String phone, String sign, String message, WinnerLookProperties properties) {
        Map<String, String> params = new HashMap<>();
oying-system/src/main/java/com/oying/modules/security/utils/WinnerLookEnum.java
File was renamed from oying-tools/src/main/java/com/oying/utils/WinnerLookEnum.java
@@ -1,4 +1,4 @@
package com.oying.utils;
package com.oying.modules.security.utils;
import lombok.AllArgsConstructor;
import lombok.Getter;
oying-system/src/main/java/com/oying/modules/security/utils/WinnerLookProperties.java
File was renamed from oying-tools/src/main/java/com/oying/utils/WinnerLookProperties.java
@@ -1,4 +1,4 @@
package com.oying.utils;
package com.oying.modules.security.utils;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -22,6 +22,7 @@
     * 每次不要超过50个号码
     */
    private String urlSendMsgBatch;
    private String token;
    private String userCode;
    private String userPass;
}
oying-system/src/main/java/com/oying/modules/winnerlook/client/BaseApi.java
New file
@@ -0,0 +1,48 @@
package com.oying.modules.winnerlook.client;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * @author MinaWu
 * @description
 * @date 2025/5/14 16:41
 */
@Slf4j
public class BaseApi {
    private static final String API_V1_TRIGGER_SEND_MSG = "/api/v1/trigger/SendMsg";
    private static final String API_V1_TRIGGER_SEND_BATCH_MSG = "/api/v1/trigger/SendBatchMsg";
    public static String TRIGGER = "101";
    /**
     * 发送短信
     *
     * @param userCode     登录名称
     * @param token        token
     * @param desNo        手机号
     * @param msg          内容
     * @param autograph    签名编号
     * @param customerUuid 用户标识字段
     * @return String
     */
    public static String sendMsgByPost(String userCode, String token, String desNo, String msg, String autograph,
                                       String customerUuid, String baseUrl) {
        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("DesNo", desNo);
        params.put("Msg", msg);
        params.put("smsType", TRIGGER);
        if (StringUtils.isNotBlank(autograph)) {
            params.put("autograph", autograph);
        }
        if (StringUtils.isNotBlank(customerUuid)) {
            params.put("customerUuid", customerUuid);
        }
        String xmlResponse = OkHttpService.doPost(params, baseUrl, userCode, token);
        return OkHttpService.parseXmlResponse(xmlResponse);
    }
}
oying-system/src/main/java/com/oying/modules/winnerlook/client/OkHttpService.java
New file
@@ -0,0 +1,201 @@
package com.oying.modules.winnerlook.client;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.oying.exception.BadRequestException;
import com.oying.modules.winnerlook.util.Md5Util;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import shade.okhttp3.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
/**
 * @author MinaWu
 * @description
 * @date 2025/5/14 16:16
 */
@Slf4j
public class OkHttpService {
    private static final String SECRET_KEY = "X-Winner-Secret-Key";
    private static final String TIMESTAMP_HEADER = "X-Winner-Timestamp";
    private static final String SIGNATURE_HEADER = "X-Winner-Signature";
    private static final String REQUEST_ID = "X-Winner-Request-Id";
    private static final String TOKEN = "X-Winner-token";
    private static final String PARAMS = "params";
    /**
     * 生成 requestId
     *
     * @param timestamp 时间戳
     * @return requestId
     */
    private static String generateRequestId(String userName, long timestamp) {
        return userName.replace("_", "") + "_" + IdUtil.simpleUUID() + "_" + timestamp;
    }
    public static String doPost(Map<String, Object> params, String baseUrl, String userName, String token) {
        String secretKey = UUID.randomUUID().toString().replace("-", "");
        String boundary = UUID.randomUUID().toString().replace("-", "");
        // 生成时间戳
        long timestamp = System.currentTimeMillis();
        String timestampStr = String.valueOf(timestamp);
        // 生成 requestId
        String requestId = generateRequestId(userName, timestamp);
        // 生成签名
        String signature = generateSignature(params, timestampStr, secretKey, requestId, token);
        // 创建请求体
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(boundary)
                .setType(MultipartBody.FORM);
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue().toString());
        }
        // 创建请求
        Request request = new Request.Builder()
                .url(baseUrl)
                .header(TIMESTAMP_HEADER, timestampStr)
                .header(SIGNATURE_HEADER, signature)
                .header(REQUEST_ID, requestId)
                .header(SECRET_KEY, secretKey)
                .header("Content-Type", "multipart/form-data;charset=UTF-8; boundary=" + boundary)
                .post(bodyBuilder.build())
                .build();
        // 发送请求
        try {
            return "response";
        } catch (Exception e) {
            log.info("doPost requestId:{},body:{},headers:{} ", requestId, request.body().toString(), request.headers().toString());
            throw new BadRequestException("短信POST:" + e.getMessage());
        }
    }
    /**
     * 对 treeMap 的键进行排序
     */
    private static void sortParams(Map<String, Object> treeMap) {
        for (String key : treeMap.keySet()) {
            Object value = treeMap.get(key);
            if (value instanceof JSONObject) {
                // 如果是 JSONObject,递归排序
                treeMap.put(key, sortJsonObject((JSONObject) value));
            } else if (value instanceof JSONArray) {
                // 如果是 JSONArray,对数组中的每个对象排序
                treeMap.put(key, sortJsonArray((JSONArray) value));
            } else {
                // 其他类型直接放入
                treeMap.put(key, value);
            }
        }
    }
    /**
     * 对 JSONObject 的键进行排序
     */
    private static JSONObject sortJsonObject(JSONObject jsonObject) {
        Map<String, Object> treeMap = new TreeMap<>();
        Set<String> keys = jsonObject.keySet();
        for (String key : keys) {
            Object value = jsonObject.get(key);
            if (value instanceof JSONObject) {
                // 如果是 JSONObject,递归排序
                treeMap.put(key, sortJsonObject((JSONObject) value));
            } else if (value instanceof JSONArray) {
                // 如果是 JSONArray,对数组中的每个对象排序
                treeMap.put(key, sortJsonArray((JSONArray) value));
            } else {
                // 其他类型直接放入
                treeMap.put(key, value);
            }
        }
        return new JSONObject(treeMap);
    }
    /**
     * 对 JSONArray 中的每个 JSONObject 进行排序
     */
    private static JSONArray sortJsonArray(JSONArray jsonArray) {
        JSONArray sortedJsonArray = new JSONArray();
        for (Object item : jsonArray) {
            if (item instanceof JSONObject) {
                // 如果是 JSONObject,递归排序
                sortedJsonArray.add(sortJsonObject((JSONObject) item));
            } else {
                // 其他类型直接放入
                sortedJsonArray.add(item);
            }
        }
        return sortedJsonArray;
    }
    /**
     * 生成签名
     *
     * @param map       请求参数
     * @param timestamp 时间戳
     * @param secretKey 密钥
     * @return 签名
     */
    private static String generateSignature(Map<String, Object> map, String timestamp, String secretKey, String requestId, String token) {
        try {
            // 对参数进行排序 将params转成字符串再转成map是为了将其中的对象和集合转成jsonObject和jsonArray 方便排序处理
//            Map<String, Object> map = JSONObject.parseObject(JSONObject.toJSONString(map), Map.class);
            Map<String, Object> sortedParams = new TreeMap<>(map);
            sortParams(sortedParams);
            // 将参数转换为 JSON 字符串
            String jsonString = JSONObject.toJSONString(sortedParams);
            // 计算参数的 MD5
            String paramsMD5 = Md5Util.getMD5ByCharset(jsonString, StandardCharsets.UTF_8);
            log.info("generateSignature requestId:{} jsonString:{} paramsMD5:{}", requestId, jsonString, paramsMD5);
            // 生成待签名的数据
            String dataToSign = PARAMS + "=" + paramsMD5 +
                    "&" + TIMESTAMP_HEADER + "=" + timestamp +
                    "&" + REQUEST_ID + "=" + requestId +
                    "&" + TOKEN + "=" + token;
            // 使用 HMAC-SHA256 生成签名
            return Md5Util.hmacSha256(dataToSign, secretKey);
        } catch (Exception e) {
            throw new BadRequestException("短信生成签名:" + e.getMessage());
        }
    }
    /**
     * 解析 XML 响应并提取 <string> 标签中的值
     *
     * @param xmlResponse XML 响应字符串
     * @return <string> 标签中的值
     */
    public static String parseXmlResponse(String xmlResponse) {
        try {
            // 创建 DocumentBuilder
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            // 将 XML 字符串转换为 Document 对象
            Document document = builder.parse(new ByteArrayInputStream(xmlResponse.getBytes(StandardCharsets.UTF_8)));
            // 获取 <string> 标签
            NodeList nodeList = document.getElementsByTagName("string");
            Element stringElement = (Element) nodeList.item(0);
            // 提取标签中的文本内容
            return stringElement.getTextContent();
        } catch (Exception e) {
            throw new BadRequestException("短信解析XML:" + e.getMessage());
        }
    }
}
oying-system/src/main/java/com/oying/modules/winnerlook/util/Md5Util.java
New file
@@ -0,0 +1,78 @@
package com.oying.modules.winnerlook.util;
import com.oying.exception.BadRequestException;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * @author MinaWu
 * @description 加密 HMAC_SHA256、MD5
 * @date 2025/3/31 14:41
 */
public class Md5Util {
    // 用来将字节转换成 16 进制表示的字符
    static char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
            '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static final String HMAC_SHA256 = "HmacSHA256";
    /**
     * HMAC_SHA256加密
     * @param data 待加密数据
     * @param key 密钥
     * @return String
     */
    public static String hmacSha256(String data, String key) throws Exception {
        Mac mac = Mac.getInstance(HMAC_SHA256);
        SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), HMAC_SHA256);
        mac.init(secretKey);
        byte[] hmacBytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.encodeBase64String(hmacBytes);
    }
    /**
     * 对一段String生成MD5加密信息
     *
     * @param message 要加密的String
     * @return 生成的MD5信息
     */
    public static String getMD5ByCharset(String message, Charset charset) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] b = md.digest(message.getBytes(charset));
            return byteToHexString(b);
        } catch (NoSuchAlgorithmException e) {
            throw new BadRequestException("短信MD5加密:" + e.getMessage());
        }
    }
    /**
     * 把byte[]数组转换成十六进制字符串表示形式
     *
     * @param tmp 要转换的byte[]
     * @return 十六进制字符串表示形式
     */
    private static String byteToHexString(byte[] tmp) {
        String s;
        // 用字节表示就是 16 个字节
        char[] str = new char[16 * 2]; // 每个字节用 16 进制表示的话,使用两个字符,
        // 所以表示成 16 进制需要 32 个字符
        int k = 0; // 表示转换结果中对应的字符位置
        for (int i = 0; i < 16; i++) { // 从第一个字节开始,对 MD5 的每一个字节
            // 转换成 16 进制字符的转换
            byte byte0 = tmp[i]; // 取第 i 个字节
            str[k++] = hexDigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换,
            // >>> 为逻辑右移,将符号位一起右移
            str[k++] = hexDigits[byte0 & 0xf]; // 取字节中低 4 位的数字转换
        }
        s = new String(str); // 换后的结果转换为字符串
        return s;
    }
}
oying-system/src/main/resources/config/application-dev.yml
@@ -178,10 +178,10 @@
  endpoint: https://obs.cn-southwest-2.myhuaweicloud.com
winner-look:
  url-send-msg: https://118.178.116.15:8443/winnerrxd/api/trigger/SendMsg
  url-send-msg-batch: https://118.178.116.15:8443/winnerrxd/api/trigger/SendBatchMsg
  user-code: CQLYSXYJ
  user-pass: lych1205!
  url-send-msg: https://118.178.116.15/winnerrxd
  token: e2318b05713e473890f79fe83d18e235
  user-code: SHOYYJ
  user-pass: Oy250928
# 支付类型
pay:
oying-system/src/main/resources/config/application-prod.yml
@@ -193,10 +193,10 @@
  endpoint: https://obs.cn-southwest-2.myhuaweicloud.com
winner-look:
  url-send-msg: https://118.178.116.15:8443/winnerrxd/api/trigger/SendMsg
  url-send-msg-batch: https://118.178.116.15:8443/winnerrxd/api/trigger/SendBatchMsg
  user-code: CQLYSXYJ
  user-pass: lych1205!
  url-send-msg: https://118.178.116.15/winnerrxd
  token: e2318b05713e473890f79fe83d18e235
  user-code: SHOYYJ
  user-pass: Oy250928
# 支付类型
pay:
oying-system/src/main/resources/config/application.yml
@@ -23,7 +23,7 @@
    check-template-location: false
  profiles:
    # 激活的环境,如果需要 quartz 分布式支持,需要修改 active: dev,quartz
    active: dev
    active: prod
  data:
    redis:
      repositories: