Browse Source

添加极光推送

Yangzw 2 months ago
parent
commit
a3c4594a97
22 changed files with 1074 additions and 15 deletions
  1. 2 0
      feifan-module-mall/feifan-module-trade-api/src/main/java/cn/newfeifan/mall/module/trade/enums/ErrorCodeConstants.java
  2. 94 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. 48 2
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/aftersale/AfterSaleServiceImpl.java
  9. 53 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogService.java
  10. 70 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/jpushmessagelog/JPushMessageLogServiceImpl.java
  11. 47 0
      feifan-module-mall/feifan-module-trade-biz/src/main/java/cn/newfeifan/mall/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  12. 182 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. 87 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. 15 0
      feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserService.java
  20. 48 11
      feifan-module-member/feifan-module-member-biz/src/main/java/cn/newfeifan/mall/module/member/service/user/MemberUserServiceImpl.java
  21. 4 1
      feifan-server/src/main/resources/application-local.yaml
  22. 4 1
      feifan-server/src/main/resources/application-prod.yaml

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

@@ -96,5 +96,7 @@ public interface ErrorCodeConstants {
     ErrorCode MESSAGE_LOG_NOT_EXISTS = new ErrorCode(1_011_008_004, "微信模板消息日志不存在");
     ErrorCode ORDER_PAY_ERROR = new ErrorCode(1_011_008_005, "钱包积分不够支付");
     ErrorCode ORDER_PAY_CONSUMPTION_POINTS_ERROR = new ErrorCode(1_011_008_006, "钱包消费分不够支付");
+    ErrorCode J_PUSH_MESSAGE_LOG_NOT_EXISTS = new ErrorCode(1_011_008_011, "极光推送消息日志不存在");
+
 
 }

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

@@ -0,0 +1,94 @@
+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;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建极光推送记录")
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:create')")
+    public CommonResult<Long> createJPushMessageLog(@Valid @RequestBody JPushMessageLogSaveReqVO createReqVO) {
+        return success(jPushMessageLogService.createJPushMessageLog(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新极光推送记录")
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:update')")
+    public CommonResult<Boolean> updateJPushMessageLog(@Valid @RequestBody JPushMessageLogSaveReqVO updateReqVO) {
+        jPushMessageLogService.updateJPushMessageLog(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除极光推送记录")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('trade:J-push-message-log:delete')")
+    public CommonResult<Boolean> deleteJPushMessageLog(@RequestParam("id") Long id) {
+        jPushMessageLogService.deleteJPushMessageLog(id);
+        return success(true);
+    }
+
+    @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));
+    }
+
+}

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

@@ -32,6 +32,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 lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -49,6 +51,7 @@ import java.util.List;
 
 import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.newfeifan.mall.module.trade.enums.ErrorCodeConstants.*;
+
 import cn.newfeifan.mall.module.trade.enums.aftersale.AfterSaleMessageEnum;
 
 /**
@@ -83,6 +86,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
     @Resource
     private MemberUserService memberUserService;
     @Resource
+    private JPushUtils jPushUtils;
+    @Resource
     private AdminUserService adminUserService;
 
 
@@ -190,7 +195,10 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
         // 发送售后消息
         //发送微信模板消息给店铺及管理员
-        sentWcChatMessage(afterSale,AfterSaleMessageEnum.APPLY_FOR_AFTER_SALE.getName());
+        sentWcChatMessage(afterSale, AfterSaleMessageEnum.APPLY_FOR_AFTER_SALE.getName());
+
+        // 发送极光推送消息
+        sentJPush(afterSale, AfterSaleMessageEnum.APPLY_FOR_AFTER_SALE.getName());
 
         return afterSale;
     }
@@ -290,7 +298,10 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
         // 发送售后消息
         // 发送微信模板消息给店铺及管理员
-        sentWcChatMessage(afterSale,AfterSaleMessageEnum.WAIT_FOR_RECEIVING.getName());
+        sentWcChatMessage(afterSale, AfterSaleMessageEnum.WAIT_FOR_RECEIVING.getName());
+
+        // 发送极光推送消息
+        sentJPush(afterSale, AfterSaleMessageEnum.WAIT_FOR_RECEIVING.getName());
     }
 
     /**
@@ -320,6 +331,41 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         }
     }
 
+    /**
+     * 发送极光推送消息
+     *
+     * @param afterSale   售后订单
+     * @param afterStatus 售后状态
+     */
+    private void sentJPush(AfterSaleDO afterSale, String 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<Long> adminUsers = adminUserService.getUserIdsByShop(afterSale.getShopId());
+        for (Long adminUser : adminUsers) {
+            // 如果没有权限就不用发送了
+            Boolean havePermission = jPushUtils.isHavePermission(adminUser, title);
+            if(!havePermission){
+                return;
+            }
+            List<String> userRsgIds = memberUserService.getUserRsgIds(adminUser, null);
+            if (userRsgIds != null && !userRsgIds.isEmpty()) {
+                String url = jPushUtils.getUrl(title, afterSale.getUserId(), afterSale.getId());
+                RequestBody requestBody = jPushUtils.getRequestBody(userRsgIds, alert, title, url);
+                jPushUtils.jPush(requestBody, afterSale.getUserId(), adminUser, afterSale.getId());
+            }
+        }
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.ADMIN_AGREE_RECEIVE)

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

@@ -0,0 +1,53 @@
+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 updateReqVO 更新信息
+     */
+    void updateJPushMessageLog(@Valid JPushMessageLogSaveReqVO updateReqVO);
+
+    /**
+     * 删除极光推送记录
+     *
+     * @param id 编号
+     */
+    void deleteJPushMessageLog(Long id);
+
+    /**
+     * 获得极光推送记录
+     *
+     * @param id 编号
+     * @return 极光推送记录
+     */
+    JPushMessageLogDO getJPushMessageLog(Long id);
+
+    /**
+     * 获得极光推送记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 极光推送记录分页
+     */
+    PageResult<JPushMessageLogDO> getJPushMessageLogPage(JPushMessageLogPageReqVO pageReqVO);
+
+}

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

@@ -0,0 +1,70 @@
+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;
+
+import static cn.newfeifan.mall.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.newfeifan.mall.module.trade.enums.ErrorCodeConstants.*;
+
+/**
+ * 极光推送记录 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 void updateJPushMessageLog(JPushMessageLogSaveReqVO updateReqVO) {
+        // 校验存在
+        validateJPushMessageLogExists(updateReqVO.getId());
+        // 更新
+        JPushMessageLogDO updateObj = BeanUtils.toBean(updateReqVO, JPushMessageLogDO.class);
+        jPushMessageLogMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteJPushMessageLog(Long id) {
+        // 校验存在
+        validateJPushMessageLogExists(id);
+        // 删除
+        jPushMessageLogMapper.deleteById(id);
+    }
+
+    private void validateJPushMessageLogExists(Long id) {
+        if (jPushMessageLogMapper.selectById(id) == null) {
+            throw exception(J_PUSH_MESSAGE_LOG_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public JPushMessageLogDO getJPushMessageLog(Long id) {
+        return jPushMessageLogMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<JPushMessageLogDO> getJPushMessageLogPage(JPushMessageLogPageReqVO pageReqVO) {
+        return jPushMessageLogMapper.selectPage(pageReqVO);
+    }
+
+}

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

@@ -86,6 +86,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.QueryWrapper;
 import lombok.extern.slf4j.Slf4j;
@@ -196,6 +198,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
     @Resource
     private AdminUserService adminUserService;
+    @Resource
+    private JPushUtils jPushUtils;
 
     @Resource
     private PtProfitService ptProfitService;
@@ -1361,6 +1365,9 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
 
             // 发送给微信消息给系统用户待发货消息
             sentWcChatMessage(order);
+
+            // 发送极光推送
+            sentJPush(order);
         }
 
 
@@ -1738,6 +1745,46 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         }
     }
 
+    /**
+     * 发送极光推送消息
+     *
+     * @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.getRefundPrice() / 100);
+        String alert = "本单金额: ¥" + formattedPrice;
+        alert += "\n单据编号\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) {
+            // 如果没有权限就不用发送了
+            Boolean havePermission = jPushUtils.isHavePermission(adminUser, title);
+            if(!havePermission){
+                return;
+            }
+            List<String> userRsgIds = memberUserService.getUserRsgIds(adminUser, null);
+            if (userRsgIds != null && !userRsgIds.isEmpty()) {
+                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());
+            }
+        }
+    }
+
     /**
      * 校验交易订单满足被支付的条件
      * <p>

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

@@ -0,0 +1,182 @@
+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.member.service.user.MemberUserService;
+import cn.newfeifan.mall.module.system.dal.dataobject.config.SystemConfigDO;
+import cn.newfeifan.mall.module.system.service.config.SystemConfigService;
+import cn.newfeifan.mall.module.system.service.permission.PermissionService;
+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 MemberUserService memberUserService;
+    @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 = new Options();
+        options.setTime_to_live(86400);
+        options.setClassification(1);
+        options.setThird_party_channel(new Options.ThirdPartyChannel().setHuawei(new Options.Channel().setImportance("NORMAL").setCategory("WORK")));
+        options.setThird_party_channel(new Options.ThirdPartyChannel().setXiaomi(new Options.Channel().setChannel_id("NORMAL")));
+        options.setThird_party_channel(new Options.ThirdPartyChannel().setHonor(new Options.Channel().setImportance("NORMAL")));
+        options.setThird_party_channel(new Options.ThirdPartyChannel().setOppo(new Options.Channel().setChannel_id("message")));
+        options.setThird_party_channel(new Options.ThirdPartyChannel().setVivo(new Options.Channel().setDistribution("secondary_push").setCategory("IM")));
+        requestBody.setOptions(options);
+
+        return requestBody;
+    }
+
+    /**
+     * 获取url
+     * @param title 标题
+     * @param memberUserId 用户id
+     * @param objectId 对象id
+     * @return url
+     */
+    public String getUrl(String title, Long memberUserId, Long objectId){
+        SystemConfigDO redisConfig = configService.getRedisConfig();
+        String username = memberUserService.getUsernameByUserId(memberUserId);
+        if (title.equals(WcChatMessageTemplateIdEnum.ORDER_DELIVERY2.getName())) {
+            //订单已发货
+            return redisConfig.getMallDomain() + "/#/pages/order/detail?id=" + objectId + "&username=" + username;
+        } else if (title.equals(WcChatMessageTemplateIdEnum.AFTER_SALE_CHECK2.getName())) {
+            //售后订单审核完成
+            return redisConfig.getMallDomain() + "/#/pages/order/aftersale/detail?id=" + objectId + "&username=" + username;
+        } else if (title.equals(WcChatMessageTemplateIdEnum.AFTER_SALE_NO_CHECK2.getName())) {
+            //售后订单待审核
+            return redisConfig.getMerchantDomain() + "/after-sale";
+        } else if (title.equals(WcChatMessageTemplateIdEnum.ORDER_NO_DELIVERY2.getName())) {
+            //订单待发货
+            return redisConfig.getMerchantDomain() + "/order";
+        }
+        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;
+    }
+}

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

@@ -0,0 +1,87 @@
+package cn.newfeifan.mall.module.trade.utils.push.pojo;
+
+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
+    public static class ThirdPartyChannel {
+        private Channel huawei;
+        private Channel xiaomi;
+        private Channel honor;
+        private Channel oppo;
+        private Channel vivo;
+    }
+
+    @Data
+    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>

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

@@ -265,4 +265,19 @@ public interface MemberUserService {
      * @return 是否关注
      */
     Boolean getUserIsSubscribe(Long userId);
+
+    /**
+     * 通过系统用户或者会员用户来获取对应的rsgId
+     * @param systemUserIds 系统用户
+     * @param userId 会员用固话
+     * @return rsgId列表
+     */
+    List<String> getUserRsgIds(Long systemUserIds, Long userId);
+
+    /**
+     * 根据用户ID获取用户名
+     * @param userId 用户ID
+     * @return 用户名
+     */
+    String getUsernameByUserId(Long userId);
 }

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

@@ -31,6 +31,8 @@ import cn.newfeifan.mall.module.system.dal.mysql.social.SocialUserBindMapper;
 import cn.newfeifan.mall.module.system.dal.mysql.social.SocialUserMapper;
 import cn.newfeifan.mall.module.system.enums.sms.SmsSceneEnum;
 import cn.newfeifan.mall.module.system.service.user.AdminUserService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
@@ -46,6 +48,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;
@@ -464,11 +467,9 @@ public class MemberUserServiceImpl implements MemberUserService {
         if (registerId != null && !registerId.equals(memberUserDO.getAndroidRegisterId())) {
             // 防止一台设备登录多个账号会导致异常发送的可能,所以如果有同样的registerId就先删除后加入
             List<MemberUserDO> users = memberUserMapper.selectList(MemberUserDO::getAndroidRegisterId, registerId);
-            if(users != null){
-                for (MemberUserDO user : users) {
-                    user.setAndroidRegisterId(null);
-                    memberUserMapper.updateById(user);
-                }
+            for (MemberUserDO user : users) {
+                user.setAndroidRegisterId("");
+                memberUserMapper.update(new LambdaUpdateWrapper<MemberUserDO>().setSql(" android_register_id = null ").eq(MemberUserDO::getId, user.getId()));
             }
 
             memberUserDO.setAndroidRegisterId(registerId);
@@ -480,29 +481,62 @@ public class MemberUserServiceImpl implements MemberUserService {
     public Boolean getUserIsSubscribe(Long userId) {
         SocialUserBindDO socialUserBindDO = socialUserBindMapper.selectOne(SocialUserBindDO::getUserId, getLoginUserId());
         // 如果没有存储用户的openid就直接返回false
-        if(socialUserBindDO == null){
+        if (socialUserBindDO == null) {
             return false;
         }
         SocialUserDO socialUserDO = socialUserMapper.selectOne(SocialUserDO::getId, socialUserBindDO.getSocialUserId());
         // 如果没有存储用户的openid就直接返回false
-        if(socialUserDO == null){
+        if (socialUserDO == null) {
             return false;
         }
 
-        return isSubscribe(socialUserDO.getOpenid());
+        return isSubscribe(socialUserDO.getOpenid(), 1);
     }
 
-    private Boolean isSubscribe(String openid) {
+    @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());
+    }
+
+    @Override
+    public String getUsernameByUserId(Long userId) {
+        if (userId != null) {
+            return memberUserMapper.selectOne(MemberUserDO::getId, userId).getUsername();
+        }
+        return null;
+    }
+
+    private Boolean isSubscribe(String openid, int maxRetries) {
         String requestUrl = StrUtil.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token={}&openid={}&lang=zh_CN", getAccessToken(), openid);
         String returnMsg = HttpUtil.get(requestUrl);
         cn.hutool.json.JSONObject responseJsonObject = JSONUtil.parseObj(returnMsg);
         if (ObjectUtil.isNull(responseJsonObject)) try {
             throw new Exception("响应异常:获取信息为空!");
-        }catch (Exception e) {
+        } catch (Exception e) {
             throw new RuntimeException(e);
         }
-        Integer subscribe = (Integer) responseJsonObject.get("subscribe");
         log.info("用户信息:{}", responseJsonObject);
+        Integer subscribe = (Integer) responseJsonObject.get("subscribe");
+        if(subscribe == null){
+            Object errCode = responseJsonObject.get("errcode");
+//            如果微信的accessToken失效,则重新获取,最多重试3次
+            if(errCode.equals("40001") && maxRetries <= 3){
+                stringRedisTemplate.delete(WX_TICK_KEY_APPID);
+                isSubscribe(openid, maxRetries + 1);
+            }
+            return false;
+        }
         // 微信返回的数据中1为关注,0为未关注
         return subscribe == 1;
     }
@@ -540,6 +574,7 @@ public class MemberUserServiceImpl implements MemberUserService {
 
     /**
      * 校验手机号码
+     *
      * @param phoneNumber 手机号
      * @return 是否合法
      */
@@ -549,6 +584,7 @@ public class MemberUserServiceImpl implements MemberUserService {
 
     /**
      * 校验邮箱
+     *
      * @param email 邮箱
      * @return 是否合法
      */
@@ -559,6 +595,7 @@ public class MemberUserServiceImpl implements MemberUserService {
 
     /**
      * 校验银行账号
+     *
      * @param accountNumber 银行账号
      * @return 是否合法
      */

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

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

+ 4 - 1
feifan-server/src/main/resources/application-prod.yaml

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