liusheng
2 天以前 4c0da4f99ca97d2cfcaa00fd5cd9c3d69d089bfa
获取subid
已添加2个文件
304 ■■■■■ 文件已修改
ruoyi-common/src/main/java/com/ruoyi/common/utils/AesUtils.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/WxMpUtils.java 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/AesUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,129 @@
package com.ruoyi.common.utils;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
 * AES åŠ è§£å¯†å·¥å…·ç±»
 * ç®—法:AES/CBC/PKCS5Padding(等效 PKCS7,128 ä½æ•°æ®å—)
 * è¾“出:HEX å¤§å†™å­—符串
 * å­—符集:UTF-8
 */
public class AesUtils {
    /** ç®—法/模式/填充 */
    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    /** é»˜è®¤ Key(HEX,32字节→256位密钥) */
    private static final String DEFAULT_KEY = "0F471C56362408AF8DB929C38EDFD23C";
    /** é»˜è®¤ IV(HEX,16字节→128位偏移量) */
    private static final String DEFAULT_IV = "11BEE6E35B881A33CF1649607295D1A7";
    private AesUtils() {
    }
    // -------------------------------------------------------------------------
    // å…¬å…± API
    // -------------------------------------------------------------------------
    /**
     * ä½¿ç”¨é»˜è®¤ Key / IV åР坆
     *
     * @param plainText æ˜Žæ–‡
     * @return HEX å¤§å†™å¯†æ–‡
     */
    public static String encrypt(String plainText) {
        return encrypt(plainText, DEFAULT_KEY, DEFAULT_IV);
    }
    /**
     * ä½¿ç”¨é»˜è®¤ Key / IV è§£å¯†
     *
     * @param hexCipherText HEX å¤§å†™å¯†æ–‡
     * @return æ˜Žæ–‡
     */
    public static String decrypt(String hexCipherText) {
        return decrypt(hexCipherText, DEFAULT_KEY, DEFAULT_IV);
    }
    /**
     * AES-CBC åР坆
     *
     * @param plainText  æ˜Žæ–‡
     * @param hexKey     HEX æ ¼å¼çš„密钥(16/24/32字节对应128/192/256位)
     * @param hexIv      HEX æ ¼å¼çš„偏移量(16字节)
     * @return HEX å¤§å†™å¯†æ–‡
     */
    public static String encrypt(String plainText, String hexKey, String hexIv) {
        try {
            byte[] keyBytes = hexToBytes(hexKey);
            byte[] ivBytes = hexToBytes(hexIv);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(encrypted).toUpperCase();
        } catch (Exception e) {
            throw new RuntimeException("AES åŠ å¯†å¤±è´¥", e);
        }
    }
    /**
     * AES-CBC è§£å¯†
     *
     * @param hexCipherText HEX å¤§å†™å¯†æ–‡
     * @param hexKey        HEX æ ¼å¼çš„密钥
     * @param hexIv         HEX æ ¼å¼çš„偏移量
     * @return æ˜Žæ–‡
     */
    public static String decrypt(String hexCipherText, String hexKey, String hexIv) {
        try {
            byte[] keyBytes = hexToBytes(hexKey);
            byte[] ivBytes = hexToBytes(hexIv);
            byte[] cipherBytes = hexToBytes(hexCipherText);
            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
            byte[] decrypted = cipher.doFinal(cipherBytes);
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("AES è§£å¯†å¤±è´¥", e);
        }
    }
    // -------------------------------------------------------------------------
    // ç§æœ‰è¾…助方法
    // -------------------------------------------------------------------------
    /**
     * HEX å­—符串转字节数组(大小写均可)
     */
    private static byte[] hexToBytes(String hex) {
        if (hex == null || hex.length() % 2 != 0) {
            throw new IllegalArgumentException("非法 HEX å­—符串:" + hex);
        }
        int len = hex.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
                    + Character.digit(hex.charAt(i + 1), 16));
        }
        return data;
    }
    /**
     * å­—节数组转 HEX å­—符串(小写,调用方统一转大写)
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/WxMpUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
package com.ruoyi.common.utils;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import java.util.Map;
/**
 * å¾®ä¿¡å…¬ä¼—号消息发送工具类
 * æ”¯æŒï¼šæ–‡æœ¬å®¢æœæ¶ˆæ¯ã€æ¨¡æ¿æ¶ˆæ¯
 */
@Slf4j
public class WxMpUtils {
    private final WxMpService wxMpService;
    /**
     * æž„造方法,通过 appId å’Œ appSecret åˆå§‹åŒ–微信公众号服务
     *
     * @param appId     å¾®ä¿¡å…¬ä¼—号 AppID
     * @param appSecret å¾®ä¿¡å…¬ä¼—号 AppSecret
     */
    public WxMpUtils(String appId, String appSecret) {
        WxMpInMemoryConfigStorage configStorage = new WxMpInMemoryConfigStorage();
        configStorage.setAppId(appId);
        configStorage.setSecret(appSecret);
        wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(configStorage);
    }
    // -------------------------------------------------------------------------
    // æ–‡æœ¬å®¢æœæ¶ˆæ¯
    // -------------------------------------------------------------------------
    /**
     * å‘指定 openid çš„用户发送文本客服消息
     * æ³¨æ„ï¼šç”¨æˆ·éœ€åœ¨48小时内与公众号有过交互才可发送
     *
     * @param openid  ç”¨æˆ·çš„微信 openid
     * @param content æ¶ˆæ¯å†…容
     * @return å‘送成功返回 true,失败返回 false
     */
    public boolean sendTextMessage(String openid, String content) {
        if (StringUtils.isEmpty(openid)) {
            log.warn("【WxMpUtils】sendTextMessage å¤±è´¥ï¼šopenid ä¸ºç©º");
            return false;
        }
        if (StringUtils.isEmpty(content)) {
            log.warn("【WxMpUtils】sendTextMessage å¤±è´¥ï¼šcontent ä¸ºç©º");
            return false;
        }
        try {
            WxMpKefuMessage message = WxMpKefuMessage.TEXT()
                    .toUser(openid)
                    .content(content)
                    .build();
            boolean result = wxMpService.getKefuService().sendKefuMessage(message);
            log.info("【WxMpUtils】文本客服消息发送{},openid:{}", result ? "成功" : "失败", openid);
            return result;
        } catch (WxErrorException e) {
            log.error("【WxMpUtils】文本客服消息发送异常,openid:{},错误:{}", openid, e.getError(), e);
            return false;
        }
    }
    // -------------------------------------------------------------------------
    // æ¨¡æ¿æ¶ˆæ¯
    // -------------------------------------------------------------------------
    /**
     * å‘指定 openid çš„用户发送模板消息(含跳转URL)
     *
     * @param openid     ç”¨æˆ·çš„微信 openid
     * @param templateId æ¨¡æ¿æ¶ˆæ¯ ID(在微信公众平台配置)
     * @param url        ç‚¹å‡»æ¶ˆæ¯åŽçš„跳转 URL,不需要跳转可传 null
     * @param dataMap    æ¨¡æ¿æ•°æ®ï¼Œkey ä¸ºæ¨¡æ¿å­—段名,value ä¸º WxMpTemplateData(含值和颜色)
     *                   ç¤ºä¾‹ï¼š{"first": new WxMpTemplateData("first", "你好", "#173177"), ...}
     * @return å‘送成功返回微信返回的 msgid,失败返回 null
     */
    public String sendTemplateMessage(String openid, String templateId, String url,
                                      Map<String, WxMpTemplateData> dataMap) {
        if (StringUtils.isEmpty(openid)) {
            log.warn("【WxMpUtils】sendTemplateMessage å¤±è´¥ï¼šopenid ä¸ºç©º");
            return null;
        }
        if (StringUtils.isEmpty(templateId)) {
            log.warn("【WxMpUtils】sendTemplateMessage å¤±è´¥ï¼štemplateId ä¸ºç©º");
            return null;
        }
        try {
            WxMpTemplateMessage templateMessage = new WxMpTemplateMessage();
            templateMessage.setToUser(openid);
            templateMessage.setTemplateId(templateId);
            if (StringUtils.isNotEmpty(url)) {
                templateMessage.setUrl(url);
            }
            if (dataMap != null) {
                for (WxMpTemplateData data : dataMap.values()) {
                    templateMessage.addData(data);
                }
            }
            String msgId = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
            log.info("【WxMpUtils】模板消息发送成功,openid:{},msgId:{}", openid, msgId);
            return msgId;
        } catch (WxErrorException e) {
            log.error("【WxMpUtils】模板消息发送异常,openid:{},templateId:{},错误:{}",
                    openid, templateId, e.getError(), e);
            return null;
        }
    }
    /**
     * å‘指定 openid çš„用户发送模板消息(简化版,value ä¸è®¾ç½®é¢œè‰²ï¼‰
     *
     * @param openid     ç”¨æˆ·çš„微信 openid
     * @param templateId æ¨¡æ¿æ¶ˆæ¯ ID
     * @param url        ç‚¹å‡»æ¶ˆæ¯åŽçš„跳转 URL,不需要跳转可传 null
     * @param dataMap    æ¨¡æ¿æ•°æ®ï¼Œkey ä¸ºæ¨¡æ¿å­—段名,value ä¸ºå­—段显示的文本内容
     *                   ç¤ºä¾‹ï¼š{"first": "你好", "keyword1": "2024-01-01", "remark": "感谢使用"}
     * @return å‘送成功返回微信返回的 msgid,失败返回 null
     */
    public String sendTemplateMessage(String openid, String templateId, String url,
                                      Map<String, String> dataMap, String defaultColor) {
        if (StringUtils.isEmpty(openid)) {
            log.warn("【WxMpUtils】sendTemplateMessage å¤±è´¥ï¼šopenid ä¸ºç©º");
            return null;
        }
        if (StringUtils.isEmpty(templateId)) {
            log.warn("【WxMpUtils】sendTemplateMessage å¤±è´¥ï¼štemplateId ä¸ºç©º");
            return null;
        }
        try {
            WxMpTemplateMessage templateMessage = new WxMpTemplateMessage();
            templateMessage.setToUser(openid);
            templateMessage.setTemplateId(templateId);
            if (StringUtils.isNotEmpty(url)) {
                templateMessage.setUrl(url);
            }
            if (dataMap != null) {
                String color = StringUtils.isEmpty(defaultColor) ? "#173177" : defaultColor;
                for (Map.Entry<String, String> entry : dataMap.entrySet()) {
                    templateMessage.addData(new WxMpTemplateData(entry.getKey(), entry.getValue(), color));
                }
            }
            String msgId = wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
            log.info("【WxMpUtils】模板消息发送成功,openid:{},msgId:{}", openid, msgId);
            return msgId;
        } catch (WxErrorException e) {
            log.error("【WxMpUtils】模板消息发送异常,openid:{},templateId:{},错误:{}",
                    openid, templateId, e.getError(), e);
            return null;
        }
    }
    // -------------------------------------------------------------------------
    // é™æ€å·¥åŽ‚æ–¹æ³•ï¼ˆå¿«é€Ÿæž„é€ ï¼‰
    // -------------------------------------------------------------------------
    /**
     * å¿«é€Ÿåˆ›å»º WxMpUtils å®žä¾‹
     *
     * @param appId     å¾®ä¿¡å…¬ä¼—号 AppID
     * @param appSecret å¾®ä¿¡å…¬ä¼—号 AppSecret
     * @return WxMpUtils å®žä¾‹
     */
    public static WxMpUtils of(String appId, String appSecret) {
        return new WxMpUtils(appId, appSecret);
    }
}