Преглед изворни кода

1.修改发送推送
2.添加用户取消订单

qmj пре 1 недеља
родитељ
комит
90d4caaadf

+ 452 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/OrderAppealController.java

@@ -0,0 +1,452 @@
+package com.ruoyi.app.order;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.ruoyi.app.order.dto.OrderPushBodyDto;
+import com.ruoyi.app.service.WalletService;
+import com.ruoyi.app.utils.PayPush;
+import com.ruoyi.app.utils.event.PushEventService;
+import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.system.domain.*;
+import com.ruoyi.system.mapper.RiderPositionMapper;
+import com.ruoyi.system.service.IInfoUserService;
+import com.ruoyi.system.service.IPosAppealService;
+import com.ruoyi.system.service.IPosOrderService;
+import com.ruoyi.system.service.IUserBillingService;
+import com.ruoyi.system.utils.Auth;
+import com.ruoyi.system.utils.JwtUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.i18n.LocaleContextHolder;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.web.bind.annotation.*;
+import com.ruoyi.common.core.controller.BaseController;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * 订单申诉Controller
+ *
+ * @author ruoyi
+ * @date 2024-07-07
+ */
+@RestController
+@RequestMapping("/system/orderAppeal")
+public class OrderAppealController extends BaseController {
+
+    /**
+     * 更新结果封装类
+     */
+    private static class UpdateResult {
+        private PosOrder order;
+        private Long state;
+
+        public UpdateResult(PosOrder order, Long state) {
+            this.order = order;
+            this.state = state;
+        }
+
+        public PosOrder getOrder() {
+            return order;
+        }
+
+        public Long getState() {
+            return state;
+        }
+    }
+
+    private static final Logger logger = LoggerFactory.getLogger(OrderAppealController.class);
+
+    @Autowired
+    private IPosAppealService posAppealService;
+    @Autowired
+    private IPosOrderService posOrderService;
+    @Autowired
+    private PushEventService pushEventService;
+    @Autowired
+    private IInfoUserService infoUserService;
+    @Autowired
+    private RiderPositionMapper riderPositionMapper;
+    @Autowired
+    private IUserBillingService billingService;
+    @Autowired
+    private PosOrderController posOrderController;
+    @Autowired
+    private WalletService walletService;
+
+
+    /**
+     * 用户取消订单
+     */
+    @Anonymous
+    @Auth
+    @GetMapping("/userCancelpOrder")
+    @Transactional(timeout = 30, rollbackFor = Exception.class)
+    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+    public AjaxResult userCancelpOrder(@RequestHeader String token, @RequestParam Long ddId, @RequestParam String language) {
+        System.out.println("用户取消订单语言:" + language);
+        // 正确解析语言参数,支持 "zh_CN", "zh-CN", "zh", "en-US" 等格式
+        Locale locale = parseLocale(language);
+        System.out.println("解析后的Locale: language=" + locale.getLanguage() + ", country=" + locale.getCountry());
+        LocaleContextHolder.setLocale(locale);
+        JwtUtil jwtUtil = new JwtUtil();
+        String userid = jwtUtil.getusid(token);
+        LambdaQueryWrapper<PosOrder> orderQuery = new LambdaQueryWrapper<>();
+        orderQuery.eq(PosOrder::getDdId, ddId);
+        PosOrder order = posOrderService.getOne(orderQuery);
+        if (!order.getUserId().equals(Long.valueOf(userid))) {
+//            String message = MessageUtils.message("no.order.nocanceluser.message");
+            throw new ServiceException("不是订单用户,无权限操作");
+        }
+        if (order.getQsId() != null) {
+            //MessageUtils.message("no.order.hasqs.message")
+            throw new ServiceException("骑手已经接单,无法取消");
+        }
+        PosOrder update = new PosOrder();
+        update.setId(order.getId());
+        if ("1".equals(order.getCollectPayment())) {
+            //货到付款骑手未接单,直接作废
+            update.setState(10L);
+//            //退回积分
+//            if (order.getPoints() != null && order.getPoints() > 0) {
+//                walletService.returnPoints(order.getUserId(), order.getDdId(), Long.valueOf(order.getPoints()));
+//            }
+            UserBilling bill = billingService.getOne(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getDdId, String.valueOf(order.getDdId())).eq(UserBilling::getUserId, Long.valueOf(userid)));
+            //账单作废
+            if (bill != null) {
+                UserBilling user = new UserBilling();
+                user.setId(bill.getId());
+                user.setState("2");  //作废
+                billingService.saveOrUpdate(user);
+            }
+        }
+//        else {
+            //在线支付客服接入
+//            update.setState(9L);
+//        }
+//        posOrderService.saveOrUpdate(update);
+//        PosAppeal appeal = new PosAppeal();
+//        appeal.setUserId(order.getUserId());
+//        appeal.setUserType("0");
+//        appeal.setDdId(order.getDdId());
+//        appeal.setContent(MessageUtils.message("no.order.hasnoqs.message"));
+//        posAppealService.insertPosAppeal(appeal);
+//        InfoUser shUer = infoUserService.getById(order.getShId());
+//        if (!StringUtils.isEmpty(shUer.getCid())) {
+//            String title =
+//                    "no.message.push.message";
+//            String body = OrderPushBodyDto.getJson(String.valueOf(order.getDdId()), String.valueOf(update.getState()));
+//            PayPush.shPushHandleLocal(new PayPush(), pushEventService, shUer.getUserId(), shUer.getCid(), title, "no.order.hasnoqs.message", body, shUer.getCidType(), String.valueOf(order.getDdId()));
+//        }
+
+        return success();
+    }
+
+//    /**
+//     * 商家取消订单
+//     */
+//    @Anonymous
+//    @Auth
+//    @PostMapping("/shCancelpOrder")
+//    @Transactional(timeout = 30, rollbackFor = Exception.class)
+//    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+//    public AjaxResult shCancelpOrder(@RequestHeader String token, @RequestBody PosAppeal posAppeal) {
+//        JwtUtil jwtUtil = new JwtUtil();
+//        String userid = jwtUtil.getusid(token);
+//        posAppeal.setUserId(Long.valueOf(userid));
+//        // 先执行数据库操作,在事务内完成
+//        UpdateResult updateResult = updateOrderInfo(Long.valueOf(userid), posAppeal.getDdId(), posAppeal, "1");
+//        posAppealService.insertPosAppeal(posAppeal);
+//        // 使用 TransactionSynchronization 确保在事务提交后再执行推送
+//
+//        PosOrder orderForPush = updateResult.getOrder();
+//        Long stateForPush = updateResult.getState();
+//        Long userIdForPush = Long.valueOf(userid);
+//        String cancelReasonForPush = posAppeal.getCancelReason();
+//        String cancelTypeForPush = posAppeal.getCancelType();
+//        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+//            @Override
+//            public void afterCommit() {
+//                // 事务提交后执行推送,此时数据库更新已经完成
+//                try {
+//                    shCanclePush(orderForPush, stateForPush, cancelReasonForPush);
+//                } catch (Exception e) {
+//                    // 注意:此时事务已提交,推送失败不会回滚主事务
+//                    logger.error("订单申诉推送失败,订单ID: {}, 错误信息: {}", orderForPush.getDdId(), e.getMessage(), e);
+//                }
+//            }
+//        });
+//
+//        return success();
+//    }
+//
+//    /**
+//     * 商家取消订单发送推送
+//     *
+//     * @param order
+//     * @param updateState
+//     * @param reason
+//     */
+//    @Async(value = "threadPoolTaskExecutor")
+//    protected void shCanclePush(PosOrder order, Long updateState, String reason) {
+//        PayPush push = new PayPush();
+//        InfoUser user = infoUserService.getById(order.getUserId());
+//        String title = "no.message.push.message";
+//        String content = "no.sh.cancelOrder.push.message";
+//        content = StrUtil.format(content, reason, order.getDdId());
+//        String body = OrderPushBodyDto.getJson(String.valueOf(order.getDdId()), String.valueOf(updateState));
+//        PayPush.userPushHandleLocal(push, pushEventService, user.getUserId(), user.getCid(), title, content, body, user.getCidType(), order.getDdId());
+//        if (order.getQsId() != null) {
+//            InfoUser qsUser = infoUserService.getById(order.getQsId());
+//            if (qsUser != null)
+//                PayPush.qsPushHandleLocal(push, pushEventService, order.getShId(), qsUser.getCid(), title, content, body, qsUser.getCidType(), String.valueOf(order.getDdId()));
+//        }
+//
+//    }
+//
+//
+//    /**
+//     * 骑手添加订单申诉(骑手取餐失败)
+//     */
+//    @Anonymous
+//    @Auth
+//    @PostMapping("/qsAdd")
+//    @Transactional(timeout = 30, rollbackFor = Exception.class)
+//    @RepeatSubmit(interval = 1000, message = "请求过于频繁")
+//    public AjaxResult qsAdd(@RequestHeader String token, @RequestBody PosAppeal posAppeal) {
+//        JwtUtil jwtUtil = new JwtUtil();
+//        String userid = jwtUtil.getusid(token);
+//        posAppeal.setUserId(Long.valueOf(userid));
+//        // 先执行数据库操作,在事务内完成
+//        UpdateResult updateResult = updateOrderInfo(Long.valueOf(userid), posAppeal.getDdId(), posAppeal, "2");
+//
+//        posAppealService.insertPosAppeal(posAppeal);
+//
+//        // 使用 TransactionSynchronization 确保在事务提交后再执行推送
+//        // 这样可以避免事务持有时间过长导致锁等待超时
+//        Locale currentLocale = LocaleContextHolder.getLocale();
+//        PosOrder orderForPush = updateResult.getOrder();
+//        Long stateForPush = updateResult.getState();
+//        Long userIdForPush = Long.valueOf(userid);
+//        //存在冻结记录,且未生成解冻账单,创建解冻账单
+//        UserBilling blocked = billingService.getOne(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getDdId, orderForPush.getDdId()).eq(UserBilling::getUserId, userIdForPush).eq(UserBilling::getType, "4").last("limit 1"));
+//        UserBilling relese = billingService.getOne(new LambdaQueryWrapper<UserBilling>().eq(UserBilling::getDdId, orderForPush.getDdId()).eq(UserBilling::getUserId, userIdForPush).eq(UserBilling::getType, "5").last("limit 1"));
+//        if (blocked != null && relese == null) {
+//            walletService.deduceBlocked(userIdForPush, BigDecimal.valueOf(blocked.getAmount()), String.valueOf(orderForPush.getDdId()));
+//            InfoUser qs = infoUserService.getById(userid);
+//            Locale locale = LocaleUtils.getUserLocale(qs.getUserId());
+//            LocaleContextHolder.setLocale(locale);
+//            var message = MessageUtils.message("no.order.daofu.relesefunds.message");
+//            message = StrUtil.format(message, orderForPush.getAmount()) + ",NO:" + orderForPush.getDdId();
+//
+//            PayPush.qsPush(new PayPush(), pushEventService, qs.getUserId(), qs.getCid(), MessageUtils.message("no.message.push.message"), message, OrderPushBodyDto.getJson(String.valueOf(orderForPush.getDdId()), String.valueOf(orderForPush.getState())), qs.getCidType());
+//        }
+//        String cancelReasonForPush = posAppeal.getCancelReason();
+//        String cancelTypeForPush = posAppeal.getCancelType();
+//        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+//            @Override
+//            public void afterCommit() {
+//                // 事务提交后执行推送,此时数据库更新已经完成
+//                try {
+//                    if ("1".equals(cancelTypeForPush)) {
+//                        riderCanclePsPush(orderForPush, stateForPush, userIdForPush);
+//                    } else {
+//                        riderOtherReasonPush(orderForPush, stateForPush, cancelReasonForPush);
+//                    }
+//                } catch (Exception e) {
+//                    // 注意:此时事务已提交,推送失败不会回滚主事务
+//                    logger.error("订单申诉推送失败,订单ID: {}, 错误信息: {}", orderForPush.getDdId(), e.getMessage(), e);
+//                }
+//            }
+//        });
+//
+//        return success();
+//    }
+
+    //骑手原因其他骑手能接着接单,其他原因变为售后
+//    private UpdateResult updateOrderInfo(Long userId, Long ddId, PosAppeal posAppeal, String userType) {
+//        PosOrder order = posOrderService.getOne(new LambdaQueryWrapper<PosOrder>().eq(PosOrder::getDdId, ddId));
+//        if (order == null) {
+//            throw new ServiceException(MessageUtils.message("no.order.not.exist"));
+//        }
+//        if ("2".equals(userType)) {
+//            if (order.getState() != 3) {
+//                throw new ServiceException(MessageUtils.message("no.order.state.error"));
+//            }
+//            if (order.getQsId() == null) {
+//                throw new ServiceException(MessageUtils.message("no.rider.permission.denied"));
+//            }
+//            if (order.getQsId() != null && !order.getQsId().equals(userId)) {
+//                throw new ServiceException(MessageUtils.message("no.rider.permission.denied"));
+//            }
+//        }
+//
+//        LambdaUpdateWrapper<PosOrder> updateWrapper = new LambdaUpdateWrapper<>();
+//        updateWrapper.eq(PosOrder::getId, order.getId());
+//        //更新为售后状态
+//        long state = 9L;
+//        updateWrapper.set(PosOrder::getState, state);
+//        //骑手取消
+//        if ("2".equals(userType)) {
+//            if ("1".equals(posAppeal.getCancelType())) {
+//                state = 2L;
+//                updateWrapper.set(PosOrder::getQsId, null)
+//                        .set(PosOrder::getKefuState, 2L)
+//                        .eq(PosOrder::getId, order.getId());
+//
+//                if (order.getIsAccepted() != null && order.getIsAccepted()) {
+//                    //商家已接单,返回商家已接单状态
+//                    updateWrapper.set(PosOrder::getState, 2L);
+//                } else {
+//                    //商家未接单
+//                    if ("1".equals(order.getCollectPayment())) {
+//                        state = 0L;
+//                        //到付,商家未接单
+//                        updateWrapper.set(PosOrder::getState, 0L);
+//                    } else {
+//                        state = 1L;
+//                        //非到付,支付成功
+//                        updateWrapper.set(PosOrder::getState, 1L);
+//
+//                    }
+//                }
+//            }
+//        }
+//        // 添加调试日志
+//        logger.info("准备更新订单,订单ID: {}, ddId: {}, 当前状态: {}, 目标状态: {}, cancelType: {}",
+//                order.getId(), order.getDdId(), order.getState(), state, posAppeal.getCancelType());
+//        boolean org = posOrderService.update(updateWrapper);
+//        logger.info("订单更新结果: {}, 订单ID: {}", org, order.getId());
+//        if (!org) {
+//            throw new ServiceException(MessageUtils.message("no.action.fail"));
+//        }
+//        // 返回订单信息和状态值,用于后续推送
+//        return new UpdateResult(order, state);
+//    }
+
+    /**
+     * 非骑手个人原因发送推送
+     *
+     * @param order
+     * @param updateState
+     * @param reason
+     */
+//    @Async(value = "threadPoolTaskExecutor")
+//    protected void riderOtherReasonPush(PosOrder order, Long updateState, String reason) {
+//
+//
+//        PayPush push = new PayPush();
+//        InfoUser shUser = infoUserService.getById(order.getShId());
+//        InfoUser user = infoUserService.getById(order.getUserId());
+//        Locale locale = LocaleUtils.getUserLocale(user.getUserId());
+//        LocaleContextHolder.setLocale(locale);
+//        String title =
+//                MessageUtils.message("no.message.push.message");
+//        String content = MessageUtils.message("no.qs.otherReason.message");
+//        content = StrUtil.format(content, reason, order.getDdId());
+//        String body = OrderPushBodyDto.getJson(String.valueOf(order.getDdId()), String.valueOf(updateState));
+//        PayPush.userPush(push, pushEventService, user.getUserId(), user.getCid(), title, content, body, user.getCidType());
+//        Locale shLocale = LocaleUtils.getUserLocale(shUser.getUserId());
+//        LocaleContextHolder.setLocale(shLocale);
+//        title =
+//                MessageUtils.message("no.message.push.message");
+//        content = MessageUtils.message("no.qs.otherReason.message");
+//        content = StrUtil.format(content, reason, order.getDdId());
+//        PayPush.shPush(push, pushEventService, order.getShId(), shUser.getCid(), title, content, body, shUser.getCidType());
+//    }
+//
+
+    /**
+     * 骑手个人原因取消推送
+     *
+     * @param order
+     * @param updateState
+     * @param userId
+     */
+//    @Async(value = "threadPoolTaskExecutor")
+//    protected void riderCanclePsPush(PosOrder order, Long updateState, Long userId) {
+//        try {
+//            PayPush push = new PayPush();
+//            InfoUser shUser = infoUserService.getById(order.getShId());
+//            InfoUser user = infoUserService.getById(order.getUserId());
+//            Locale locale = LocaleUtils.getUserLocale(user.getUserId());
+//            LocaleContextHolder.setLocale(locale);
+//            String title =
+//                    MessageUtils.message("no.message.push.message");
+//            String userContent = MessageUtils.message("no.qs.cancelpps.usermessage");
+//            userContent += ",No:" + order.getDdId();
+//
+//            String body = OrderPushBodyDto.getJson(String.valueOf(order.getDdId()), String.valueOf(updateState), 1);
+//            PayPush.userPush(push, pushEventService, user.getUserId(), user.getCid(), title, userContent, body, user.getCidType());
+//            Locale shLocale = LocaleUtils.getUserLocale(shUser.getUserId());
+//            LocaleContextHolder.setLocale(shLocale);
+//            title =
+//                    MessageUtils.message("no.message.push.message");
+//            String shContent = MessageUtils.message("no.qs.cancelps.shmessage");
+//            shContent = StrUtil.format(shContent, order.getDdId());
+//            PayPush.shPush(push, pushEventService, order.getShId(), shUser.getCid(), title, shContent, body, shUser.getCidType());
+//
+//            //取消次数
+//            List<PosAppeal> list = posAppealService.list(new LambdaQueryWrapper<PosAppeal>().eq(PosAppeal::getDdId, order.getDdId()).eq(PosAppeal::getCancelType, "1"));
+//            List<Long> cancalUserIds = list.stream().map(PosAppeal::getUserId).collect(Collectors.toList());
+//            cancalUserIds.add(userId);
+//            cancalUserIds = cancalUserIds.stream().distinct().collect(Collectors.toList());
+//            //取消超过三次广播
+//            if (list.size() >= 3) {
+//                PayPush.sendAcceptRiderPush(order, pushEventService, riderPositionMapper, body, cancalUserIds);
+//            } else {
+//                //自动重新派单
+//                posOrderController.sendOrderMessage(order, push, false, cancalUserIds);
+//            }
+//        } catch (Exception e) {
+//            // 异步方法中的异常不会影响主事务,但需要记录日志
+//            // 数据库更新已经成功,但推送失败,需要记录以便后续处理
+//            logger.error("骑手取消订单推送失败,订单ID: {}, 错误信息: {}", order.getDdId(), e.getMessage(), e);
+//        }
+//    }
+
+    /**
+     * 解析语言字符串为 Locale 对象
+     * 支持格式:zh_CN, zh-CN, zh, en-US, en_US, vi 等
+     *
+     * @param language 语言字符串
+     * @return Locale 对象
+     */
+    private Locale parseLocale(String language) {
+        if (language == null || language.isEmpty()) {
+            return new Locale("vi"); // 默认越南语
+        }
+
+        // 处理下划线格式(如 zh_CN)
+        if (language.contains("_")) {
+            String[] parts = language.split("_", 2);
+            if (parts.length == 2) {
+                return new Locale(parts[0], parts[1]);
+            } else {
+                return new Locale(parts[0]);
+            }
+        }
+
+        // 处理连字符格式(如 zh-CN)
+        if (language.contains("-")) {
+            return Locale.forLanguageTag(language);
+        }
+
+        // 简单语言代码(如 zh, en, vi)
+        return new Locale(language);
+    }
+
+}

+ 19 - 4
ruoyi-admin/src/main/java/com/ruoyi/app/user/InfoUserController.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.ruoyi.app.utils.DateUtil;
+import com.ruoyi.app.utils.PayPush;
 import com.ruoyi.app.utils.RsaMima;
 import com.ruoyi.app.utils.UUIDUtil;
 import com.ruoyi.app.utils.trtc.TLSSigAPIv2;
@@ -47,6 +48,7 @@ import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -72,6 +74,14 @@ public class InfoUserController extends BaseController {
     private IUserWalletService userWalletService;
     @Autowired
     private RedisCache redisCache;
+
+    @Anonymous
+    @PostMapping("/testBathPush")
+    public AjaxResult testBathPush(@RequestBody List<String> cids){
+        PayPush push=new PayPush();
+        push.userPushBatch(cids,"测试","内容","{\"ddId\":991754272700492,\"state\":3,\"type\":-1}");
+        return success();
+    }
     /**
      * 查询用户信息列表
      */
@@ -482,14 +492,19 @@ public class InfoUserController extends BaseController {
     @Anonymous
     @GetMapping("/getcode")
     public AjaxResult getcode(@RequestParam String phone) {
+        // GET 查询参数中 "+" 会被解码成空格,此处还原:前导空格+数字 → +数字
+        if (phone != null && phone.startsWith(" ") && phone.trim().matches("\\d+")) {
+            phone = "+" + phone.trim();
+        }
         MobileSMS sms = new MobileSMS();
-        System.out.println(phone.trim().replaceAll("\\+", ""));
-        RedisUtils jedis = new RedisUtils();
+//        System.out.println(phone.trim().replaceAll("\\+", ""));
+        String toPhone = phone;
+
         String authcode = "1" + RandomStringUtils.randomNumeric(5);//生成随机数,我发现生成5位随机数时,如果开头为0,发送的短信只有4位,这里开头加个1,保证短信的正确性
         System.out.println("验证码:" + authcode);
-        jedis.set(phone.trim().replaceAll("\\+", ""), authcode);//将验证码存入缓存
+        redisCache.setCacheObject(phone.trim().replaceAll("\\+", ""), authcode, 5, TimeUnit.MINUTES);//将验证码存入缓存
 //        sms.getcode(phone, authcode);//发送短息
-        sms.getTwCode(phone,authcode);
+        sms.getTwCode(toPhone,authcode);
         return success(MessageUtils.message("no.sms.send.success"));
     }
 

+ 27 - 1
ruoyi-admin/src/main/java/com/ruoyi/app/utils/PayPush.java

@@ -1,6 +1,10 @@
 package com.ruoyi.app.utils;
 
-import cn.hutool.http.HttpUtil;
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import java.util.List;
 
 public class PayPush {
     //用户端推送
@@ -13,6 +17,28 @@ public class PayPush {
         //JSONObject jsonObject =  JSON.parseObject(res);
         System.out.println("=========================1111111111111111"+res);
     }
+
+    //用户端批量推送
+    public void userPushBatch(List<String> cid, String title, String content, String body){
+        String url = "https://fc-mp-20ccbe79-10f8-41d7-a06c-c42581f351c1.next.bspapp.com/msduser";
+
+        JSONObject json = new JSONObject();
+        // cid 是数组/列表,不能直接字符串拼接,否则会得到 [Ljava.lang.String;@xxxx
+        json.put("cid", cid);
+        json.put("title", title);
+        json.put("content", content);
+        // payload 建议作为 JSON 对象传递,避免双引号转义问题
+        json.put("payload", body == null ? null : JSON.parse(body));
+
+        String jsonBody = json.toJSONString();
+        String res = HttpRequest.post(url)
+                .header("Content-Type", "application/json")
+                .body(jsonBody)
+                .execute()
+                .body();
+        System.out.println("-------用户批量推送");
+    }
+
     //骑手端推送
     public void qspush(String cid,String title, String content, String body){
         String url = "https://fc-mp-20ccbe79-10f8-41d7-a06c-c42581f351c1.next.bspapp.com/msdrider?cid="+cid+

+ 5 - 5
ruoyi-system/src/main/java/com/ruoyi/system/utils/MobileSMS.java

@@ -23,11 +23,11 @@ public class MobileSMS {
     }
 
     /**
-     * 发送 OTP 验证码(EngageLab OTP API
-     * 参考:https://otp.api.engagelab.cc/v1/messages
+     * 发送自定义验证码(EngageLab 自定义消息下发
+     * 文档:https://www.engagelab.com/zh_CN/docs/otp/REST-API/CustomMessages-Send
      *
      * @param phone 手机号(含国家码,如 +8613700000000)
-     * @param code  验证码
+     * @param code  自定义验证码,模板类型为验证码时必填
      */
     public void getTwCode(String phone, String code) {
         try {
@@ -36,9 +36,9 @@ public class MobileSMS {
             String authString = Base64.getEncoder()
                     .encodeToString((apiKey + ":" + apiSecret).getBytes(StandardCharsets.UTF_8));
 
+            // 自定义消息:template.params.code 必填(验证码模板)
             JSONObject template = new JSONObject();
             template.put("id", "tw_foodie_code");
-            template.put("language", "default");
             JSONObject params = new JSONObject();
             params.put("code", code);
             template.put("params", params);
@@ -48,7 +48,7 @@ public class MobileSMS {
             body.put("template", template);
             String bodyString = body.toJSONString();
 
-            String res = HttpRequest.post("https://otp.api.engagelab.cc/v1/codes")
+            String res = HttpRequest.post("https://otp.api.engagelab.cc/v1/custom-messages")
                     .header("Authorization", "Basic " + authString)
                     .header("Content-Type", "application/json")
                     .body(bodyString)