Bladeren bron

fix 微信支付,顺丰查询,敏感词过滤

Administrator 1 jaar geleden
bovenliggende
commit
736514003d

+ 9 - 3
pom.xml

@@ -8,9 +8,9 @@
     <artifactId>ruoyi</artifactId>
     <version>3.8.7</version>
 
-    <name>ruoyi</name>
-    <url>http://www.ruoyi.vip</url>
-    <description>若依管理系统</description>
+    <name>boman</name>
+    <url>http://www.bomankeji.com/</url>
+    <description>痘姆古陶</description>
     
     <properties>
         <ruoyi.version>3.8.7</ruoyi.version>
@@ -170,6 +170,12 @@
                 <version>${ruoyi.version}</version>
             </dependency>
 
+            <!--hutool-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>5.8.16</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 32 - 1
ruoyi-admin/pom.xml

@@ -16,7 +16,38 @@
     </description>
 
     <dependencies>
-
+        <!--顺丰依赖-->
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.9</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <version>1.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+            <version>4.3.2</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.7</version>
+        </dependency>
+        <!--顺丰的核心包(本地打包)-->
+        <dependency>
+            <groupId>com.sf.csim.express</groupId>
+            <artifactId>SF-CSIM-EXPRESS-SDF</artifactId>
+            <version>1.0.0</version>
+        </dependency>
         <!-- spring-boot-devtools -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 97 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/sf/CallExpressNewAPIServiceUtil.java

@@ -0,0 +1,97 @@
+package com.ruoyi.web.controller.sf;
+
+import cn.hutool.json.JSON;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.druid.support.json.JSONUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.sf.csim.express.service.CallExpressServiceTools;
+import com.sf.csim.express.service.HttpClientUtil;
+import com.sf.csim.express.service.IServiceCodeStandard;
+import com.sf.csim.express.service.code.ExpressServiceCodeEnum;
+
+import java.util.*;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/1/16 17:08
+ * @Describe:
+ */
+public class CallExpressNewAPIServiceUtil {
+
+    /**
+     * 丰桥新沙箱测试顾客编码  Yg4Zf06w_sxZs3A5D
+     * 校验码  3Xdk1jqeG1Xod9nUXus8Op7DNOkchTnw
+     **/
+    //此处替换为您在丰桥平台获取的顾客编码
+    private static final String CLIENT_CODE = "ZXYJS48Y74BY";
+    //生产校验码
+    private static final String CHECK_WORD = "BoSL7gKn7aFKht0E0OwEePsM1oo0oJxG";
+    //沙箱校验码
+    private static final String CHECK_WORD_BOX = "sl0E3IdF3leaRDEOQXKmSKUY1ybg36j9";
+
+    //沙箱环境的地址 -PRO
+    private static final String CALL_URL_BOX = "https://sfapi-sbox.sf-express.com/std/service";
+    //生产环境的地址 -PRO
+    private static final String CALL_URL_PROD = "https://bspgw.sf-express.com/std/service";
+    /**
+     * @Description: 调用顺丰API方法
+     * checkPhoneNo:电话号码验证手机号后四位
+     * trackingType: 顺丰运单号
+     * @Date: 2023/3/20 17:25
+     **/
+    public static JSONArray callSFApi(String checkPhoneNo ,String trackingType) throws Exception {
+                //验证请求参数
+        if (StringUtils.isEmpty(checkPhoneNo) || StringUtils.isEmpty(trackingType)) {
+            throw new Exception("参数不能为空!");
+        }
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("language", "zh-CN");
+        //查询号类别 1:根据顺丰运单号查询,trackingNumber将被当作顺丰运单号处理
+        jsonObject.put("trackingType", "1");
+        //快递单号
+        jsonObject.put("trackingNumber", trackingType);
+        //1:标准路由查询
+        jsonObject.put("methodType", "1");
+        //收件人手机号码后4位
+        jsonObject.put("checkPhoneNo", checkPhoneNo);
+        String msgData = jsonObject.toString();
+        IServiceCodeStandard standardService = ExpressServiceCodeEnum.EXP_RECE_SEARCH_ROUTES;
+        // set common header
+        Map<String, String> params = new HashMap<String, String>();
+        String timeStamp = String.valueOf(System.currentTimeMillis());
+        // 顾客编码 ,对应丰桥上获取的clientCode
+        params.put("partnerID", CLIENT_CODE);
+        //请求唯一号UUID
+        params.put("requestID", UUID.randomUUID().toString().replace("-", ""));
+        // 接口服务码
+        params.put("serviceCode", standardService.getCode());
+        //调用接口时间戳
+        params.put("timestamp", timeStamp);
+        //报文
+        params.put("msgData", msgData);
+        //数字签名
+        params.put("msgDigest", CallExpressServiceTools.getMsgDigest(msgData, timeStamp, CHECK_WORD));
+        long startTime = System.currentTimeMillis();
+        System.out.println("====调用实际请求:" + params);
+        String result = HttpClientUtil.post(CALL_URL_PROD, params);
+        System.out.println("====调用丰桥的接口服务代码:" + String.valueOf(standardService.getCode()) + " 接口耗时:"+ String.valueOf(System.currentTimeMillis()-startTime)+"====");
+        System.out.println("===调用地址 ===" + CALL_URL_PROD);
+        System.out.println("===顾客编码 ===" + CLIENT_CODE);
+        System.out.println("===返回结果:" + result);
+        JSONObject apiResultData = JSONUtil.parseObj(JSONUtil.parseObj(result).get("apiResultData"));
+        if(!apiResultData.getBool("success")){
+            throw new Exception("暂无物流信息");
+        }
+        JSONObject resultMsgData = JSONUtil.parseObj(apiResultData.get("msgData"));
+        JSONArray routeResps = JSONUtil.parseArray(resultMsgData.get("routeResps"));
+        JSONArray routes = routeResps.getJSONObject(0).getJSONArray("routes");
+        if (routes.isEmpty()){
+            throw new Exception("暂无物流信息");
+        }
+        //根据时间翻转
+        Collections.reverse(routes);
+        return routes;
+    }
+}

+ 45 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/wx/WxPayController.java

@@ -0,0 +1,45 @@
+package com.ruoyi.web.controller.wx;
+
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.system.domain.wx.WxPayOrderReqVo;
+import com.ruoyi.system.domain.wx.WxPayRespVo;
+import com.ruoyi.system.service.ISysPostService;
+import com.ruoyi.system.service.IWxPayService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/1/17 14:10
+ * @Describe:
+ */
+@RestController
+@RequestMapping("/wx/pay")
+public class WxPayController extends BaseController {
+
+    @Resource
+    private IWxPayService wxPayService;
+
+    @ApiOperation(value = "微信预支付", notes = "微信预支付")
+    @PostMapping("/createOrder")
+    public AjaxResult createOrder(@RequestBody @Validated WxPayOrderReqVo req) throws Exception {
+        return wxPayService.createOrder(req);
+    }
+
+    @ApiOperation(value = "微信支付回调", notes = "微信支付回调")
+    @PostMapping("/payNotify")
+    public AjaxResult payNotify(HttpServletRequest request){
+        //注意:回调接口需要暴露到公网上,且要放开token验证
+        wxPayService.payNotify(request);
+        return AjaxResult.success();
+    }
+}

+ 18 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -72,4 +72,21 @@ xss:
   # 排除链接(多个用逗号分隔)
   excludes: /system/notice
   # 匹配链接
-  urlPatterns: /system/*,/monitor/*,/tool/*
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 微信小程序支付配置信息
+wx:
+  # 微信小程序appid
+  app-id: xxxxx
+  # 商户号
+  mch-id: xxxx
+  # 证书序列号
+  mch-serial-no: xxxxx
+  # 小程序密钥
+  app-secret: xxxxxx
+  # api密钥
+  api-key: xxxxxxxx
+  # 回调接口地址
+  notify-url: https://xxxx/a/biz/wxpay/payNotify
+  # 证书地址
+  key-path: module-app/src/main/resources/cert/apiclient_key.pem

+ 7 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/RuoYiConfig.java

@@ -119,4 +119,11 @@ public class RuoYiConfig
     {
         return getProfile() + "/upload";
     }
+
+    /**
+     * 获取系统生成二维码保存路径
+     */
+    public static String getUploadQrPath() {
+        return getProfile() + "/qrUpload";
+    }
 }

+ 28 - 1
ruoyi-system/pom.xml

@@ -16,12 +16,39 @@
     </description>
 
     <dependencies>
-
+        <!--二维码-->
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>core</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.google.zxing</groupId>
+            <artifactId>javase</artifactId>
+            <version>3.1.0</version>
+        </dependency>
+        <!--微信支付-->
+        <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-java</artifactId>
+            <version>0.2.12</version>
+        </dependency>
+        <!--支付宝支付-->
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.38.192.ALL</version>
+        </dependency>
         <!-- 通用工具-->
         <dependency>
             <groupId>com.ruoyi</groupId>
             <artifactId>ruoyi-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fhs-opensource</groupId>
+            <artifactId>core</artifactId>
+            <version>1.0.0</version>
+        </dependency>
 
     </dependencies>
 

+ 81 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/wx/WxPayOrderReqVo.java

@@ -0,0 +1,81 @@
+package com.ruoyi.system.domain.wx;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 预支付请求类
+ * @Author: tjf
+ * @Date: 2024/1/17 14:04
+ * @Describe:
+ */
+
+public class WxPayOrderReqVo {
+
+    //附加数据,回调时可根据这个数据辨别订单类型或其他
+    @NotBlank(message = "订单支付类型不能为空!")
+    private String orderType;
+
+    @NotNull(message = "总金额不能为空!")
+    private Integer totalPrice;
+
+    @NotBlank(message = "商品名称不能为空!")
+    private String goodsName;
+
+    @NotBlank(message = "openId不能为空!")
+    private String openId;
+
+    @NotNull(message = "商品订单号不能为空!")
+    private Long orderSn;
+
+    public String getOrderType() {
+        return orderType;
+    }
+
+    public void setOrderType(String orderType) {
+        this.orderType = orderType;
+    }
+
+    public Integer getTotalPrice() {
+        return totalPrice;
+    }
+
+    public void setTotalPrice(Integer totalPrice) {
+        this.totalPrice = totalPrice;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public Long getOrderSn() {
+        return orderSn;
+    }
+
+    public void setOrderSn(Long orderSn) {
+        this.orderSn = orderSn;
+    }
+
+    @Override
+    public String toString() {
+        return "WxPayOrderReqVo{" +
+                "orderType='" + orderType + '\'' +
+                ", totalPrice=" + totalPrice +
+                ", goodsName='" + goodsName + '\'' +
+                ", openId='" + openId + '\'' +
+                ", orderSn=" + orderSn +
+                '}';
+    }
+}

+ 70 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/wx/WxPayRespVo.java

@@ -0,0 +1,70 @@
+package com.ruoyi.system.domain.wx;
+
+import java.io.Serializable;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/1/17 14:11
+ * @Describe:
+ */
+public class WxPayRespVo implements Serializable {
+    private static final long serialVersionUID = 1L;
+    /**
+     * 预支付交易会话标识小程序下单接口返回的prepay_id参数值
+     */
+    private String prepayId;
+    /**
+     * 随机字符串
+     */
+    private String nonceStr;
+    /**
+     * 时间戳
+     */
+    private Long timeStamp;
+    /**
+     * 签名
+     */
+    private String paySign;
+
+    public String getPrepayId() {
+        return prepayId;
+    }
+
+    public void setPrepayId(String prepayId) {
+        this.prepayId = prepayId;
+    }
+
+    public String getNonceStr() {
+        return nonceStr;
+    }
+
+    public void setNonceStr(String nonceStr) {
+        this.nonceStr = nonceStr;
+    }
+
+    public Long getTimeStamp() {
+        return timeStamp;
+    }
+
+    public void setTimeStamp(Long timeStamp) {
+        this.timeStamp = timeStamp;
+    }
+
+    public String getPaySign() {
+        return paySign;
+    }
+
+    public void setPaySign(String paySign) {
+        this.paySign = paySign;
+    }
+
+    @Override
+    public String toString() {
+        return "WxPayRespVo{" +
+                "prepayId='" + prepayId + '\'' +
+                ", nonceStr='" + nonceStr + '\'' +
+                ", timeStamp=" + timeStamp +
+                ", paySign='" + paySign + '\'' +
+                '}';
+    }
+}

+ 96 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/wx/WxPayV3Bean.java

@@ -0,0 +1,96 @@
+package com.ruoyi.system.domain.wx;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 微信支付获取配置信息
+ * @Author: tjf
+ * @Date: 2024/1/17 14:01
+ */
+@Component
+@ConfigurationProperties(prefix = "wx")
+public class WxPayV3Bean {
+    private String appId;
+
+    private String mchId;
+
+    private String mchSerialNo;
+
+    private String appSecret;
+
+    private String apiKey;
+
+    private String notifyUrl;
+
+    private String keyPath;
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getMchId() {
+        return mchId;
+    }
+
+    public void setMchId(String mchId) {
+        this.mchId = mchId;
+    }
+
+    public String getMchSerialNo() {
+        return mchSerialNo;
+    }
+
+    public void setMchSerialNo(String mchSerialNo) {
+        this.mchSerialNo = mchSerialNo;
+    }
+
+    public String getAppSecret() {
+        return appSecret;
+    }
+
+    public void setAppSecret(String appSecret) {
+        this.appSecret = appSecret;
+    }
+
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    public void setApiKey(String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+    public String getNotifyUrl() {
+        return notifyUrl;
+    }
+
+    public void setNotifyUrl(String notifyUrl) {
+        this.notifyUrl = notifyUrl;
+    }
+
+    public String getKeyPath() {
+        return keyPath;
+    }
+
+    public void setKeyPath(String keyPath) {
+        this.keyPath = keyPath;
+    }
+
+    @Override
+    public String toString() {
+        return "WxPayOrderReqVo{" +
+                "appId='" + appId + '\'' +
+                ", mchId='" + mchId + '\'' +
+                ", mchSerialNo='" + mchSerialNo + '\'' +
+                ", appSecret='" + appSecret + '\'' +
+                ", apiKey='" + apiKey + '\'' +
+                ", notifyUrl='" + notifyUrl + '\'' +
+                ", keyPath='" + keyPath + '\'' +
+                '}';
+    }
+}

+ 26 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/IWxPayService.java

@@ -0,0 +1,26 @@
+package com.ruoyi.system.service;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.system.domain.wx.WxPayOrderReqVo;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/1/17 14:15
+ * @Describe:
+ */
+public interface IWxPayService {
+    /**
+     * 创建微信预支付订单
+     * @param req
+     * @return
+     */
+    AjaxResult createOrder(WxPayOrderReqVo req) throws Exception;;
+    /**
+     * 支付后回调地址
+     * @param request
+     * @return
+     */
+    void payNotify(HttpServletRequest request);
+}

+ 150 - 0
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WxPayServiceImpl.java

@@ -0,0 +1,150 @@
+package com.ruoyi.system.service.impl;
+
+
+
+import com.fhs.core.exception.ResultException;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.system.domain.wx.WxPayOrderReqVo;
+import com.ruoyi.system.domain.wx.WxPayRespVo;
+import com.ruoyi.system.domain.wx.WxPayV3Bean;
+import com.ruoyi.system.service.IWxPayService;
+import com.wechat.pay.java.core.Config;
+import com.wechat.pay.java.core.RSAAutoCertificateConfig;
+import com.wechat.pay.java.core.notification.NotificationConfig;
+import com.wechat.pay.java.core.notification.NotificationParser;
+import com.wechat.pay.java.core.notification.RequestParam;
+import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
+import com.wechat.pay.java.service.payments.jsapi.JsapiService;
+import com.wechat.pay.java.service.payments.jsapi.model.Amount;
+import com.wechat.pay.java.service.payments.jsapi.model.Payer;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
+import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
+import utils.WxPayUtil;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletInputStream;
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.ruoyi.common.constant.Constants.LOGIN_FAIL;
+import static com.wechat.pay.java.core.http.Constant.*;
+
+/**
+ * @Author: tjf
+ * @Date: 2024/1/17 14:16
+ * @Describe:
+ */
+public class WxPayServiceImpl implements IWxPayService {
+
+    @Resource
+    private WxPayV3Bean wxPayV3Bean;
+    /**
+     * 创建微信预支付订单
+     * @param req
+     * @return
+     */
+    @Override
+    public AjaxResult createOrder(WxPayOrderReqVo req) throws Exception {
+        try {
+            // 使用自动更新平台证书的RSA配置
+            // 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
+            Config config =
+                    new RSAAutoCertificateConfig.Builder()
+                            .merchantId(wxPayV3Bean.getMchId())
+                            .privateKeyFromPath(wxPayV3Bean.getKeyPath())
+                            .merchantSerialNumber(wxPayV3Bean.getMchSerialNo())
+                            .apiV3Key(wxPayV3Bean.getApiKey())
+                            .build();
+            // 构建service
+            JsapiService service = new JsapiService.Builder().config(config).build();
+            // request.setXxx(val)设置所需参数,具体参数可见Request定义
+            PrepayRequest request = new PrepayRequest();
+            Amount amount = new Amount();
+            amount.setTotal(req.getTotalPrice());
+            request.setAmount(amount);
+            request.setAppid(wxPayV3Bean.getAppId());
+            request.setMchid(wxPayV3Bean.getMchId());
+            request.setDescription(req.getGoodsName());
+            request.setNotifyUrl(wxPayV3Bean.getNotifyUrl());
+            request.setOutTradeNo(req.getOrderSn().toString());
+            request.setAttach(req.getOrderType());
+            Payer payer = new Payer();
+            payer.setOpenid(req.getOpenId());
+            request.setPayer(payer);
+            // 调用下单方法,得到应答
+            PrepayResponse response = service.prepay(request);
+            WxPayRespVo vo = new WxPayRespVo();
+            Long timeStamp = System.currentTimeMillis() / 1000;
+            vo.setTimeStamp(timeStamp);
+            String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
+            vo.setNonceStr(substring);
+            String signatureStr = Stream.of(wxPayV3Bean.getAppId(), String.valueOf(timeStamp), substring, "prepay_id=" + response.getPrepayId())
+                    .collect(Collectors.joining("\n", "", "\n"));
+            String sign = WxPayUtil.getSign(signatureStr, wxPayV3Bean.getKeyPath());
+            vo.setPaySign(sign);
+            vo.setPrepayId("prepay_id=" + response.getPrepayId());
+            //todo 存储预支付订单信息
+            return AjaxResult.success(vo);
+        }catch (ServiceException e){
+            System.out.println("创建微信预支付订单失败");
+
+        }
+        return AjaxResult.error();
+    }
+
+    /**
+     * 微信支付回调
+     * @param request
+     * @return
+     */
+    @Override
+    public void payNotify(HttpServletRequest request) {
+        try {
+            //读取请求体的信息
+            ServletInputStream inputStream = request.getInputStream();
+            StringBuffer stringBuffer = new StringBuffer();
+            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+            String s;
+            //读取回调请求体
+            while ((s = bufferedReader.readLine()) != null) {
+                stringBuffer.append(s);
+            }
+            String s1 = stringBuffer.toString();
+            String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
+            String nonce = request.getHeader(WECHAT_PAY_NONCE);
+            String signType = request.getHeader("Wechatpay-Signature-Type");
+            String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
+            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
+            // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
+            // 没有的话,则构造一个
+           // log.error(com.alibaba.fastjson2.JSON.toJSONString(wxPayV3Bean));
+            NotificationConfig config = new RSAAutoCertificateConfig.Builder()
+                    .merchantId(wxPayV3Bean.getMchId())
+                    .privateKeyFromPath(wxPayV3Bean.getKeyPath())
+                    .merchantSerialNumber(wxPayV3Bean.getMchSerialNo())
+                    .apiV3Key(wxPayV3Bean.getApiKey())
+                    .build();
+            // 初始化 NotificationParser
+            NotificationParser parser = new NotificationParser(config);
+            RequestParam requestParam=new RequestParam.Builder()
+                    .serialNumber(serialNo)
+                    .nonce(nonce)
+                    .signature(signature)
+                    .timestamp(timestamp)
+                    // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
+                    .signType(signType)
+                    .body(s1)
+                    .build();
+            Transaction parse = parser.parse(requestParam, Transaction.class);
+            //todo 存储支付结果
+            System.out.println("parse = " + parse);
+        } catch (Exception e) {
+            System.out.println("获取微信支付回调失败");
+        }
+    }
+}

+ 148 - 0
ruoyi-system/src/main/java/utils/BadWordsUtil.java

@@ -0,0 +1,148 @@
+package utils;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 敏感词审核工具类
+ * @author tjf
+ */
+
+public class BadWordsUtil {
+
+    public static final int WORDS_MAX_LENGTH = 10;
+
+    // 我们自己的敏感词库
+    public static final String BAD_WORDS_LIB_FILE_NAME = "badWord.txt";
+
+    //敏感词列表
+    public static Map<String, String>[] badWordsList = null;
+
+    //敏感词索引
+    public static Map<String, Integer> wordIndex = new HashMap<>();
+
+    /**
+
+     * 初始化敏感词库
+
+     */
+
+    public static void initbadWordsList() throws IOException {
+
+        if (badWordsList == null) {
+
+            badWordsList = new Map[WORDS_MAX_LENGTH];
+
+            for (int i = 0; i < badWordsList.length; i++) {
+
+                badWordsList[i] = new HashMap<>();
+
+            }
+
+        }
+
+        //敏感词词库所在目录,这里为txt文本,一个敏感词一行
+        String path = BadWordsUtil.class.getClassLoader()
+
+            .getResource(BAD_WORDS_LIB_FILE_NAME)
+
+            .getPath();
+
+        System.out.println(path);
+
+        List<String> words = FileUtils.readLines(new File(path), "UTF-8");
+
+        for (String w : words) {
+
+            if (StringUtils.isNotBlank(w)) {
+
+                //将敏感词按长度存入map
+
+                badWordsList[w.length()].put(w, "");
+
+                Integer index = wordIndex.get(w.substring(0, 1));
+
+                //生成敏感词索引,存入map
+
+                if (index == null) {
+
+                    index = 0;
+
+                }
+
+                int x = (int) Math.pow(2, w.length());
+
+                index = (index | x);
+
+                wordIndex.put(w.substring(0, 1), index);
+
+            }
+
+        }
+
+    }
+
+    /**
+     * 检索敏感词
+     *
+     * @param content 你是大傻叉
+     * @return
+     */
+
+    public static String searchBanWords(String content) {
+
+        if (badWordsList == null) {
+
+            try {
+
+                initbadWordsList();
+
+            } catch (IOException e) {
+
+                throw new RuntimeException(e);
+
+            }
+
+        }
+
+        for (int i = 0; i < content.length(); i++) {
+
+            Integer index = wordIndex.get(content.substring(i, i + 1));
+
+            int p = 0;
+
+            while ((index != null) && (index > 0)) {
+
+                p++;
+
+                index = index >> 1;
+
+                String sub = "";
+
+                if ((i + p) < (content.length() - 1)) {
+
+                    sub = content.substring(i, i + p);
+
+                } else {
+
+                    sub = content.substring(i);
+
+                }
+
+                if (((index % 2) == 1) && badWordsList[p].containsKey(sub)) {
+                    content = content.replace(sub, "**");
+                }
+            }
+
+        }
+
+        return content;
+
+    }
+}

+ 240 - 0
ruoyi-system/src/main/java/utils/QRCodeUtils.java

@@ -0,0 +1,240 @@
+package utils;
+
+import com.google.zxing.*;
+import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.uuid.UUID;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.geom.RoundRectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.OutputStream;
+import java.util.Hashtable;
+
+/**
+ * @author tjf
+ * @Date: 2021/12/29/15:09
+ */
+public class QRCodeUtils {
+    private static final String CHARSET = "utf-8";
+    private static final String FORMAT_NAME = "JPG";
+    // 二维码尺寸
+    private static final int QRCODE_SIZE = 300;
+    // LOGO宽度
+    private static final int WIDTH = 60;
+    // LOGO高度
+    private static final int HEIGHT = 60;
+
+    private static BufferedImage createImage(String content, String imgPath,
+                                             boolean needCompress) throws Exception {
+        Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
+        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
+        hints.put(EncodeHintType.MARGIN, 1);
+        BitMatrix bitMatrix = new MultiFormatWriter().encode(content,
+                BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
+        int width = bitMatrix.getWidth();
+        int height = bitMatrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000
+                        : 0xFFFFFFFF);
+            }
+        }
+        if (imgPath == null || "".equals(imgPath)) {
+            return image;
+        }
+        // 插入图片
+        QRCodeUtils.insertImage(image, imgPath, needCompress);
+        return image;
+    }
+
+    /**
+     * 插入LOGO
+     *
+     * @param source       二维码图片
+     * @param imgPath      LOGO图片地址
+     * @param needCompress 是否压缩
+     * @throws Exception
+     */
+    private static void insertImage(BufferedImage source, String imgPath,
+                                    boolean needCompress) throws Exception {
+        File file = new File(imgPath);
+        if (!file.exists()) {
+            System.err.println("" + imgPath + "   该文件不存在!");
+            return;
+        }
+        Image src = ImageIO.read(new File(imgPath));
+        int width = src.getWidth(null);
+        int height = src.getHeight(null);
+        if (needCompress) { // 压缩LOGO
+            if (width > WIDTH) {
+                width = WIDTH;
+            }
+            if (height > HEIGHT) {
+                height = HEIGHT;
+            }
+            Image image = src.getScaledInstance(width, height,
+                    Image.SCALE_SMOOTH);
+            BufferedImage tag = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_RGB);
+            Graphics g = tag.getGraphics();
+            g.drawImage(image, 0, 0, null); // 绘制缩小后的图
+            g.dispose();
+            src = image;
+        }
+        // 插入LOGO
+        Graphics2D graph = source.createGraphics();
+        int x = (QRCODE_SIZE - width) / 2;
+        int y = (QRCODE_SIZE - height) / 2;
+        graph.drawImage(src, x, y, width, height, null);
+        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
+        graph.setStroke(new BasicStroke(3f));
+        graph.draw(shape);
+        graph.dispose();
+    }
+
+    /**
+     * 生成二维码(内嵌LOGO)
+     *
+     * @param content      内容
+     * @param imgPath      LOGO地址
+     * @param destPath     存放目录
+     * @param needCompress 是否压缩LOGO
+     * @throws Exception
+     */
+    public static String encode(String content, String imgPath, String destPath,
+                                boolean needCompress) throws Exception {
+        BufferedImage image = QRCodeUtils.createImage(content, imgPath,
+                needCompress);
+        String date = DateUtils.datePath();
+        destPath = destPath+"/" + date;
+        mkdirs(destPath);
+        String file = UUID.randomUUID() + ".jpg";
+        File fileQR = new File(destPath + "/" + file);
+        ImageIO.write(image, FORMAT_NAME, fileQR);
+        return "/profile/qrUpload/"+date+"/"+file;
+    }
+
+
+    /**
+     * 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
+     *
+     * @param destPath 存放目录
+     * @date 2013-12-11 上午10:16:36
+     */
+    public static void mkdirs(String destPath) {
+        File file = new File(destPath);
+        //当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
+        if (!file.exists() && !file.isDirectory()) {
+            file.mkdirs();
+        }
+    }
+
+    /**
+     * 生成二维码(内嵌LOGO)
+     *
+     * @param content  内容
+     * @param imgPath  LOGO地址
+     * @param destPath 存储地址
+     * @throws Exception
+     */
+    public static void encode(String content, String imgPath, String destPath)
+            throws Exception {
+        QRCodeUtils.encode(content, imgPath, destPath, false);
+    }
+
+    /**
+     * 生成二维码
+     *
+     * @param content      内容
+     * @param destPath     存储地址
+     * @param needCompress 是否压缩LOGO
+     * @throws Exception
+     */
+    public static void encode(String content, String destPath,
+                              boolean needCompress) throws Exception {
+        QRCodeUtils.encode(content, null, destPath, needCompress);
+    }
+
+    /**
+     * 生成二维码
+     *
+     * @param content  内容
+     * @param destPath 存储地址
+     * @throws Exception
+     */
+    public static void encode(String content, String destPath) throws Exception {
+        QRCodeUtils.encode(content, null, destPath, false);
+    }
+
+    /**
+     * 生成二维码(内嵌LOGO)
+     *
+     * @param content      内容
+     * @param imgPath      LOGO地址
+     * @param output       输出流
+     * @param needCompress 是否压缩LOGO
+     * @throws Exception
+     */
+    public static void encode(String content, String imgPath,
+                              OutputStream output, boolean needCompress) throws Exception {
+        BufferedImage image = QRCodeUtils.createImage(content, imgPath,
+                needCompress);
+        ImageIO.write(image, FORMAT_NAME, output);
+    }
+
+    /**
+     * 生成二维码
+     *
+     * @param content 内容
+     * @param output  输出流
+     * @throws Exception
+     */
+    public static void encode(String content, OutputStream output)
+            throws Exception {
+        QRCodeUtils.encode(content, null, output, false);
+    }
+
+    /**
+     * 解析二维码
+     *
+     * @param file 二维码图片
+     * @return
+     * @throws Exception
+     */
+    public static String decode(File file) throws Exception {
+        BufferedImage image;
+        image = ImageIO.read(file);
+        if (image == null) {
+            return null;
+        }
+        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(
+                image);
+        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+        Result result;
+        Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
+        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
+        result = new MultiFormatReader().decode(bitmap, hints);
+        String resultStr = result.getText();
+        return resultStr;
+    }
+
+    /**
+     * 解析二维码
+     *
+     * @param path 二维码图片地址
+     * @return
+     * @throws Exception
+     */
+    public static String decode(String path) throws Exception {
+        return QRCodeUtils.decode(new File(path));
+    }
+}

+ 50 - 0
ruoyi-system/src/main/java/utils/WxPayUtil.java

@@ -0,0 +1,50 @@
+package utils;
+
+import com.wechat.pay.java.core.util.PemUtil;
+import org.springframework.util.Base64Utils;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Signature;
+import java.util.Random;
+
+/**
+ * 微信支付工具类
+ * @Author: tjf
+ * @Date: 2024/1/17 14:07
+ * @Describe:
+ */
+public class WxPayUtil {
+
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    private static final Random RANDOM = new SecureRandom();
+
+
+    public static String getSign(String signatureStr,String privateKey) throws Exception {
+        //replace 根据实际情况,不一定都需要
+        String replace = privateKey.replace("\\n", "\n");
+        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromPath(replace);
+        Signature sign = Signature.getInstance("SHA256withRSA");
+        sign.initSign(merchantPrivateKey);
+        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
+        return Base64Utils.encodeToString(sign.sign());
+    }
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        char[] nonceChars = new char[32];
+        for (int index = 0; index < nonceChars.length; ++index) {
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+        }
+        return new String(nonceChars);
+    }
+}

+ 41 - 0
ruoyi-system/src/main/resources/badWord.txt

@@ -0,0 +1,41 @@
+sb
+SB
+傻逼
+自杀
+恨你
+发泄
+去死
+JB
+死
+傻 逼
+MB
+草泥马
+叼
+贱
+強奸
+Make Love
+TMD
+销魂
+人渣
+王八蛋
+贱逼
+末日
+全脱
+脱光
+屁
+脑残
+Fuck
+F u c k
+王八
+法轮功
+法 轮 功
+法.轮.功
+尻
+操你
+我日
+日你
+日他
+草你妈
+屁
+打到共产党
+打倒共产党