微信小程序集成b2b交易代码

vue:

// 微信支付,将参数传后端获取数据掉起微信支付
                        wxPayOrder(orderData).then(response => {
                            let params = response.data
                            wx.requestCommonPayment({
                                signData: params.signData,
                                mode: params.mode,
                                paySig: params.paySig,
                                signature: params.signature,
                                success(res) {
                                    console.log('支付成功', res);
                                    this.paySuccess()
                                },
                                fail(res) {
                                    console.log('支付失败,res', res);
                                    uni.showToast({
                                        title: '支付失败,如果您已支付,请勿反复支付',
                                        icon: 'none'
                                    })
                                }
                            });

java

    //微信支付下单统一参数封装
            Map<String, Object> payParams = Maps.newHashMapWithExpectedSize(CommonConstants.INTEGER_TYPE.TYPE_EIGHT);

            payParams.put("mchid", wxConfig.getB2bMchId());

            payParams.put("description", request.getOrderName());
            payParams.put("out_trade_no", DateUtil.getDateline() + "b2b" + request.getOutTradeNo());
            Map<String, Object> outTradeNoParams = Maps.newHashMapWithExpectedSize(CommonConstants.INTEGER_TYPE.TYPE_ONE);
            outTradeNoParams.put("out_trade_no", request.getOutTradeNo());
            payParams.put("attach", JsonUtils.toJsonString(outTradeNoParams));
            Map<String, Object> amountMap = Maps.newHashMapWithExpectedSize(CommonConstants.INTEGER_TYPE.TYPE_ONE);
            amountMap.put("order_amount", MoneyUtils.yuanToFen(request.getFee()));
            payParams.put("amount", amountMap);
            payParams.put("env", 0);
            String uri = "requestCommonPayment";
            String url2 = uri + "&" + JSON.toJSONString(payParams);

            Map<String, Object> connect = remoteConnectService.queryConnect(
                LoginHelper.getUserId() + "", SourceEnum.WECHAT_MP_OPEN_ID.name()
            );
            if (connect == null) {
                log.error("未获取到用户{}:{}登录信息", LoginHelper.getUserId() + "", SourceEnum.WECHAT_MP_OPEN_ID.name());
                return null;
            }

            String paramsStr = JSON.toJSONString(payParams);
            log.info("统一下单API,支付参数:{}", paramsStr);

            PayResponse payResponse = new PayResponse();
            payResponse.setSignData(paramsStr);
            payResponse.setMode(wxConfig.getB2bMode());
            payResponse.setPaySig(HmacSha256Util.hmacSha256Hex(wxConfig.getB2bAppKey(), url2));
            payResponse.setSignature(HmacSha256Util.hmacSha256Hex(connect.get("sessionKey").toString(), paramsStr));

            log.info("统一下单最后返回支付参数:{}", JSON.toJSONString(payResponse));

编码工具类:

package com.honghan.common.core.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

public class HmacSha256Util {
    public static String hmacSha256Hex(String key, String data) {
        try {
            Mac sha256Hmac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256Hmac.init(secretKey);

            byte[] hashBytes = sha256Hmac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hashBytes);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Error while generating HMAC-SHA256", e);
        }
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1)
                hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }

    // Example usage:
    public static void main(String[] args) {
        String sessionKey = "yourSessionKey";
        String signData = "yourSignData";
        String signature = hmacSha256Hex(sessionKey, signData);
        System.out.println("Signature: " + signature);
    }
}

b2b支付回调通知已经请求加解密方法:

package com.honghan.trade.controller.pay;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.honghan.common.core.constant.CommonConstants;
import com.honghan.common.core.constant.Constants;
import com.honghan.common.core.domain.ResultResponse;
import com.honghan.common.core.enums.OrderStatus;
import com.honghan.common.core.exception.ServiceException;
import com.honghan.common.core.utils.HttpUtils;
import com.honghan.common.core.utils.TimeUtils;
import com.honghan.common.core.utils.ajax.AjaxUtils;
import com.honghan.common.core.utils.copy.CopyUtils;
import com.honghan.mall.api.RemoteProductSkuStockService;
import com.honghan.site.api.RemoteCompanyService;
import com.honghan.store.api.RemoteStoreOrderService;
import com.honghan.store.api.RemoteStoreStockService;
import com.honghan.system.api.RemoteSysConfigService;
import com.honghan.trade.api.call.PaySuccessCallback;
import com.honghan.trade.api.call.ResultWechatBack;
import com.honghan.trade.api.domain.PaymentSuccessParamsVO;
import com.honghan.trade.api.domain.TradeOrder;
import com.honghan.trade.api.domain.pay.PayResponse;
import com.honghan.trade.config.pay.config.WxConfig;
import com.honghan.trade.config.pay.sign.WechatPayValidator;
import com.honghan.trade.domain.vo.TradeOrderVO;
import com.honghan.trade.payment.RefundLogService;
import com.honghan.trade.payment.entity.RefundLog;
import com.honghan.trade.payment.entity.enums.PaymentMethodEnum;
import com.honghan.trade.payment.param.PaymentSuccessParams;
import com.honghan.trade.service.*;
import com.honghan.trade.service.pay.AliPayService;
import com.honghan.trade.service.pay.call.OrderPayCallback;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.util.crypto.PKCS7Encoder;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 订单回调
 */
@Slf4j
@RestController
@RequestMapping("/order/notify")
public class TradeOrderNotifyController {

    private final ReentrantLock lock = new ReentrantLock();
    @Resource
    private TradeOrderService tradeOrderService;
    @Resource
    private TradeOrderProductService tradeOrderProductService;
    @Resource
    private TradeMessageService tradeMessageService;
    @Resource
    private IBillOrderService billOrderService;
    @Resource
    private ISubOrderService subOrderService;
    @Resource
    private PaySuccessCallback paySuccessCallback;
    @Resource
    private AliPayService aliPayService;
    @Resource
    private TradeMatchService tradeMatchService;
    @Resource
    private TradeMatchDetailService tradeMatchDetailService;
    @Resource
    private WxConfig wxConfig;
    @Resource
    private Verifier verifier;
    @Resource
    private IPaymentOrderService paymentOrderService;
    @Resource
    private OrderPayCallback orderPayCallback;
    @DubboReference
    private RemoteCompanyService companyService;
    @DubboReference
    private RemoteStoreStockService remoteStoreStockService;
    @DubboReference
    private RemoteCompanyService remoteCompanyService;
    @DubboReference
    private RemoteSysConfigService remoteSysConfigService;
    @DubboReference
    private RemoteStoreOrderService remoteStoreOrderService;
    @DubboReference
    private RemoteProductSkuStockService remoteProductSkuStockService;
    /**
     * 退款日志
     */
    @Autowired
    private RefundLogService refundLogService;

    /**
     * 支付宝回调接口
     */
    @PostMapping("/alipayNotify")
    public void aliPayNotify(HttpServletRequest request, HttpServletResponse response) {
        ResultResponse<PayResponse> result = aliPayService.asyncNotify(request, paySuccessCallback);
        if (result.getCode() == Constants.SUCCESS) {
            AjaxUtils.renderText(response, CommonConstants.SUCCESS);
        } else {
            AjaxUtils.renderText(response, CommonConstants.FAIL);
        }
    }


    /**
     * 微信回调接口
     */
    @PostMapping("/wxPayNotify")
    public Map<String, String> wxPayNotify(HttpServletRequest request, HttpServletResponse response) {
        String orderNo = "";
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            log.info("微信支付回调参数:{}", body);
            //转换为Map
            Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
            });

            //微信的通知ID(通知的唯一ID)
            String notifyId = bodyMap.get("id").toString();

            //构造验证签名信息
            WechatPayValidator wxCheckSign = new WechatPayValidator(verifier, notifyId, body);
            if (!wxCheckSign.validate(request)) {
                return ResultWechatBack.falseMsg(response);
            }
            if (lock.tryLock(6, TimeUnit.SECONDS)) {
                //解密resource中的通知数据
                String resource = bodyMap.get("resource").toString();
                Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wxConfig.getApiV3Key(), 1);
                //解密后返回相关订单详情
                log.info("微信支付回调解密后返回相关订单详情---{}", resourceMap);
                orderNo = resourceMap.get("out_trade_no").toString();
                String transactionId = resourceMap.get("transaction_id").toString();
                String tradeState = resourceMap.get("trade_state").toString();

                if (StringUtils.equals(WxConfig.SUCCESS, tradeState)) {
                    String _payTime = resourceMap.get("success_time").toString();
                    DateTime dateTime = new DateTime(_payTime);
                    //转换成Date对象
                    Date timeDate = dateTime.toJdkDate();

                    String payParamStr = resourceMap.get("attach").toString();
                    String payParamJson = URLDecoder.decode(payParamStr, StandardCharsets.UTF_8);
                    //PayParam payParam = JSONUtil.toBean(payParamJson, PayParam.class);


                    String tradeNo_ = resourceMap.get("transaction_id").toString();
                    Double totalAmount = null;

                    PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
                        PaymentMethodEnum.WECHAT.name(),
                        tradeNo_,
                        totalAmount,
                        null
                    );

                    paySuccessCallback.doWhilePaySuccess(CopyUtils.copy(paymentSuccessParams, PaymentSuccessParamsVO.class), orderNo, transactionId, timeDate);

//                    if(orderNo.startsWith("Y")) {
//                        //购物车下的订单
//                        List<SubOrder> subOrder = subOrderService.getOrderNos(orderNo);
//                        List<String> orderNoList = subOrder.stream().map(SubOrder::getCarOrderNo).collect(Collectors.toList());
//                        List<TradeOrder> tradeOrders = tradeOrderService.getByPayOrder(orderNoList);
//                        for (TradeOrder tradeOrder : tradeOrders) {
//                            if (tradeOrder.getType().equals(OrderType.MALL.getValue())) {
//                                Asserts.isTrue(tradeOrder.getStatus().equals(OrderStatus.CONFIRMED.getValue()), "订单状态不是待付款状态!");
//                                tradeOrder.setStatus(OrderStatus.PAYED.getValue());
//                            } else if (tradeOrder.getType().equals(OrderType.MALL_MATCH.getValue())) {
//                                Asserts.isTrue(tradeOrder.getStatus().equals(OrderStatus.CONFIRMED.getValue()), "订单状态不是待付款状态!");
//                                tradeOrder.setStatus(OrderStatus.COUNT.getValue());
//                            }
//                            tradeOrder.setPayTime(TimeUtils.getNowDate());
//                            orderPayCallback.paySuccess(orderNo, transactionId, tradeOrder);
//                        }
//                    }else if (orderNo.startsWith("W") || orderNo.startsWith("F")) {
//                        PaymentOrder payMentOrder = paymentOrderService.queryOrderNo(orderNo);
//                        Asserts.isTrue(payMentOrder.getStatus().equals(0), "付款单状态不是待付款状态!");
//                        TradeOrder tradeOrder =  tradeOrderService.getById(payMentOrder.getOriginalOrderId());
//                        payMentOrder.setPayType(tradeOrder.getPayType());
//                        payMentOrder.setStatus(PaymentStatusEnum.FINISH.getValue());
//                        payMentOrder.setPayTime(TimeUtils.getNowDate());
//                        payMentOrder.setPayNo(transactionId);
//                        boolean updateById = paymentOrderService.updateById(payMentOrder);
//                        if (updateById) {
//                            Integer total = payMentOrder.getTotal();
//                            List<PaymentOrder> paymentOrders = paymentOrderService.queryOriginalOrderId(payMentOrder.getOriginalOrderId(), null);
//                            long payAmountNum = paymentOrders.stream().filter(paymentOrder-> paymentOrder.getStatus() == 1).count();
//                            //付款方式 1 先款后货 2 先付订金 后付尾款   3 先货后款
//                            if(total == 1 && payMentOrder.getPaymentType() == 3) {
//                                //付完钱直接完成
//                                tradeOrder.setStatus(OrderStatus.COUNT.getValue());
//                            }else if(total == 2){
//                                if(payMentOrder.getRemark().equals(PaymentPayTypeEnum.FIRST_PAYMENT.getValue())){
//                                    tradeOrder.setIfPickUp(1);
//                                    tradeOrder.setStatus(OrderStatus.PART_PAYED.getValue());
//                                } else {
//                                    tradeOrder.setStatus(OrderStatus.COUNT.getValue());
//                                }
//                            }else {
//                                tradeOrder.setIfPickUp(1);
//                                tradeOrder.setStatus(OrderStatus.COUNT.getValue());
//                            }
//                            boolean paymentUpdateById = tradeOrderService.updateById(tradeOrder);
//                            if(paymentUpdateById){
//                                tradeOrderProductService.batchUpdateProductSaleNum(tradeOrder.getId());
//                            }
//                        }
//                    }else {
//                        LambdaQueryWrapper<TradeOrder> queryWrapper = Wrappers.lambdaQuery();
//                        queryWrapper.eq(TradeOrder::getIdentifier, orderNo);
//                        queryWrapper.last("LIMIT 1");
//                        TradeOrder tradeOrder = tradeOrderService.getOne(queryWrapper);
//                        Asserts.isTrue(tradeOrder.getStatus().equals(OrderStatus.CONFIRMED.getValue()), "订单状态不是待付款状态!");
//                        tradeOrder.setIfPickUp(1);
//                        tradeOrder.setStatus(OrderStatus.PAYED.getValue());
//                        orderPayCallback.paySuccess(orderNo, transactionId, tradeOrder);
//                    }
                    //成功应答 响应微信
                    log.info("微信支付回调成功----商户订单号:{}", orderNo);
                    return ResultWechatBack.trueMsg(response);
                } else {
                    log.warn("微信支付回调状态并不是SUCCESS:{}", resourceMap);
                    return ResultWechatBack.falseMsg(response);
                }
            }
        } catch (Exception e) {
            log.error("微信支付回调处理失败, orderNo = " + orderNo, e);
            return ResultWechatBack.falseMsg(response);
        } finally {
            //要主动释放锁
            lock.unlock();
        }
        return ResultWechatBack.trueMsg(response);
    }


    private static final String TOKEN = "xxx"; // Token
    private static final String ENCODING_AES_KEY = "xxx"; // EncodingAESKey
    private static final String APPID = "xxx"; // 小程序Appid

    /**
     * 微信消息推送
     */
    @RequestMapping("/callback")
    public void callback(HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException {

        // 从 URL 参数中获取
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");

        if (StringUtils.isNotBlank(echostr) && checkSignature(signature, timestamp, nonce)) {
            // 返回 echostr 表示验证通过
            httpServletResponse.getWriter().write(echostr);
            return null;
        }
        String msgSignature = request.getParameter("msg_signature");
        String encryptType = request.getParameter("encrypt_type"); // "aes" 是加密类型

        // 打印请求的 URL 参数
        System.out.println("signature: " + signature);
        System.out.println("timestamp: " + timestamp);
        System.out.println("nonce: " + nonce);
        System.out.println("msg_signature: " + msgSignature);
        System.out.println("encrypt_type: " + encryptType);

        // 从请求体中获取 JSON 数据(Encrypt 字段)
        StringBuilder body = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                body.append(line);
            }
        }

        String jsonBody = body.toString();
        System.out.println("Request Body (JSON): " + jsonBody);

        // 解析 JSON 数据中的 Encrypt 字段
        String encrypt = parseEncryptField(jsonBody);
        System.out.println("Encrypt: " + encrypt);

        // 校验 msg_signature 签名是否正确
        if (!checkMsgSignature(msgSignature, timestamp, nonce, encrypt)) {
            return ResponseEntity.status(400).body("Signature verification failed!");
        }

        // 解密消息体中的 Encrypt 内容
        String decryptedMessage = decryptMessage(encrypt); // 解密消息
        System.out.println("Decrypted message: " + decryptedMessage);

        cn.hutool.json.JSONObject entries = JSONUtil.parseObj(decryptedMessage);

        if (!entries.getStr("appid").equals(APPID)) {
            throw new RuntimeException("AppId 不匹配,非法请求");
        }
        entries = entries.getJSONObject("msg");
        String event = entries.getStr("Event");
        if (event.equalsIgnoreCase("retail_pay_notify")) {
            String orderNo = "";
            String transactionId = entries.getStr("order_id").toString();
            String tradeState = entries.getStr("pay_status").toString();

            if (StringUtils.equalsIgnoreCase("ORDER_PAY_SUCC", tradeState)) {
                String _payTime = entries.getStr("pay_time").toString();
                DateTime dateTime = new DateTime(_payTime);
                //转换成Date对象
                Date timeDate = dateTime.toJdkDate();

                String payParamStr = entries.getStr("attach").toString();
                orderNo = JSONUtil.parseObj(payParamStr).getStr("out_trade_no");
                if (orderNo.contains("b2b")) {
                    orderNo = orderNo.split("b2b")[1];
                }
                String tradeNo_ = entries.getStr("order_id").toString();
                Double totalAmount = null;

                PaymentSuccessParams paymentSuccessParams = new PaymentSuccessParams(
                    PaymentMethodEnum.WECHAT.name(),
                    tradeNo_,
                    totalAmount,
                    null
                );
                try {
                    paySuccessCallback.doWhilePaySuccess(CopyUtils.copy(paymentSuccessParams, PaymentSuccessParamsVO.class), orderNo, transactionId, timeDate);
                } catch (Exception e) {
                    log.error("微信支付回调处理失败, orderNo = " + orderNo, e);
                }
                //成功应答 响应微信
                log.info("微信支付回调成功----商户订单号:{}", orderNo);
            } else {
                log.warn("微信支付回调状态并不是SUCCESS:{}", tradeState);
            }
        } else if (event.equalsIgnoreCase("retail_refund_notify")) {
            String orderNo = "";
            String transactionId = entries.getStr("order_id").toString();
            String refundId = entries.getStr("refund_id").toString();
            orderNo = entries.getStr("out_trade_no");
            if (orderNo.contains("b2b")) {
                orderNo = orderNo.split("b2b")[1];
            }
            String outRefundNo = entries.getStr("out_refund_no").toString();//商户退款单号
            String refundStatus = entries.getStr("refund_status").toString();//微信退款状态
            if (StringUtils.isNotBlank(refundStatus) && StringUtils.equalsIgnoreCase("REFUND_SUCC", refundStatus)) {
                TradeOrderVO tradeOrderVO = tradeOrderService.selectVoByOutRefundNo(outRefundNo);
                if (Objects.isNull(tradeOrderVO)) {
                    log.warn("微信该笔订单号商家不存在,退款订单号:{}", outRefundNo);
                    new ServiceException("微信该笔订单号商家不存在,退款订单号:" + outRefundNo);
                }
                String successTime = entries.getStr("refund_time").toString();//微信退款成功时间
                TradeOrder tradeOrder = new TradeOrder();
                tradeOrder.setId(tradeOrderVO.getId());
                tradeOrder.setStatus(OrderStatus.CANCELLED.getValue());
                try {
                    if (StringUtils.isNotBlank(successTime)) {
                        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
                        Date date = formatter.parse(successTime);
                        Date refundSuccessTime = TimeUtils.parseDate(TimeUtils.format(date, CommonConstants.DATE.FORMAT_DEFAULT), CommonConstants.DATE.FORMAT_DEFAULT);
                        tradeOrder.setRefundTime(refundSuccessTime);
                    } else {
                        tradeOrder.setRefundTime(TimeUtils.getNowDate());
                    }
                } catch (Exception e) {
                    log.error("商城订单-微信退款回调处理失败, orderNo = " + orderNo, e);
                }

                tradeOrderService.updateById(tradeOrder);
                log.info("商城订单微信退款回调成功success----商户订单号:{}", orderNo);

                RefundLog refundLog = refundLogService.getOne(new LambdaQueryWrapper<RefundLog>().eq(RefundLog::getPaymentReceivableNo,
                    transactionId));
                if (refundLog != null) {
                    refundLog.setIsRefund(true);
                    refundLog.setReceivableNo(refundId);
                    refundLogService.saveOrUpdate(refundLog);
                }
            }
        }

        // 处理解密后的消息逻辑(根据需求)
     //   String responseMessage = "{\"demo_resp\":\"good luck\"}";

        // 构造回包
     //   String response = createResponse(responseMessage);

        httpServletResponse.getWriter().write("success");
    }

    /**
     * 验证微信签名
     *
     * @param signature 微信传来的签名
     * @param timestamp 微信传来的时间戳
     * @param nonce     微信传来的随机数
     * @return 是否是合法请求
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        try {
            String[] arr = new String[]{TOKEN, timestamp, nonce};
            // 1. 字典序排序
            Arrays.sort(arr);
            // 2. 拼接成一个字符串
            StringBuilder content = new StringBuilder();
            for (String s : arr) {
                content.append(s);
            }
            // 3. SHA1加密
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            // 4. 转成十六进制字符串
            StringBuilder hexStr = new StringBuilder();
            for (byte b : digest) {
                String shaHex = Integer.toHexString(b & 0xFF);
                if (shaHex.length() < 2) {
                    hexStr.append(0);
                }
                hexStr.append(shaHex);
            }
            // 5. 比较签名
            return hexStr.toString().equals(signature);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 解析请求体中的 Encrypt 字段
     */
    private String parseEncryptField(String jsonBody) {
        try {
            JSONObject jsonObject = JSON.parseObject(jsonBody);
            return jsonObject.getString("Encrypt");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 校验 msg_signature 签名
     */
    private boolean checkMsgSignature(String signature, String timestamp, String nonce, String encrypt) {
        try {
            String[] arr = new String[]{encrypt, timestamp, nonce, TOKEN};
            Arrays.sort(arr);

            StringBuilder sb = new StringBuilder();
            for (String str : arr) {
                sb.append(str);
            }

            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(sb.toString().getBytes());
            StringBuilder hexStr = new StringBuilder();
            for (byte b : digest) {
                String shaHex = Integer.toHexString(b & 0xFF);
                if (shaHex.length() < 2) {
                    hexStr.append(0);
                }
                hexStr.append(shaHex);
            }

            return hexStr.toString().equals(signature);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 解密消息体中的 Encrypt 内容
     */
    private String decryptMessage(String encrypt) {
        try {
            // 1. Base64 解密 Encrypt 字段
            byte[] encryptedBytes = Base64.getDecoder().decode(encrypt);

            // 2. 获取 AESKey
            byte[] aesKeyBytes = Base64.getDecoder().decode(ENCODING_AES_KEY + "=");
            SecretKeySpec secretKey = new SecretKeySpec(aesKeyBytes, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKeyBytes, 0, 16)); // 使用前16字节作为 IV

            // 3. AES 解密
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
            byte[] decrypted = cipher.doFinal(encryptedBytes);

            // 4.去除补位字符
            decrypted = PKCS7Encoder.decode(decrypted);

            // 5. 拆解结构 random(16B) + msg_len(4B) + msg + appid
            ByteBuffer byteBuffer = ByteBuffer.wrap(decrypted);

            // 读取 random
            byte[] randomBytes = new byte[16];
            byteBuffer.get(randomBytes);
            String randomStr = new String(randomBytes, StandardCharsets.UTF_8);

            // 读取 msg_len(4 字节,网络字节序,大端)
            int msgLen = byteBuffer.getInt();

            // 读取 msg
            byte[] msgBytes = new byte[msgLen];
            byteBuffer.get(msgBytes);
            String msg = new String(msgBytes, StandardCharsets.UTF_8);

            // 读取 appid
            byte[] appIdBytes = new byte[byteBuffer.remaining()];
            byteBuffer.get(appIdBytes);
            String appId = new String(appIdBytes, StandardCharsets.UTF_8);

            // 5. 将解密后的数据以 JSON 格式返回
            JSONObject jsonResponse = new JSONObject();
            jsonResponse.put("random", randomStr);
            jsonResponse.put("msg_len", msgLen);
            jsonResponse.put("msg", msg);
            jsonResponse.put("appid", appId);
            return jsonResponse.toString();  // 返回 JSON 格式字符串
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 构造回包并加密
     */
    private String createResponse(String message) {
        try {
            // 1. 构造 FullStr
            String randomStr = generateRandomString(16);  // 随机生成16字节
            byte[] msgBytes = message.getBytes("UTF-8");

            // 2. 构造消息头:random(16B) + msg_len(4B) + msg
            byte[] msgLenBytes = intToByteArray(msgBytes.length);  // 长度占4字节
            byte[] fullMessage = new byte[randomStr.length() + 4 + msgBytes.length];

            // 拼接消息
            System.arraycopy(randomStr.getBytes("UTF-8"), 0, fullMessage, 0, randomStr.length());
            System.arraycopy(msgLenBytes, 0, fullMessage, randomStr.length(), msgLenBytes.length);
            System.arraycopy(msgBytes, 0, fullMessage, randomStr.length() + 4, msgBytes.length);

            // 3. 用 AES 加密消息
            byte[] encryptedMessage = encryptWithAES(fullMessage);

            // 4. Base64 编码加密结果
            String encrypt = Base64.getEncoder().encodeToString(encryptedMessage);

            // 5. 生成签名
            String msgSignature = generateMsgSignature(encrypt);

            // 6. 构造回包
            long timeStamp = System.currentTimeMillis() / 1000;  // 当前时间戳,单位秒
            int nonce = new Random().nextInt(Integer.MAX_VALUE);  // 随机生成一个 nonce

            // 7. 构造回包
            return "{\"Encrypt\": \"" + encrypt + "\", \"MsgSignature\": \"" + msgSignature + "\", \"TimeStamp\": " + timeStamp + ", \"Nonce\": " + nonce + "}";
        } catch (Exception e) {
            e.printStackTrace();
            return "{\"error\":\"Failed to create response\"}";
        }
    }

    /**
     * AES 加密
     */
    private byte[] encryptWithAES(byte[] data) throws Exception {
        byte[] aesKeyBytes = Base64.getDecoder().decode(ENCODING_AES_KEY + "=");

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        SecretKeySpec secretKey = new SecretKeySpec(aesKeyBytes, "AES");
        IvParameterSpec iv = new IvParameterSpec(aesKeyBytes, 0, 16); // 使用前16字节作为 IV

        cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); // 使用空的 16 字节偏移量

        return cipher.doFinal(data);
    }

    /**
     * 生成消息签名
     */
    private String generateMsgSignature(String encrypt) throws Exception {
        String[] arr = new String[]{"1713424427", "415670741", TOKEN, encrypt};
        Arrays.sort(arr);

        StringBuilder sb = new StringBuilder();
        for (String str : arr) {
            sb.append(str);
        }

        MessageDigest md = MessageDigest.getInstance("SHA-1");
        byte[] digest = md.digest(sb.toString().getBytes());

        StringBuilder hexStr = new StringBuilder();
        for (byte b : digest) {
            String shaHex = Integer.toHexString(b & 0xFF);
            if (shaHex.length() < 2) {
                hexStr.append(0);
            }
            hexStr.append(shaHex);
        }

        return hexStr.toString();
    }

    // 将整数转化为网络字节序的4字节
    private byte[] intToByteArray(int num) {
        return new byte[]{
            (byte) (num >>> 24),
            (byte) (num >>> 16),
            (byte) (num >>> 8),
            (byte) num
        };
    }

    /**
     * 生成固定长度的随机字符串
     */
    private String generateRandomString(int length) {
        StringBuilder sb = new StringBuilder();
        String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(chars.charAt(random.nextInt(chars.length())));
        }
        return sb.toString();
    }

}

MQClientException: readLocalOffset Exception, maybe fastjson version too low

Caused by: org.apache.rocketmq.client.exception.MQClientException: readLocalOffset Exception, maybe fastjson version too low
See http://rocketmq.apache.org/docs/faq/ for further details.
    at org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore.readLocalOffsetBak(LocalFileOffsetStore.java:221)
    at org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore.readLocalOffset(LocalFileOffsetStore.java:199)
    at org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore.load(LocalFileOffsetStore.java:63)
    at org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl.start(DefaultMQPushConsumerImpl.java:609)
    at org.apache.rocketmq.client.consumer.DefaultMQPushConsumer.start(DefaultMQPushConsumer.java:698)
    at com.xquant.oms.tool.jetcache.JetCacheSyncMessageConsumer.init(JetCacheSyncMessageConsumer.java:89)
    ... 56 common frames omitted
Caused by: com.alibaba.fastjson.JSONException: syntax error, expect {, actual EOF, pos 710, fastjson-version 1.2.69
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:503)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseRest(JavaBeanDeserializer.java:1576)
    at com.alibaba.fastjson.parser.deserializer.FastjsonASMDeserializer_1_OffsetSerializeWrapper.deserialze(Unknown Source)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:284)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:688)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:396)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:300)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:573)
    at org.apache.rocketmq.remoting.protocol.RemotingSerializable.fromJson(RemotingSerializable.java:43)
    at org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore.readLocalOffsetBak(LocalFileOffsetStore.java:217)
    ... 61 common frames omitted

问题原因:因为mq换成了广播模式

解决方法:C盘搜索【.rocketmq_offsets】文件,删除即可

linux:

    find ~ -name ".rocketmq_offsets"
    
    sudo rm ~/.rocketmq_offsets

RocketMQMessageListener有时间无法监听到数据

其实已经发送成功了,但是有时无法监听到数据造成无法消费者

  1. 消费组多个实例,抢占式消费

场景:

你用的是默认的 集群消费模式(CLUSTERING),如果你有 多个消费者实例(本地启动多个、或者微服务有多个副本),一条消息只会给一个实例消费。

👉 所以你会出现:

A 启动的时候能收到消息

B 启动之后,A 就收不到了(消息被 B 消费了)

✅ 解决方案:

如果你想所有消费者都能收到消息,改为广播模式:

@RocketMQMessageListener(
    topic = "lili_member_topic",
    consumerGroup = "member-consumer-group",
    messageModel = MessageModel.BROADCASTING // 👈 重点
)

简单总结这句话的意思:

RocketMQ 的默认模式是集群消费(CLUSTERING),这个模式下:

同一个消费者组(consumerGroup)里的多个实例,会“平均分摊”消费消息,一条消息只会被其中一个实例处理。

🌰 举个最常见的例子:

你有两个服务实例都在监听 lili_member_topic:

@RocketMQMessageListener(

topic = "lili_member_topic",
consumerGroup = "member-group"

)

你启动了 服务 A ➜ 它能收到消息。

然后你启动了 服务 B ➜ 它也能收到消息。

但:

同一条消息只会发给其中一个服务(A 或 B),不会同时发给两个。

所以你看到 A 有时候收不到,那可能是消息刚好被 B 收走了。

这就是“你以为监听不到,其实被别人消费了”。
✅ 怎么解决?

看你需求是什么:
✅ 如果你想“每个服务实例都收到一份”:

那你就要改为 广播模式(BROADCASTING):

@RocketMQMessageListener(

topic = "lili_member_topic",
consumerGroup = "member-group-A", // 👈 每个实例最好用不同 group(广播模式建议)
messageModel = MessageModel.BROADCASTING

)

🚨 注意:广播模式下,每条消息每个实例都会收到一份,不适合处理订单扣库存这种需要幂等处理的场景,更适合做通知、日志同步、刷新缓存等。
🤔 最后一句话总结你这个情况:

“监听不到” ≠ “没发消息”,而是 消息被同一个 group 下的其他实例消费了。

GPT4图片生成指南

下面有更多风格和提示词参考,大家可以灵活运用。

以下内容转载自老E的博客:

  1. 吉卜力
  • 提示词:

吉卜力风格,阳光下的草原小镇突然出现丧尸群,穿红裙的少女骑着会飞的扫地机器人逃跑,柔和光影与恐怖元素的反差萌,手绘质感,温馨末日氛围。

  • 风格描述:
    温暖色调,梦幻光影
    适合插画、儿童绘本、治愈系内容
  1. 赛博朋克
  • 提示词:

赛博朋克风格,霓虹灯下的东京小巷,颓废的程序员坐在全息电脑前,机械义肢,蓝紫色调,Cyberpunk 2077风格,高对比度。 图片中显示标题"银翼杀手 2049"

  • 风格描述:
    霓虹炫光+机械细节, 高科技与低生活的碰撞
    适合科幻概念设计、游戏宣传等
  1. 国风/水墨
  • 提示词:

中国水墨风格,白娘子在西湖断桥,烟雨朦胧,毛笔笔触,留白意境,古典山水画质感,题诗落款。

  • 风格描述:
    淡雅墨色,虚实结合,像故宫藏画的诗情画意
    适合文化输出、品牌中国风设计
  1. 迪士尼
  • 提示词:

迪士尼动画风格,Q版人鱼服务员在海底咖啡馆忙碌,珊瑚造型的咖啡机,会吐泡泡的浓缩咖啡,贝壳形状的蛋糕,明亮的大眼睛和夸张的表情,柔光滤镜营造梦幻水下氛围。

  • 风格描述:
    圆润角色以及夸张的表情
    适合儿童内容、IP 衍生创作、职场幽默主题等
  1. 像素艺术
  • 提示词:

16-bit像素风格,马斯克与奥特曼结伴在森林冒险,怀旧游戏机质感,动态像素光影,图中显示简体中文标题"我们的世界"

  • 风格描述:
    方块化设计,复古色彩,《我的世界》、《Switch 2的幻想 RPG》等都是典型的像素游戏
    适合独立游戏开发、Web3 NFT 头像
  1. 浮世绘
  • 提示词:

浮世绘风格,海浪中的渔船与富士山,艺伎在樱花下,葛饰北斋式构图,版画线条,艳丽色彩。

  • 风格描述:
    平面化构图,经典海浪纹,传统与现代的结合
    适合文创产品、装饰画等
  1. 皮克斯3D
  • 提示词:

皮克斯 3D 风格,少年哪吒踩着风火轮在城市上空,卡通渲染,金属光泽,柔和阴影,电影级光影。

  • 风格描述:
    3D 逼真材质+卡通化比例,类似《玩具兵总动员》等电影作品的细腻感
    适合 3D 动画预演、IP 设计
  1. 极简主义
  • 提示词:

极简主义,单色几何客厅,无框落地窗,一棵孤植,莫兰迪灰调,无多余装饰。

  • 风格描述:
    留白艺术,用色克制,less is more 生活方式(“断舍离”)
    适合品牌 LOGO、现代家居设计等
  1. 波普艺术
  • 提示词:

波普艺术风格,重复排列的埃隆·马斯克肖像,荧光粉绿撞色,安迪·沃霍尔丝网印刷效果。

  • 风格描述:
    高饱和撞色,商业广告风
    适合潮玩设计、社交媒体头像等
  1. 文艺复兴
  • 提示词:

文艺复兴风格,扎克伯格作为古典肖像,油画画布质感,卡拉瓦乔式明暗对比,深红帷幕背景。

  • 风格描述:
    宗教画的庄严感
    适合高端品牌视觉、艺术实验等
  1. 涂鸦艺术
  • 提示词:

涂鸦艺术风格,城市废弃墙壁上的喷绘,'No AI Control'夸张字体,破碎的机器人图案,荧光粉绿撞色,街头叛逆感,笔触粗糙有质感。

  • 风格描述:
    粗线条、高对比色彩、随性不羁的街头感、社会批判
    适合潮牌设计、社会议题内容
  1. 暗黑奇幻
  • 提示词:

暗黑奇幻风格,苍白皮肤的新娘在哥特教堂举行婚礼,血月当空,枯树鸦群,诡异美感,深紫与墨绿主色调。

  • 风格描述:
    扭曲的建筑轮廓、病态唯美的角色造型
    适合恐怖游戏、另类时尚设计等
  1. 蒸汽波
  • 提示词:

蒸汽波风格,故障艺术的 Windows98 界面,粉色蓝天背景,古希腊雕像戴着 VR 眼镜,网格光效,数码虚化,90 年代复古未来感。

  • 风格描述:
    数码噪点、怀旧的矛盾美感
    适合音乐专辑封面、潮玩设计
  1. 超现实主义
  • 提示词:

超现实主义风格,沙漠中悬浮的巨型鲸鱼,鲸鱼体内是城市景观,融化的时钟挂在仙人掌上,达利式扭曲空间,黄昏暖色调。

  • 风格描述:
    荒诞的元素组合、精细的梦境质感
    适合艺术展览/心理学内容
  1. 插画笔刷
  • 提示词:

插画笔刷风格,童话书插图,小女孩与会说话的狐狸在枫树林,可见铅笔线条,水彩晕染效果,柔和蜡笔质感,留白页边距。

  • 风格描述:
    手绘笔触明显- 温暖的手工质感
    适合儿童读物、文创产品等
  1. 矢量扁平插画
  • 提示词:

矢量扁平插画风格,三名不同的员工正在开会,分析数据。

  • 风格描述:
    绘制简单,小清新,简约且整洁
    适合用于广告海报、电商封面等

最后,GPT4o 无论生图还是改图,无论风格、用途,SD/Flux 以及 Comfy 方案都可以实现同样的目标和效果,但是,GPT4o 胜在零门槛,市场最终由 99.9% 不愿/不会部署 SD 的用户决定。

img绕过图片防盗链

使用 属性尝试规避

部分防盗链是基于 HTTP Referer 的,可以尝试这样:

但这并不能绕过所有类型的防盗链,效果有限。

referrerpolicy="no-referrer" 是 HTML 中的一种设置,用于控制浏览器在加载资源(如图片、脚本、链接等)时,是否发送 Referer 头。
🔍 什么是 Referer?

当你点击一个链接或加载一个资源(比如 ),浏览器会自动发送一个 HTTP 头部字段叫 Referer,告诉服务器你是从哪个页面跳过来的。

比如:

Referer: https://your-website.com/page.html

某些网站通过检查 Referer 来判断你是不是“外链访问”(也就是热链),从而进行防盗链。
🧩 referrerpolicy="no-referrer" 是什么意思?

这段代码的意思是:

在加载该资源时,不发送任何 Referer 头信息。

用在