Browse Source

添加极光推送通知

Yangzw 5 months ago
parent
commit
c4280095b7
27 changed files with 1114 additions and 8 deletions
  1. 1 3
      feifan-module-mall/feifan-module-trade-api/src/main/java/cn/newfeifan/mall/module/trade/enums/ErrorCodeConstants.java
  2. 70 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/JPushMessageLogController.java
  3. 43 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogPageReqVO.java
  4. 49 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogRespVO.java
  5. 40 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogSaveReqVO.java
  6. 57 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/dal/dataobject/jpushmessagelog/JPushMessageLogDO.java
  7. 32 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/dal/mysql/jpushmessagelog/JPushMessageLogMapper.java
  8. 44 4
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/aftersale/AfterSaleServiceImpl.java
  9. 39 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogService.java
  10. 44 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogServiceImpl.java
  11. 43 1
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  12. 221 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/JPushUtils.java
  13. 34 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Callback.java
  14. 33 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Message.java
  15. 64 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/NotificationData.java
  16. 90 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Options.java
  17. 56 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/RequestBody.java
  18. 12 0
      feifan-module-mall/feifan-module-trade-biz/src/main/resources/mapper/jpushmessagelog/JPushMessageLogMapper.xml
  19. 5 0
      feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/dal/dataobject/user/MemberUserDO.java
  20. 8 0
      feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserService.java
  21. 17 0
      feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserServiceImpl.java
  22. 60 0
      feifan-module-sale/feifan-module-sale-biz/src/main/java/cn/newfeifan/mall/sale/service/merchantapply/MerchantApplyServiceImpl.java
  23. 1 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionService.java
  24. 26 0
      feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionServiceImpl.java
  25. 4 0
      feifan-server/src/main/resources/application-local.yaml
  26. 5 0
      feifan-server/src/main/resources/application-prod.yaml
  27. 16 0
      sql/mysql/建空库SQL/25_20241011.sql

+ 1 - 3
feifan-module-mall/feifan-module-trade-api/src/main/java/cn/newfeifan/mall/module/trade/enums/ErrorCodeConstants.java

@@ -25,7 +25,6 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_COMMENT_STATUS_NOT_FALSE = new ErrorCode(1_011_000_020, "创建交易订单项的评价失败,订单已评价");
     ErrorCode ORDER_DELIVERY_FAIL_REFUND_STATUS_NOT_NONE = new ErrorCode(1_011_000_021, "交易订单发货失败,订单已退款或部分退款");
     ErrorCode ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_022, "交易订单发货失败,拼团未成功");
-    ErrorCode ORDER_DELIVERY_FAIL_BARGAIN_RECORD_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_023, "交易订单发货失败,砍价未成功");
     ErrorCode ORDER_DELIVERY_FAIL_DELIVERY_TYPE_NOT_EXPRESS = new ErrorCode(1_011_000_024, "交易订单发货失败,发货类型不是快递");
     ErrorCode ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID = new ErrorCode(1_011_000_025, "交易订单取消失败,订单不是【待支付】状态");
     ErrorCode ORDER_UPDATE_PRICE_FAIL_PAID = new ErrorCode(1_011_000_026, "支付订单调价失败,原因:支付订单已付款,不能调价");
@@ -96,8 +95,7 @@ public interface ErrorCodeConstants {
     ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元");
     ErrorCode NOT_SUFFICIENT_FUNDS = new ErrorCode(1_011_008_004, "账号无可用单量,需要充值");
     ErrorCode CALLBACK_PARAMETER_ERROR = new ErrorCode(1_011_008_005, "第三方回调参数为Null");
-    ErrorCode SUBSCRIBER_ERROR = new ErrorCode(1_011_008_006, "订阅异常");
     ErrorCode ORDER_CALLBACK_LOGS_NOT_EXISTS = new ErrorCode(1_011_008_007, "回调日志不存在");
-    ErrorCode CREATE_TRADE_ORDER_LOGS_ERROR = new ErrorCode(1_011_008_008, "创建第三方日志异常");
     ErrorCode MESSAGE_LOG_NOT_EXISTS = new ErrorCode(1_011_008_009, "微信模板消息日志不存在");
+    ErrorCode J_PUSH_MESSAGE_LOG_NOT_EXISTS = new ErrorCode(1_011_008_011, "极光推送消息日志不存在");
 }

+ 70 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/JPushMessageLogController.java

@@ -0,0 +1,70 @@
+package cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog;
+
+import org.springframework.web.bind.annotation.*;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.security.access.prepost.PreAuthorize;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Operation;
+
+import javax.validation.*;
+import javax.servlet.http.*;
+import java.util.*;
+import java.io.IOException;
+
+import cn.newfeifan.mall.framework.common.pojo.PageParam;
+import cn.newfeifan.mall.framework.common.pojo.PageResult;
+import cn.newfeifan.mall.framework.common.pojo.CommonResult;
+import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
+import static cn.newfeifan.mall.framework.common.pojo.CommonResult.success;
+
+import cn.newfeifan.mall.framework.excel.core.util.ExcelUtils;
+
+import cn.newfeifan.mall.framework.operatelog.core.annotations.OperateLog;
+import static cn.newfeifan.mall.framework.operatelog.core.enums.OperateTypeEnum.*;
+
+import cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo.*;
+import cn.newfeifan.mall.module.trade.dal.dataobject.jpushmessagelog.JPushMessageLogDO;
+import cn.newfeifan.mall.module.trade.service.jpushmessagelog.JPushMessageLogService;
+
+@Tag(name = "管理后台 - 极光推送记录")
+@RestController
+@RequestMapping("/trade/J-push-message-log")
+@Validated
+public class JPushMessageLogController {
+
+    @Resource
+    private JPushMessageLogService jPushMessageLogService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获得极光推送记录")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:query')")
+    public CommonResult<JPushMessageLogRespVO> getJPushMessageLog(@RequestParam("id") Long id) {
+        JPushMessageLogDO jPushMessageLog = jPushMessageLogService.getJPushMessageLog(id);
+        return success(BeanUtils.toBean(jPushMessageLog, JPushMessageLogRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得极光推送记录分页")
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:query')")
+    public CommonResult<PageResult<JPushMessageLogRespVO>> getJPushMessageLogPage(@Valid JPushMessageLogPageReqVO pageReqVO) {
+        PageResult<JPushMessageLogDO> pageResult = jPushMessageLogService.getJPushMessageLogPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, JPushMessageLogRespVO.class));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出极光推送记录 Excel")
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:export')")
+    @OperateLog(type = EXPORT)
+    public void exportJPushMessageLogExcel(@Valid JPushMessageLogPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<JPushMessageLogDO> list = jPushMessageLogService.getJPushMessageLogPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "极光推送记录.xls", "数据", JPushMessageLogRespVO.class,
+                        BeanUtils.toBean(list, JPushMessageLogRespVO.class));
+    }
+
+}

+ 43 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogPageReqVO.java

@@ -0,0 +1,43 @@
+package cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo;
+
+import lombok.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.newfeifan.mall.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.newfeifan.mall.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 极光推送记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class JPushMessageLogPageReqVO extends PageParam {
+
+    @Schema(description = "接收消息用户的openid", example = "25139")
+    private String regId;
+
+    @Schema(description = "会员用户ID", example = "13890")
+    private Long memberUserId;
+
+    @Schema(description = "系统用户ID", example = "12879")
+    private Long systemUserId;
+
+    @Schema(description = "微信消息模板参数")
+    private String jpushParams;
+
+    @Schema(description = "对应的业务对象ID,如订单ID、售后订单ID", example = "26581")
+    private Long objectId;
+
+    @Schema(description = "发送时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] sendTime;
+
+    @Schema(description = "极光返回的消息响应结果")
+    private String responseResult;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 49 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogRespVO.java

@@ -0,0 +1,49 @@
+package cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import java.time.LocalDateTime;
+import com.alibaba.excel.annotation.*;
+
+@Schema(description = "管理后台 - 极光推送记录 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class JPushMessageLogRespVO {
+
+    @Schema(description = "消息记录ID,自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "22852")
+    @ExcelProperty("消息记录ID,自增主键")
+    private Long id;
+
+    @Schema(description = "接收消息用户的openid", requiredMode = Schema.RequiredMode.REQUIRED, example = "25139")
+    @ExcelProperty("接收消息用户的openid")
+    private String regId;
+
+    @Schema(description = "会员用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "13890")
+    @ExcelProperty("会员用户ID")
+    private Long memberUserId;
+
+    @Schema(description = "系统用户ID", example = "12879")
+    @ExcelProperty("系统用户ID")
+    private Long systemUserId;
+
+    @Schema(description = "微信消息模板参数")
+    @ExcelProperty("微信消息模板参数")
+    private String jpushParams;
+
+    @Schema(description = "对应的业务对象ID,如订单ID、售后订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26581")
+    @ExcelProperty("对应的业务对象ID,如订单ID、售后订单ID")
+    private Long objectId;
+
+    @Schema(description = "发送时间")
+    @ExcelProperty("发送时间")
+    private LocalDateTime sendTime;
+
+    @Schema(description = "极光返回的消息响应结果")
+    @ExcelProperty("极光返回的消息响应结果")
+    private String responseResult;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 40 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/controller/admin/jpushmessagelog/vo/JPushMessageLogSaveReqVO.java

@@ -0,0 +1,40 @@
+package cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.*;
+import javax.validation.constraints.*;
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 极光推送记录新增/修改 Request VO")
+@Data
+@Builder
+public class JPushMessageLogSaveReqVO {
+
+    @Schema(description = "消息记录ID,自增主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "22852")
+    private Long id;
+
+    @Schema(description = "接收消息用户的openid", requiredMode = Schema.RequiredMode.REQUIRED, example = "25139")
+    @NotEmpty(message = "接收消息用户的openid不能为空")
+    private String regId;
+
+    @Schema(description = "会员用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "13890")
+    @NotNull(message = "会员用户ID不能为空")
+    private Long memberUserId;
+
+    @Schema(description = "系统用户ID", example = "12879")
+    private Long systemUserId;
+
+    @Schema(description = "微信消息模板参数")
+    private String jpushParams;
+
+    @Schema(description = "对应的业务对象ID,如订单ID、售后订单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "26581")
+    @NotNull(message = "对应的业务对象ID,如订单ID、售后订单ID不能为空")
+    private Long objectId;
+
+    @Schema(description = "发送时间")
+    private LocalDateTime sendTime;
+
+    @Schema(description = "极光返回的消息响应结果")
+    private String responseResult;
+
+}

+ 57 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/dal/dataobject/jpushmessagelog/JPushMessageLogDO.java

@@ -0,0 +1,57 @@
+package cn.newfeifan.mall.module.trade.dal.dataobject.jpushmessagelog;
+
+import lombok.*;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.newfeifan.mall.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 极光推送记录 DO
+ *
+ * @author 非繁人
+ */
+@TableName("jpush_message_log")
+@KeySequence("jpush_message_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class JPushMessageLogDO extends BaseDO {
+
+    /**
+     * 消息记录ID,自增主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 接收消息用户的openid
+     */
+    private String regId;
+    /**
+     * 会员用户ID
+     */
+    private Long memberUserId;
+    /**
+     * 系统用户ID
+     */
+    private Long systemUserId;
+    /**
+     * 微信消息模板参数
+     */
+    private String jpushParams;
+    /**
+     * 对应的业务对象ID,如订单ID、售后订单ID
+     */
+    private Long objectId;
+    /**
+     * 发送时间
+     */
+    private LocalDateTime sendTime;
+    /**
+     * 极光返回的消息响应结果
+     */
+    private String responseResult;
+
+}

+ 32 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/dal/mysql/jpushmessagelog/JPushMessageLogMapper.java

@@ -0,0 +1,32 @@
+package cn.newfeifan.mall.module.trade.dal.mysql.jpushmessagelog;
+
+
+import cn.newfeifan.mall.framework.common.pojo.PageResult;
+import cn.newfeifan.mall.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.newfeifan.mall.framework.mybatis.core.mapper.BaseMapperX;
+import cn.newfeifan.mall.module.trade.dal.dataobject.jpushmessagelog.JPushMessageLogDO;
+import org.apache.ibatis.annotations.Mapper;
+import cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo.*;
+
+/**
+ * 极光推送记录 Mapper
+ *
+ * @author 非繁人
+ */
+@Mapper
+public interface JPushMessageLogMapper extends BaseMapperX<JPushMessageLogDO> {
+
+    default PageResult<JPushMessageLogDO> selectPage(JPushMessageLogPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<JPushMessageLogDO>()
+                .eqIfPresent(JPushMessageLogDO::getRegId, reqVO.getRegId())
+                .eqIfPresent(JPushMessageLogDO::getMemberUserId, reqVO.getMemberUserId())
+                .eqIfPresent(JPushMessageLogDO::getSystemUserId, reqVO.getSystemUserId())
+                .eqIfPresent(JPushMessageLogDO::getJpushParams, reqVO.getJpushParams())
+                .eqIfPresent(JPushMessageLogDO::getObjectId, reqVO.getObjectId())
+                .betweenIfPresent(JPushMessageLogDO::getSendTime, reqVO.getSendTime())
+                .eqIfPresent(JPushMessageLogDO::getResponseResult, reqVO.getResponseResult())
+                .betweenIfPresent(JPushMessageLogDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(JPushMessageLogDO::getId));
+    }
+
+}

+ 44 - 4
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
 import cn.newfeifan.mall.framework.common.pojo.PageParam;
 import cn.newfeifan.mall.framework.common.pojo.PageResult;
 import cn.newfeifan.mall.framework.common.util.object.ObjectUtils;
+import cn.newfeifan.mall.module.member.service.user.MemberUserService;
 import cn.newfeifan.mall.module.pay.api.refund.PayRefundApi;
 import cn.newfeifan.mall.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.newfeifan.mall.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
@@ -34,6 +35,8 @@ import cn.newfeifan.mall.module.trade.framework.order.config.TradeOrderPropertie
 import cn.newfeifan.mall.module.trade.service.delivery.DeliveryExpressService;
 import cn.newfeifan.mall.module.trade.service.order.TradeOrderQueryService;
 import cn.newfeifan.mall.module.trade.service.order.TradeOrderUpdateService;
+import cn.newfeifan.mall.module.trade.utils.push.JPushUtils;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.RequestBody;
 import cn.newfeifan.mall.module.trade.utils.wechat.WcChatMessageUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.google.common.collect.Maps;
@@ -85,6 +88,10 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
     @Resource
     private WcChatMessageUtils wcChatMessageUtils;
+    @Resource
+    private JPushUtils jPushUtils;
+    @Resource
+    private MemberUserService memberUserService;
 
     @Override
     public PageResult<AfterSaleDO> getAfterSalePage(AfterSalePageReqVO pageReqVO) {
@@ -232,7 +239,10 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
 
         // 发送给微信用户售后消息
-        sentWcChatMessage(afterSale,false);
+        sentWcChatMessage(afterSale, false);
+
+        // 发送极光推送消息
+        sentJPush(afterSale, false);
 
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
@@ -380,7 +390,10 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 AfterSaleStatusEnum.COMPLETE.getStatus());
 
         // 发送给微信用户售后消息
-        sentWcChatMessage(afterSale,true);
+        sentWcChatMessage(afterSale, true);
+
+        // 发送极光推送消息
+        sentJPush(afterSale, true);
 
         // 更新交易订单项的售后状态为【已完成】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
@@ -388,10 +401,11 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
     /**
      * 发送微信消息
-     * @param afterSale 售后订单
+     *
+     * @param afterSale   售后订单
      * @param afterStatus 是否同意退款
      */
-    private void sentWcChatMessage(AfterSaleDO afterSale,Boolean afterStatus) {
+    private void sentWcChatMessage(AfterSaleDO afterSale, Boolean afterStatus) {
         List<String> params = new ArrayList<>();
         params.add(afterSale.getNo());
         params.add(afterSale.getSpuName());
@@ -407,6 +421,32 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 params, null, afterSale.getUserId(), afterSale.getId());
     }
 
+    /**
+     * 发送极光推送消息
+     *
+     * @param afterSale 售后订单
+     */
+    private void sentJPush(AfterSaleDO afterSale, Boolean afterStatus) {
+        String title = WcChatMessageTemplateIdEnum.AFTER_SALE_CHECK2.getName();
+
+        String alert = "售后订单号: " + afterSale.getNo();
+        alert += "\n商品名称: " + afterSale.getSpuName();
+        DecimalFormat df = new DecimalFormat("0.00");
+        String formattedPrice = df.format((double) afterSale.getRefundPrice() / 100);
+        alert += "\n金额: ¥ " + formattedPrice;
+
+        alert += "\n售后状态: " + (afterStatus ? "商家确认退款" : "商家拒绝退款");
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        alert += "\n时间: " + LocalDateTime.now().format(formatter);
+
+        List<String> userRsgIds = memberUserService.getUserRsgIds(null, afterSale.getUserId());
+        if (userRsgIds != null) {
+            String url = jPushUtils.getUrl(title, afterSale.getUserId(), afterSale.getId());
+            RequestBody requestBody = jPushUtils.getRequestBody(userRsgIds, alert, title, url);
+            jPushUtils.jPush(requestBody, afterSale.getUserId(), null, afterSale.getId());
+        }
+    }
+
     private void createPayRefund(String userIp, AfterSaleDO afterSale) {
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
 

+ 39 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogService.java

@@ -0,0 +1,39 @@
+package cn.newfeifan.mall.module.trade.service.jpushmessagelog;
+
+import javax.validation.*;
+import cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo.*;
+import cn.newfeifan.mall.module.trade.dal.dataobject.jpushmessagelog.JPushMessageLogDO;
+import cn.newfeifan.mall.framework.common.pojo.PageResult;
+
+/**
+ * 极光推送记录 Service 接口
+ *
+ * @author 非繁人
+ */
+public interface JPushMessageLogService {
+
+    /**
+     * 创建极光推送记录
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createJPushMessageLog(@Valid JPushMessageLogSaveReqVO createReqVO);
+
+    /**
+     * 获得极光推送记录
+     *
+     * @param id 编号
+     * @return 极光推送记录
+     */
+    JPushMessageLogDO getJPushMessageLog(Long id);
+
+    /**
+     * 获得极光推送记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 极光推送记录分页
+     */
+    PageResult<JPushMessageLogDO> getJPushMessageLogPage(JPushMessageLogPageReqVO pageReqVO);
+
+}

+ 44 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogServiceImpl.java

@@ -0,0 +1,44 @@
+package cn.newfeifan.mall.module.trade.service.jpushmessagelog;
+
+import org.springframework.stereotype.Service;
+import javax.annotation.Resource;
+import org.springframework.validation.annotation.Validated;
+import cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo.*;
+import cn.newfeifan.mall.module.trade.dal.dataobject.jpushmessagelog.JPushMessageLogDO;
+import cn.newfeifan.mall.framework.common.pojo.PageResult;
+import cn.newfeifan.mall.framework.common.util.object.BeanUtils;
+
+import cn.newfeifan.mall.module.trade.dal.mysql.jpushmessagelog.JPushMessageLogMapper;
+
+/**
+ * 极光推送记录 Service 实现类
+ *
+ * @author 非繁人
+ */
+@Service
+@Validated
+public class JPushMessageLogServiceImpl implements JPushMessageLogService {
+
+    @Resource
+    private JPushMessageLogMapper jPushMessageLogMapper;
+
+    @Override
+    public Long createJPushMessageLog(JPushMessageLogSaveReqVO createReqVO) {
+        // 插入
+        JPushMessageLogDO jPushMessageLog = BeanUtils.toBean(createReqVO, JPushMessageLogDO.class);
+        jPushMessageLogMapper.insert(jPushMessageLog);
+        // 返回
+        return jPushMessageLog.getId();
+    }
+
+    @Override
+    public JPushMessageLogDO getJPushMessageLog(Long id) {
+        return jPushMessageLogMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<JPushMessageLogDO> getJPushMessageLogPage(JPushMessageLogPageReqVO pageReqVO) {
+        return jPushMessageLogMapper.selectPage(pageReqVO);
+    }
+
+}

+ 43 - 1
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -71,6 +71,8 @@ import cn.newfeifan.mall.module.trade.service.price.TradePriceService;
 import cn.newfeifan.mall.module.trade.service.price.bo.TradePriceCalculateReqBO;
 import cn.newfeifan.mall.module.trade.service.price.bo.TradePriceCalculateRespBO;
 import cn.newfeifan.mall.module.trade.service.price.calculator.TradePriceCalculatorHelper;
+import cn.newfeifan.mall.module.trade.utils.push.JPushUtils;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.RequestBody;
 import cn.newfeifan.mall.module.trade.utils.wechat.WcChatMessageUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -150,6 +152,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     private TradeOrderProperties tradeOrderProperties;
     @Resource
     private WcChatMessageUtils wcChatMessageUtils;
+    @Resource
+    private JPushUtils jPushUtils;
 
     @Value("${feifan.trade.express.kd100.key}")
     private String key;
@@ -690,7 +694,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 if (payIntegral > 0) {
                     integralService.updateUserIntegral(userId, CaclEnum.ORDER_CANCEL_BY_SYSTEM_REFUND_INTEGRAL, payIntegral, tradeOrderId, orderNum);
                 }
-                if(order.getPayConsumptionPoints() > 0){
+                if (order.getPayConsumptionPoints() > 0) {
                     integralService.updateUserConsumptionPoints(userId, ConsumptionEnum.ORDER_CANCEL_BY_SYSTEM_REFUND_INTEGRAL, order.getPayConsumptionPoints(), tradeOrderId, orderNum);
                 }
 
@@ -1240,12 +1244,50 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
             // 发送给微信消息给系统用户待发货消息
             sentWcChatMessage(order);
+
+            // 发送给极光推送给系统用户代发货消息
+            sentJPush(order);
         }
 
         // 七天获得变为即算即得,后续如果恢复把这里注释即可,并打开平台定时任务得订单结算
         calc(TradeOrderDOList, orderPercentageDO, JsonUtils.toJsonString(orderPercentageDO));
     }
 
+    /**
+     * 发送极光推送消息
+     *
+     * @param order 订单
+     */
+    private void sentJPush(TradeOrderDO order) {
+        String title = WcChatMessageTemplateIdEnum.ORDER_NO_DELIVERY2.getName();
+
+        DecimalFormat df = new DecimalFormat("0.00");
+        String formattedPrice = df.format((double) order.getPayPrice() / 100);
+        String alert = "订单金额: " + formattedPrice;
+        alert += "\n单据编号: " + order.getNo();
+        alert += "\n客户名称: " + memberUserService.getUser(order.getUserId()).getNickname();
+
+        List<TradeOrderItemDO> tradeOrderItemDOS = tradeOrderItemMapper.selectListByOrderId(order.getId());
+        List<String> productNames = tradeOrderItemDOS.stream().map(TradeOrderItemDO::getSpuName).collect(Collectors.toList());
+        String name = productNames.toString();
+        // 去除首尾的括号,并去除空格
+        name = name.substring(1, name.length() - 1).trim();
+        alert += "\n商品名称: " + name;
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        alert += "\n单据日期: " + LocalDateTime.now().format(formatter);
+
+        // 获取管理员用户
+        List<Long> adminUsers = adminUserService.getUserIdsByShop(order.getShopId());
+        for (Long adminUser : adminUsers) {
+            List<String> userRsgIds = memberUserService.getUserRsgIds(adminUser, null);
+            if (userRsgIds != null) {
+                String url = jPushUtils.getUrl(title, order.getUserId(), order.getId());
+                RequestBody requestBody = jPushUtils.getRequestBody(userRsgIds, alert, title, url);
+                jPushUtils.jPush(requestBody, order.getUserId(), adminUser, order.getId());
+            }
+        }
+    }
+
     /**
      * 计算订单支付成功后,订单项的冻结积分转换
      *

+ 221 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/JPushUtils.java

@@ -0,0 +1,221 @@
+package cn.newfeifan.mall.module.trade.utils.push;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import cn.newfeifan.mall.module.system.controller.admin.auth.vo.AuthLoginRespVO;
+import cn.newfeifan.mall.module.system.dal.dataobject.config.SystemConfigDO;
+import cn.newfeifan.mall.module.system.dal.dataobject.user.AdminUserDO;
+import cn.newfeifan.mall.module.system.enums.logger.LoginLogTypeEnum;
+import cn.newfeifan.mall.module.system.service.auth.AdminAuthService;
+import cn.newfeifan.mall.module.system.service.config.SystemConfigService;
+import cn.newfeifan.mall.module.system.service.permission.PermissionService;
+import cn.newfeifan.mall.module.system.service.user.AdminUserService;
+import cn.newfeifan.mall.module.trade.controller.admin.jpushmessagelog.vo.JPushMessageLogSaveReqVO;
+import cn.newfeifan.mall.module.trade.enums.wxmessage.WcChatMessageTemplateIdEnum;
+import cn.newfeifan.mall.module.trade.service.jpushmessagelog.JPushMessageLogService;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.NotificationData;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.Options;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.RequestBody;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static cn.newfeifan.mall.module.trade.enums.MessageTemplateConstants.*;
+
+@Component
+@Slf4j
+public class JPushUtils {
+    @Value("${jpush.app-key}")
+    private String appKey;
+    @Value("${jpush.secret}")
+    private String secret;
+
+    @Resource
+    private JPushMessageLogService jPushService;
+    @Resource
+    private SystemConfigService configService;
+    @Resource
+    private AdminAuthService authService;
+    @Resource
+    private AdminUserService adminUserService;
+    @Resource
+    private PermissionService permissionService;
+
+    /**
+     * 获取请求体
+     *
+     * @param rsgIds 极光注册id
+     * @param alert  推送内容
+     * @param title  推送标题
+     * @return 请求体
+     */
+    public RequestBody getRequestBody(List<String> rsgIds, String alert, String title, String url) {
+        RequestBody requestBody = RequestBody.builder().build();
+        requestBody.setPlatform("all");
+        requestBody.setAudience(new RequestBody.Audience().setRegistration_id(rsgIds));
+
+        NotificationData notificationData = new NotificationData();
+        notificationData.setAlert("Hello, content!");
+        NotificationData.Notification notification = new NotificationData.Notification();
+        notification.setAlert(alert);
+        notification.setTitle(title);
+        notification.setBuilder_id(1);
+        notification.setIntent(new NotificationData.Intent().setUrl("letcgo://gizmos?"+url));
+
+        notificationData.setAndroid(notification);
+        requestBody.setNotification(notificationData);
+
+        Options options = Options.builder()
+                .time_to_live(86400)
+                .classification(1)
+                .third_party_channel(Options.ThirdPartyChannel
+                        .builder()
+                        .huawei(Options.Channel
+                                .builder()
+                                .importance("NORMAL")
+                                .category("WORK")
+                                .build())
+                        .xiaomi(Options.Channel
+                                .builder()
+                                .channel_id("NORMAL")
+                                .build())
+                        .honor(Options.Channel
+                                .builder()
+                                .importance("NORMAL")
+                                .build())
+                        .oppo(Options.Channel
+                                .builder()
+                                .channel_id("message")
+                                .build())
+                        .vivo(Options.Channel
+                                .builder()
+                               .distribution("secondary_push")
+                                .category("IM")
+                                .build())
+                        .build())
+                .build();
+        requestBody.setOptions(options);
+        return requestBody;
+    }
+
+    /**
+     * 获取url
+     * @param title 标题
+     * @param systemUserId 系统用户id
+     * @param objectId 对象id
+     * @return url
+     */
+    public String getUrl(String title, Long objectId, Long systemUserId){
+        SystemConfigDO redisConfig = configService.getRedisConfig();
+        if (title.equals(WcChatMessageTemplateIdEnum.ORDER_DELIVERY2.getName())) {
+            //订单已发货
+            return redisConfig.getMallDomain() + "/#/pages/order/detail?id=" + objectId;
+        } else if (title.equals(WcChatMessageTemplateIdEnum.AFTER_SALE_CHECK2.getName())) {
+            //售后订单审核完成
+            return redisConfig.getMallDomain() + "/#/pages/order/aftersale/detail?id=" + objectId;
+        } else if (title.equals(WcChatMessageTemplateIdEnum.AFTER_SALE_NO_CHECK2.getName())) {
+            //售后订单待审核
+            AdminUserDO systemUser = adminUserService.getUser(systemUserId);
+            AuthLoginRespVO token = authService.createTokenAfterLoginSuccess(systemUser.getId(), systemUser.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
+//            return redisConfig.getMerchantDomain() + "/after-sale";
+            return redisConfig.getMerchantDomain() + "/login" + "?accessToken=" + token.getAccessToken() + "&refreshToken=" + token.getRefreshToken();
+        } else if (title.equals(WcChatMessageTemplateIdEnum.ORDER_NO_DELIVERY2.getName())) {
+            //订单待发货
+            AdminUserDO systemUser = adminUserService.getUser(systemUserId);
+            AuthLoginRespVO token = authService.createTokenAfterLoginSuccess(systemUser.getId(), systemUser.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
+//            return redisConfig.getMerchantDomain() + "/order";
+            return redisConfig.getMerchantDomain() + "/login" + "?accessToken=" + token.getAccessToken() + "&refreshToken=" + token.getRefreshToken();
+        } else if(title.equals(WcChatMessageTemplateIdEnum.MERCHANT_APPLY_CHECK.getName())){
+            // 商户入驻申请审批通知
+            AdminUserDO systemUser = adminUserService.getUser(systemUserId);
+            AuthLoginRespVO token = authService.createTokenAfterLoginSuccess(systemUser.getId(), systemUser.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
+            return redisConfig.getPlatformDomain() + "/login" + "?accessToken=" + token.getAccessToken() + "&refreshToken=" + token.getRefreshToken();
+        } else if(title.equals(WcChatMessageTemplateIdEnum.MERCHANT_APPLY_CHECK_RESULT.getName())){
+            // 商户入驻审核结果通知
+            return redisConfig.getMallDomain() + "/#/pages/public/merchant";
+        }
+        return null;
+    }
+
+    /**
+     * 推送消息
+     */
+    public void jPush(RequestBody requestBody, Long memberUserId, Long systemUserId, Long objectId) {
+        String url = "https://api.jpush.cn/v3/push";
+
+        String authString = appKey + ":" + secret;
+        String encodedAuthString = Base64.encode(authString);
+
+
+        try {
+            RequestConfig clientConfig = RequestConfig.custom().setConnectionRequestTimeout(30000).setSocketTimeout(30000).setConnectTimeout(30000).build();
+            CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(clientConfig).build();
+
+            HttpPost httpPost = new HttpPost(url);
+            httpPost.setHeader("Authorization", "Basic " + encodedAuthString);
+            httpPost.setHeader("Content-Type", "application/json");
+            JSONObject message = JSONUtil.parseObj(JSONUtil.toJsonStr(requestBody));
+            httpPost.setEntity(new StringEntity(message.toString(), ContentType.APPLICATION_JSON));
+
+            CloseableHttpResponse execute = client.execute(httpPost);
+
+            int statusCode = execute.getStatusLine().getStatusCode();
+            log.info("请求状态:{}", statusCode);
+
+            HttpEntity entity = execute.getEntity();
+            String str = EntityUtils.toString(entity);
+            log.info("请求结果:{}", str);
+
+            // 记录推送日志
+            JPushMessageLogSaveReqVO jPushMessageLogSaveReqVO = JPushMessageLogSaveReqVO.builder()
+                    .regId(JSONUtil.toJsonStr(requestBody.getAudience().getRegistration_id()))
+                    .memberUserId(memberUserId)
+                    .systemUserId(systemUserId)
+                    .objectId(objectId)
+                    .jpushParams(message.toString())
+                    .sendTime(LocalDateTime.now())
+                    .responseResult(str)
+                    .build();
+            jPushService.createJPushMessageLog(jPushMessageLogSaveReqVO);
+        } catch (Exception e) {
+            log.info("用户rsgId:{}", JSONUtil.toJsonStr(requestBody.getAudience().getRegistration_id()));
+            log.info("推送异常:{}", e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 判断系统用户是否有接收消息的权限
+     *
+     * @param userId     用户id
+     * @param messageName 推送的消息
+     * @return 是否有权限
+     */
+    public Boolean isHavePermission(Long userId, String messageName) {
+        if (messageName.equals(WcChatMessageTemplateIdEnum.AFTER_SALE_NO_CHECK2.getName())) {
+            // 售后待审核
+            return permissionService.isHavePermission(userId, AFTER_SALE_NO_CHECK_List);
+        } else if (messageName.equals(WcChatMessageTemplateIdEnum.ORDER_NO_DELIVERY2.getName())) {
+            // 订单待发货
+            return permissionService.isHavePermission(userId, ORDER_NO_DELIVERY_List);
+        } else if (messageName.equals(WcChatMessageTemplateIdEnum.MERCHANT_APPLY_CHECK.getName())) {
+            // 商户入驻审核
+            return permissionService.isHavePermission(userId, MERCHANT_APPLY_CHECK_LIST);
+        }
+        return false;
+    }
+}

+ 34 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Callback.java

@@ -0,0 +1,34 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 回调参数
+ */
+
+@Data
+public class Callback {
+    /**
+     * 数据临时回调地址
+     * 指定后以此处指定为准,仅针对这一次推送请求生效。
+     * 不指定,则以极光后台配置为准。
+     */
+    private String url;
+    /**
+     * 需要回调给用户的自定义参数
+     */
+    private Map<String, String> params;
+    /**
+     * 回调数据类型。
+     * 1: 送达回执
+     * 2: 点击回执
+     * 3: 送达和点击回执
+     * 8: 推送成功回执
+     * 9: 成功和送达回执
+     * 10: 成功和点击回执
+     * 11: 成功和送达以及点击回执
+     */
+    private int type;
+}

+ 33 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Message.java

@@ -0,0 +1,33 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * 自定义消息
+ */
+
+@Data
+public class Message {
+    /**
+     * 消息内容本身
+     * 必填
+     */
+    private String msg_content;
+    /**
+     * 消息标题
+     * 可选
+     */
+    private String title;
+    /**
+     * 消息内容类型,开发者可根据自身业务定义具体类型。
+     * 可选
+     */
+    private String content_type;
+    /**
+     * JSON 格式的可选参数
+     * 可选
+     */
+    private Map<String, String> extras;
+}

+ 64 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/NotificationData.java

@@ -0,0 +1,64 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+import lombok.Data;
+
+/**
+ * 通知类
+ */
+
+@Data
+//@Builder
+public class NotificationData {
+    /**
+     * 通知的内容在各个平台上,都可能只有这一个最基本的属性 "alert"。
+     * 这个位置的 "alert" 属性(直接在 notification 对象下),是一个快捷定义,各平台的 alert 信息如果都一样,则以此定义为准。如果各平台有定义,则覆盖这里的定义。
+     */
+    private String alert;
+    /**
+     * android 平台
+     */
+    private Notification android;
+    /**
+     * ios 平台
+     */
+    private Notification ios;
+    /**
+     * hmos 平台
+     */
+    private Notification hmos;
+
+
+    @Data
+    public static class Intent {
+        private String url;
+    }
+
+    @Data
+    public static class Extras {
+        private int newsid;
+    }
+
+    @Data
+    public static class Notification {
+        /**
+         * 通知内容
+         */
+        private String alert;
+        /**
+         * 通知标题
+         */
+        private String title;
+        /**
+         * 通知样式ID
+         */
+        private int builder_id;
+        /**
+         * 指定跳转页面
+         */
+        private Intent intent;
+        /**
+         * 扩展字段(基本不用)
+         */
+        private Extras extras;
+    }
+}

+ 90 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/Options.java

@@ -0,0 +1,90 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 可选参数类
+ */
+
+@Data
+@Builder
+public class Options {
+    /**
+     * 离线消息保留时长 (秒)
+     * 推送当前用户不在线时,为该用户保留多长时间的离线消息,以便其上线时再次推送。
+     * 默认 86400 (1 天),普通用户最长 3 天, VIP 用户最长 10 天。设置为 0 表示不保留离线消息,只有推送当前在线的用户可以收到。
+     * 该字段对 iOS 的 Notification 消息无效。
+     */
+    private int time_to_live;
+
+    /**
+     * APNs 是否生产环境
+     * 该字段仅对 iOS 的 Notification 有效,如果不指定则为推送生产环境。注意:JPush 服务端 SDK 默认设置为推送 “开发环境”。
+     * true:表示推送生产环境。
+     * false:表示推送开发环境。
+     */
+    private boolean apns_production;
+
+    /**
+     * 消息类型分类
+     * 极光不对指定的消息类型进行判断或校准,会以开发者自行指定的消息类型适配 Android 厂商通道。不填默认为 0。
+     * 0:代表运营消息。
+     * 1:代表系统消息。
+     * 此字段优先级最高,会覆盖 options.third_party_channel.vivo.classification 设置的值。
+     */
+    private int classification;
+
+    /**
+     * 推送请求下发通道
+     * 仅针对配置了厂商用户使用有效
+     */
+    private ThirdPartyChannel third_party_channel;
+
+    /**
+     * 厂商通道类
+     * key 只支持 xiaomi、huawei、honor、meizu、oppo、vivo、fcm 类型用户。 key 可以为上述 7 个类型中的其中一个或者多个同时存在,未传递的 key 其对应的厂商下发走默认下发逻辑。
+     */
+    @Data
+    @Builder
+    public static class ThirdPartyChannel {
+        private Channel huawei;
+        private Channel xiaomi;
+        private Channel honor;
+        private Channel oppo;
+        private Channel vivo;
+    }
+
+    @Data
+    @Builder
+    public static class Channel {
+        /**
+         * 华为、荣耀通知栏消息智能分类
+         * LOW:一般消息。
+         * NORMAL:重要消息。
+         * HIGH:非常重要消息(仅华为支持)。
+         */
+        private String importance;
+        /**
+         * 华为、vivo 厂商消息场景标识
+         * 华为的参数值参考文档: <a href="https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/message-classification-0000001149358835#section1085395991513">...</a>
+         */
+        private String category;
+
+        /**
+         * 小米推送平台登记的 channel id
+         * OPPO 官网登记的通道 ID
+         */
+        private String channel_id;
+
+        /**
+         * 通知栏消息下发逻辑
+         * first_ospush(VIP):成功注册厂商通道的设备走厂商通道,仅注册极光通道的设备走极光通道。
+         * ospush(VIP):表示推送强制走厂商通道下发。 需要特别注意,只要指定此值的厂商对应配额不够时,推送请求会失败,返回 1012 错误码。
+         * 举例:假设指定一个小米用户的 RegistrationID 推送,请求时针对小米、OPPO 等厂商通道都指定了“ospush”,且 OPPO 厂商通道都配额已经用完,则推送同样会返回 1012 错误,提示厂商配额不足。
+         * jpush:表示推送强制走极光通道下发。
+         * secondary_push:表示推送优先走极光,极光不在线再走厂商,厂商作为辅助(建议此种方式)。
+         */
+        private String distribution;
+    }
+}

+ 56 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/utils/push/pojo/RequestBody.java

@@ -0,0 +1,56 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 推送通知类
+ */
+
+@Data
+@Builder
+public class RequestBody {
+    /**
+     * 推送平台
+     * "all"就是所有
+     * {"platform" : ["android", "ios","quickapp","hmos"] },这是指定
+     */
+    private String platform;
+    /**
+     * 推送目标
+     * "all" 全部,"tag" 标签,"registration_id" 注册id,"alias" 别名
+     */
+    private Audience audience;
+    /**
+     * 通知
+     */
+    private NotificationData notification;
+    /**
+     * 通知的内容在各个平台上,都可能只有这一个最基本的属性 "alert"。
+     * 这个位置的 "alert" 属性(直接在 notification 对象下),是一个快捷定义,各平台的 alert 信息如果都一样,则以此定义为准。如果各平台有定义,则覆盖这里的定义。
+     */
+    private String alert;
+    /**
+     * 自定义消息
+     */
+    private Message message;
+    /**
+     * 可选参数
+     */
+    private Options options;
+    /**
+     * 回调参数
+     */
+    private Callback callback;
+
+    @Data
+    public static class Audience {
+        /**
+         * 用户注册的极光id
+         * 还有其他很多类型的选择,tag,alias,live_activity_id等等,不过我们项目就只用到这个
+         */
+        private List<String> registration_id;
+    }
+}

+ 12 - 0
feifan-module-mall/feifan-module-trade-biz/src/main/resources/mapper/jpushmessagelog/JPushMessageLogMapper.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.newfeifan.mall.module.trade.dal.mysql.jpushmessagelog.JPushMessageLogMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见:https://www.zhongxing.cn/MyBatis/x-plugins/
+     -->
+
+</mapper>

+ 5 - 0
feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/dal/dataobject/user/MemberUserDO.java

@@ -213,4 +213,9 @@ public class MemberUserDO extends TenantBaseDO {
      */
     private String bankAccount;
 
+    /**
+     * 安卓app注册id
+     */
+    private String androidRegisterId;
+
 }

+ 8 - 0
feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserService.java

@@ -219,4 +219,12 @@ public interface MemberUserService {
      * @param username 用户名
      */
     List<MemberUserRespVO> getUserListByUsername(String username);
+
+    /**
+     * 通过系统用户或者会员用户来获取对应的rsgId
+     * @param systemUserIds 系统用户
+     * @param userId 会员用固话
+     * @return rsgId列表
+     */
+    List<String> getUserRsgIds(Long systemUserIds, Long userId);
 }

+ 17 - 0
feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserServiceImpl.java

@@ -36,6 +36,7 @@ import javax.validation.Valid;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.newfeifan.mall.framework.common.util.servlet.ServletUtils.getClientIP;
@@ -359,4 +360,20 @@ public class MemberUserServiceImpl implements MemberUserService {
         ), MemberUserRespVO.class);
     }
 
+    @Override
+    public List<String> getUserRsgIds(Long systemUserId, Long userId) {
+        List<MemberUserDO> memberUserDOS;
+        if (systemUserId != null) {
+            memberUserDOS = memberUserMapper.selectList(new LambdaQueryWrapper<MemberUserDO>()
+                    .eq(MemberUserDO::getSystemUsersId, systemUserId)
+                    .isNotNull(MemberUserDO::getAndroidRegisterId));
+        } else {
+            memberUserDOS = memberUserMapper.selectList(new LambdaQueryWrapper<MemberUserDO>()
+                    .eq(MemberUserDO::getId, userId)
+                    .isNotNull(MemberUserDO::getAndroidRegisterId));
+
+        }
+        return memberUserDOS.stream().map(MemberUserDO::getAndroidRegisterId).collect(Collectors.toList());
+    }
+
 }

+ 60 - 0
feifan-module-sale/feifan-module-sale-biz/src/main/java/cn/newfeifan/mall/sale/service/merchantapply/MerchantApplyServiceImpl.java

@@ -10,6 +10,8 @@ import cn.newfeifan.mall.module.member.dal.dataobject.user.MemberUserDO;
 import cn.newfeifan.mall.module.member.service.user.MemberUserService;
 import cn.newfeifan.mall.module.system.controller.admin.ip.vo.AreaNodeRespVO;
 import cn.newfeifan.mall.module.trade.enums.wxmessage.WcChatMessageTemplateIdEnum;
+import cn.newfeifan.mall.module.trade.utils.push.JPushUtils;
+import cn.newfeifan.mall.module.trade.utils.push.pojo.RequestBody;
 import cn.newfeifan.mall.module.trade.utils.wechat.WcChatMessageUtils;
 import cn.newfeifan.mall.sale.controller.admin.merchant.vo.MerchantSaveReqVO;
 import cn.newfeifan.mall.sale.controller.admin.merchantapply.vo.MerchantApplyPageReqVO;
@@ -73,6 +75,8 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
 
     @Resource
     private WcChatMessageUtils wcChatMessageUtils;
+    @Resource
+    private JPushUtils jPushUtils;
 
     @Override
     public Long createMerchantApply(MerchantApplySaveReqVO createReqVO) {
@@ -113,6 +117,9 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
             // 商户审核不通过向用户发送微信信息
             sentWxChatMessageToTheMemberUser(updateDO);
 
+            // 商户审核不通过向商户发送极光推送
+            jPushToTheMemberUser(updateDO);
+
             return;
         } else {
             // 审核通过
@@ -133,6 +140,9 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
 
                 // todo 商户审核通过向上级发送微信信息
                 sentWxChantMessageToThSystemUser(updateDO,MerchantApplyCheckPersonStatusEnum.TWO.getUserid());
+
+                // 发送极光推送
+                jPushToThSystemUser(updateDO, MerchantApplyCheckPersonStatusEnum.TWO.getUserid());
                 return;
             } else if (MerchantApplyCheckPersonStatusEnum.getCheckPersonStatus(loginUserId).equals(MerchantApplyCheckPersonStatusEnum.TWO.getStatus())) {
                 // 修改为向三级审核人审核,状态不变
@@ -150,6 +160,8 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
 
                 // todo 商户审核通过向上级发送微信信息
                 sentWxChantMessageToThSystemUser(updateDO,MerchantApplyCheckPersonStatusEnum.THREE.getUserid());
+                // 发送极光推送
+                jPushToThSystemUser(updateDO, MerchantApplyCheckPersonStatusEnum.THREE.getUserid());
 
                 return;
             }
@@ -199,6 +211,8 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
 
             // 商户审核通过向用户发送微信信息
             sentWxChatMessageToTheMemberUser(updateDO);
+            // 商户审核不通过向商户发送极光推送
+            jPushToTheMemberUser(updateDO);
         } else if (Objects.equals(updateDO.getCheckStatus(), MerchantApplyCheckStatusEnum.AUDIT_FAIL.getType())) {
             if (merchantDO != null) {
                 merchantDO.setStatus(MerchantStatusEnum.STOP.getType());
@@ -224,9 +238,27 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
                 params, getLoginUserId(), merchantApply.getApplyMemberUserId(), merchantApply.getId());
     }
 
+    private void jPushToTheMemberUser(MerchantApplyDO merchantApply){
+        String title = WcChatMessageTemplateIdEnum.MERCHANT_APPLY_CHECK_RESULT.getName();
+
+        String alert = "商户名称: " + merchantApply.getName();
+        alert += "\n审核结果: " + (merchantApply.getCheckStatus().equals(MerchantApplyCheckStatusEnum.AUDIT_PASS.getType()) ? "通过" : "拒绝");
+
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        alert += "\n审核时间: " + LocalDateTime.now().format(formatter);
+
+        List<String> userRsgIds = memberUserService.getUserRsgIds(null, merchantApply.getApplyMemberUserId());
+        if (userRsgIds != null) {
+            String url = jPushUtils.getUrl(title, merchantApply.getId(), null);
+            RequestBody requestBody = jPushUtils.getRequestBody(userRsgIds, alert, title, url);
+            jPushUtils.jPush(requestBody, merchantApply.getApplyMemberUserId(), null, merchantApply.getId());
+        }
+    }
+
     /**
      * 发送微信信息到系统用户
      * @param merchantApply 入驻申请
+     * @param sentSystemUserId 系统用户id
      */
     private void sentWxChantMessageToThSystemUser(MerchantApplyDO merchantApply, Long sentSystemUserId){
         List<String> params = new ArrayList<>();
@@ -241,6 +273,34 @@ public class MerchantApplyServiceImpl implements MerchantApplyService {
                 params, sentSystemUserId, merchantApply.getId());
     }
 
+    /**
+     * 发送极光推送到系统用户
+     * @param merchantApply 入驻申请
+     * @param sentSystemUserId 系统用户id
+     */
+    private void jPushToThSystemUser(MerchantApplyDO merchantApply, Long sentSystemUserId){
+        String title = WcChatMessageTemplateIdEnum.MERCHANT_APPLY_CHECK.getName();
+
+        String alert = "商户名称: " + merchantApply.getName();
+        alert += "\n负责人: " + merchantApply.getContact();
+        alert += "\n负责人手机号: " + merchantApply.getContactNumber();
+
+        alert += "\n办公地址: " + (merchantApply.getAddress() == null || merchantApply.getAddress().isEmpty() ? "-" : merchantApply.getAddress());
+        alert += "\n所在地: " + getAreaById(merchantApply.getAreaId().intValue());
+
+        // 如果没有权限就不用发送了
+        Boolean havePermission = jPushUtils.isHavePermission(sentSystemUserId, title);
+        if(!havePermission){
+            return;
+        }
+        List<String> userRsgIds = memberUserService.getUserRsgIds(sentSystemUserId, null);
+        if (userRsgIds != null) {
+            String url = jPushUtils.getUrl(title, merchantApply.getId(), sentSystemUserId);
+            RequestBody requestBody = jPushUtils.getRequestBody(userRsgIds, alert, title, url);
+            jPushUtils.jPush(requestBody, merchantApply.getApplyMemberUserId(), sentSystemUserId, merchantApply.getId());
+        }
+    }
+
     private String getAreaById(Integer id){
         Area area = AreaUtils.getArea(Area.ID_CHINA);
         Assert.notNull(area, "获取不到中国");

+ 1 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionService.java

@@ -143,4 +143,5 @@ public interface PermissionService {
      */
     DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
 
+    Boolean isHavePermission(Long userId, String... permissions);
 }

+ 26 - 0
feifan-module-system/feifan-module-system-biz/src/main/java/cn/newfeifan/mall/module/system/service/permission/PermissionServiceImpl.java

@@ -19,6 +19,7 @@ import cn.newfeifan.mall.module.system.enums.permission.DataScopeEnum;
 import cn.newfeifan.mall.module.system.service.dept.DeptService;
 import cn.newfeifan.mall.module.system.service.user.AdminUserService;
 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Suppliers;
 import com.google.common.collect.Sets;
@@ -32,6 +33,7 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import java.util.*;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import static cn.newfeifan.mall.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.newfeifan.mall.framework.common.util.json.JsonUtils.toJsonString;
@@ -326,6 +328,30 @@ public class PermissionServiceImpl implements PermissionService {
         return result;
     }
 
+    @Override
+    public Boolean isHavePermission(Long userId, String... permissions) {
+        List<UserRoleDO> userRoleDOS = userRoleMapper.selectList(new LambdaQueryWrapper<UserRoleDO>()
+                .eq(UserRoleDO::getUserId, userId));
+        //代表这个系统用户没有角色
+        if(userRoleDOS.isEmpty()){
+            return false;
+        }
+        List<Long> roleIds = userRoleDOS.stream().map(UserRoleDO::getRoleId).collect(Collectors.toList());
+        List<RoleMenuDO> roleMenuDOS = roleMenuMapper.selectList(new LambdaQueryWrapper<RoleMenuDO>()
+                .in(RoleMenuDO::getRoleId, roleIds));
+        //角色没有权限
+        if(roleMenuDOS.isEmpty()){
+            return false;
+        }
+        List<Long> menuIds = roleMenuDOS.stream().map(RoleMenuDO::getMenuId).collect(Collectors.toList());
+
+        List<MenuDO> menuList = menuService.getMenuList(menuIds);
+        List<String> menuPermissions = menuList.stream().map(MenuDO::getPermission).collect(Collectors.toList());
+
+        Set<String> permissionSet = new HashSet<>(Arrays.asList(permissions));
+        return menuPermissions.stream().anyMatch(permissionSet::contains);
+    }
+
     /**
      * 获得自身的代理对象,解决 AOP 生效问题
      *

+ 4 - 0
feifan-server/src/main/resources/application-local.yaml

@@ -265,4 +265,8 @@ justauth:
 #    file:
 #       disabled: true
 
+#极光推送配置
+jpush:
+  app-key: ae2e75f19ee1b240e744191b
+  secret: ae5e69155a3cb68ffd94158e
 

+ 5 - 0
feifan-server/src/main/resources/application-prod.yaml

@@ -269,4 +269,9 @@ justauth:
 #    file:
 #       disabled: true
 
+#极光推送配置
+jpush:
+  app-key: ae2e75f19ee1b240e744191b
+  secret: ae5e69155a3cb68ffd94158e
+
 

+ 16 - 0
sql/mysql/建空库SQL/25_20241011.sql

@@ -0,0 +1,16 @@
+CREATE TABLE `jpush_message_log` (
+                                     `id` bigint NOT NULL AUTO_INCREMENT COMMENT '消息记录ID,自增主键',
+                                     `reg_id` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收消息用户的appID',
+                                     `member_user_id` bigint NOT NULL COMMENT '会员用户ID',
+                                     `system_user_id` bigint DEFAULT NULL COMMENT '系统用户ID',
+                                     `jpush_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '极光消息推送参数',
+                                     `object_id` bigint NOT NULL DEFAULT '0' COMMENT '对应的业务对象ID,如订单ID、售后订单ID',
+                                     `send_time` datetime DEFAULT NULL COMMENT '发送时间',
+                                     `response_result` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '极光返回的消息响应结果',
+                                     `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
+                                     `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                                     `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
+                                     `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+                                     `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
+                                     PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='极光推送记录表';