feat:【mp】完善“模版消息”的功能

This commit is contained in:
YunaiV
2025-11-26 18:30:07 +08:00
parent b6f75b3825
commit 04fb7b107e
27 changed files with 478 additions and 1308 deletions

View File

@@ -61,7 +61,10 @@ public interface ErrorCodeConstants {
ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复");
ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复");
// ========== 公众号消息模板 1-006-010-000 ============
ErrorCode MSG_TEMPLATE_NOT_EXISTS = new ErrorCode(1_006_010_000, "消息模板不存在");
ErrorCode MSG_TEMPLATE_LOG_NOT_EXISTS = new ErrorCode(1_006_010_001, "微信模版消息发送记录不存在");
// ========== 公众号模版消息 1-006-010-000 ============
ErrorCode MESSAGE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_006_010_000, "模版消息不存在");
ErrorCode MESSAGE_TEMPLATE_DELETE_FAIL = new ErrorCode(1_006_010_002, "删除模版消息失败,原因:{}");
ErrorCode MESSAGE_TEMPLATE_SYNC_FAIL = new ErrorCode(1_006_010_003, "同步模版消息失败,原因:{}");
ErrorCode MESSAGE_TEMPLATE_SEND_FAIL = new ErrorCode(1_006_010_004, "发送模版消息失败,原因:{}");
}

View File

@@ -0,0 +1,44 @@
### 请求 /mp/message-template/get 接口 => 成功
GET {{baseUrl}}/mp/message-template/get?id=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /mp/message-template/list 接口 => 成功
GET {{baseUrl}}/mp/message-template/list?accountId=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /mp/message-template/delete 接口 => 成功
DELETE {{baseUrl}}/mp/message-template/delete?id=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /mp/message-template/sync 接口 => 成功
POST {{baseUrl}}/mp/message-template/sync?accountId=5
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
### 请求 /mp/message-template/send 接口 => 成功
POST {{baseUrl}}/mp/message-template/send
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}}
{
"id": 66,
"userId": 65,
"url": "https://example.com",
"data": {
"result": "领奖成功",
"withdrawMoney": "1000.00元",
"withdrawTime": "2024-01-01 10:00:00",
"cardInfo": "工商银行(尾号1234)",
"arrivedTime": "2024-01-01 10:30:00"
}
}
// "miniprogram": "{\"appid\":\"wx1234567890\",\"pagepath\":\"pages/index/index\"}",

View File

@@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.mp.controller.admin.message;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MpMessageTemplateDO;
import cn.iocoder.yudao.module.mp.service.template.MpMessageTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 公众号模版消息")
@RestController
@RequestMapping("/mp/message-template")
@Validated
public class MpMessageTemplateController {
@Resource
private MpMessageTemplateService messageTemplateService;
@DeleteMapping("/delete")
@Operation(summary = "删除模版消息")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('mp:message-template:delete')")
public CommonResult<Boolean> deleteMessageTemplate(@RequestParam("id") Long id) {
messageTemplateService.deleteMessageTemplate(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得模版消息")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('mp:message-template:query')")
public CommonResult<MpMessageTemplateRespVO> getMessageTemplate(@RequestParam("id") Long id) {
MpMessageTemplateDO msgTemplate = messageTemplateService.getMessageTemplate(id);
return success(BeanUtils.toBean(msgTemplate, MpMessageTemplateRespVO.class));
}
@GetMapping("/list")
@Operation(summary = "获得模版消息列表")
@Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048")
@PreAuthorize("@ss.hasPermission('mp:message-template:query')")
public CommonResult<List<MpMessageTemplateRespVO>> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) {
List<MpMessageTemplateDO> list = messageTemplateService.getMessageTemplateList(listReqVO);
return success(BeanUtils.toBean(list, MpMessageTemplateRespVO.class));
}
@PostMapping("/sync")
@Operation(summary = "同步公众号模板")
@Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048")
@PreAuthorize("@ss.hasPermission('mp:message-template:sync')")
public CommonResult<Boolean> syncMessageTemplate(@RequestParam("accountId") Long accountId) {
messageTemplateService.syncMessageTemplate(accountId);
return success(true);
}
@PostMapping("/send")
@Operation(summary = "给粉丝发送模版消息")
@PreAuthorize("@ss.hasPermission('mp:message-template:send')")
public CommonResult<Boolean> sendMessageTemplate(@Valid @RequestBody MpMessageTemplateSendReqVO sendReqVO) {
messageTemplateService.sendMessageTempalte(sendReqVO);
return success(true);
}
}

View File

@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - 公众号模版消息列表 Request VO")
@Data
public class MpMessageTemplateListReqVO {
@Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 公众号模版消息 Response VO")
@Data
public class MpMessageTemplateRespVO {
@Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019")
private Long id;
@Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long accountId;
@Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890abcdef")
private String appId;
@Schema(description = "公众号模板ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "IjkGxO9M_mC9pE5Yl7QYJk1h0Dj2N4lC3oOp6rRsT8u")
private String templateId;
@Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单状态提醒")
private String title;
@Schema(description = "模板内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "模板示例")
private String example;
@Schema(description = "模板所属行业的一级行业", example = "电商")
private String primaryIndustry;
@Schema(description = "模板所属行业的二级行业", example = "商品售后")
private String deputyIndustry;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
@Schema(description = "管理后台 - 公众号消息模版发送 Request VO") // 关联 https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html 文档
@Data
public class MpMessageTemplateSendReqVO {
@Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019")
@NotNull(message = "模版主键不能为空")
private Long id;
@Schema(description = "公众号粉丝的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "公众号粉丝的编号不能为空")
private Long userId;
@Schema(description = "模板跳转链接")
private String url;
@Schema(description = "跳转小程序时填写")
private String miniprogram;
@Schema(description = "模板内容")
private Map<String, String> data;
}

View File

@@ -1,136 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import java.io.IOException;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateBatchReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplatePageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateDO;
import cn.iocoder.yudao.module.mp.service.template.MsgTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import me.chanjar.weixin.common.error.WxErrorException;
/**
* @author dengsl
*/
@Tag(name = "管理后台 - 消息模板")
@RestController
@RequestMapping("/mp/template")
@Validated
public class MsgTemplateController {
@Resource
private MsgTemplateService msgTemplateService;
@PostMapping("/create")
@Operation(summary = "创建消息模板")
@PreAuthorize("@ss.hasPermission('mp:template:create')")
public CommonResult<Long> createMsgTemplate(@Valid @RequestBody MsgTemplateSaveReqVO createReqVO) {
return success(msgTemplateService.createMsgTemplate(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新消息模板")
@PreAuthorize("@ss.hasPermission('mp:template:update')")
public CommonResult<Boolean> updateMsgTemplate(@Valid @RequestBody MsgTemplateSaveReqVO updateReqVO) {
msgTemplateService.updateMsgTemplate(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除消息模板")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('mp:template:delete')")
public CommonResult<Boolean> deleteMsgTemplate(@RequestParam("id") Long id) {
//msgTemplateService.deleteMsgTemplate(id);
//TODO 该逻辑没有实现 删除需要判断该消息模板是否被关联
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除消息模板")
@PreAuthorize("@ss.hasPermission('mp:template:delete')")
public CommonResult<Boolean> deleteMsgTemplateList(@RequestBody List<Long> ids) {
//msgTemplateService.deleteMsgTemplateListByIds(ids);
//TODO 该逻辑没有实现 删除需要判断该消息模板是否被关联
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得消息模板")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('mp:template:query')")
public CommonResult<MsgTemplateRespVO> getMsgTemplate(@RequestParam("id") Long id) {
MsgTemplateDO msgTemplate = msgTemplateService.getMsgTemplate(id);
return success(BeanUtils.toBean(msgTemplate, MsgTemplateRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得消息模板分页")
@PreAuthorize("@ss.hasPermission('mp:template:query')")
public CommonResult<PageResult<MsgTemplateRespVO>> getMsgTemplatePage(@Valid MsgTemplatePageReqVO pageReqVO) {
PageResult<MsgTemplateDO> pageResult = msgTemplateService.getMsgTemplatePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, MsgTemplateRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出消息模板 Excel")
@PreAuthorize("@ss.hasPermission('mp:template:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportMsgTemplateExcel(@Valid MsgTemplatePageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<MsgTemplateDO> list = msgTemplateService.getMsgTemplatePage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "消息模板.xls", "数据", MsgTemplateRespVO.class,
BeanUtils.toBean(list, MsgTemplateRespVO.class));
}
@GetMapping("/syncMsgTemplate")
@Operation(summary = "同步公众号模板")
@PreAuthorize("@ss.hasPermission('mp:template:sync')")
public CommonResult<Boolean> syncWxTemplate(@RequestParam("accountId") Long accountId) throws WxErrorException {
msgTemplateService.syncWxTemplate(accountId);
return success(true);
}
/**
* 批量向用户发送模板消息
* 通过用户筛选条件(一般使用标签筛选),将消息发送给数据库中所有符合筛选条件的用户
*/
@PostMapping("/sendMsgBatch")
@Operation(summary = "批量向用户发送模板消息")
@PreAuthorize("@ss.hasPermission('mp:template:send')")
public CommonResult<Boolean> sendMsgBatch(@Valid @RequestBody MsgTemplateBatchReqVO batchReqVO) {
if (StrUtil.isEmpty(batchReqVO.getOpenid()) && StrUtil.isEmpty(batchReqVO.getUnionId())
&& StrUtil.isEmpty(batchReqVO.getNickname()) && CollUtil.isEmpty((batchReqVO.getOpenidList()))) {
return error(BAD_REQUEST.getCode(), "请选择用户");
}
msgTemplateService.sendMsgBatch(batchReqVO);
return success(true);
}
}

View File

@@ -1,105 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import java.io.IOException;
import java.util.List;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateLogDO;
import cn.iocoder.yudao.module.mp.service.template.MsgTemplateLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
/**
* @author dengsl
*/
@Tag(name = "管理后台 - 微信模版消息发送记录")
@RestController
@RequestMapping("/mp/template/log")
@Validated
public class MsgTemplateLogController {
@Resource
private MsgTemplateLogService msgTemplateLogService;
@PostMapping("/create")
@Operation(summary = "创建微信模版消息发送记录")
@PreAuthorize("@ss.hasPermission('mp:template-log:create')")
public CommonResult<Long> createMsgTemplateLog(@Valid @RequestBody MsgTemplateLogSaveReqVO createReqVO) {
return success(msgTemplateLogService.createMsgTemplateLog(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新微信模版消息发送记录")
@PreAuthorize("@ss.hasPermission('mp:template-log:update')")
public CommonResult<Boolean> updateMsgTemplateLog(@Valid @RequestBody MsgTemplateLogSaveReqVO updateReqVO) {
msgTemplateLogService.updateMsgTemplateLog(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除微信模版消息发送记录")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('mp:template-log:delete')")
public CommonResult<Boolean> deleteMsgTemplateLog(@RequestParam("id") Long id) {
msgTemplateLogService.deleteMsgTemplateLog(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Parameter(name = "ids", description = "编号", required = true)
@Operation(summary = "批量删除微信模版消息发送记录")
@PreAuthorize("@ss.hasPermission('mp:template-log:delete')")
public CommonResult<Boolean> deleteMsgTemplateLogList(@RequestParam("ids") List<Long> ids) {
msgTemplateLogService.deleteMsgTemplateLogListByIds(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得微信模版消息发送记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('mp:template-log:query')")
public CommonResult<MsgTemplateLogRespVO> getMsgTemplateLog(@RequestParam("id") Long id) {
MsgTemplateLogDO msgTemplateLog = msgTemplateLogService.getMsgTemplateLog(id);
return success(BeanUtils.toBean(msgTemplateLog, MsgTemplateLogRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得微信模版消息发送记录分页")
@PreAuthorize("@ss.hasPermission('mp:template-log:query')")
public CommonResult<PageResult<MsgTemplateLogRespVO>> getMsgTemplateLogPage(@Valid MsgTemplateLogPageReqVO pageReqVO) {
PageResult<MsgTemplateLogDO> pageResult = msgTemplateLogService.getMsgTemplateLogPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, MsgTemplateLogRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出微信模版消息发送记录 Excel")
@PreAuthorize("@ss.hasPermission('mp:template-log:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportMsgTemplateLogExcel(@Valid MsgTemplateLogPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<MsgTemplateLogDO> list = msgTemplateLogService.getMsgTemplateLogPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "微信模版消息发送记录.xls", "数据", MsgTemplateLogRespVO.class,
BeanUtils.toBean(list, MsgTemplateLogRespVO.class));
}
}

View File

@@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 消息批量推送 Request VO")
@Data
public class MsgTemplateBatchReqVO extends MpUserPageReqVO {
@Schema(description = "appId", example = "9758")
@NotNull(message = "appId不能为空")
private String appId;
@Schema(description = "公众号模板ID", example = "14517")
@NotNull(message = "公众号模板ID不能为空")
private String templateId;
}

View File

@@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
import java.time.LocalDateTime;
import org.springframework.format.annotation.DateTimeFormat;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 微信模版消息发送记录分页 Request VO")
@Data
public class MsgTemplateLogPageReqVO extends PageParam {
@Schema(description = "appId", example = "6914")
private String appId;
@Schema(description = "用户openid")
private String toUser;
@Schema(description = "公众号模板ID", example = "8374")
private String templateId;
@Schema(description = "消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
private String url;
@Schema(description = "小程序appid", example = "21567")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "发送时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] sendTime;
@Schema(description = "发送状态 0成功1失败", example = "2")
private Integer sendStatus;
@Schema(description = "发送结果")
private String sendResult;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -1,66 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import java.time.LocalDateTime;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 微信模版消息发送记录 Response VO")
@Data
@ExcelIgnoreUnannotated
public class MsgTemplateLogRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "22254")
@ExcelProperty("主键")
private Long id;
@Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "6914")
@ExcelProperty("appId")
private String appId;
@Schema(description = "用户openid")
@ExcelProperty("用户openid")
private String toUser;
@Schema(description = "公众号模板ID", example = "8374")
@ExcelProperty("公众号模板ID")
private String templateId;
@Schema(description = "消息内容")
@ExcelProperty("消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
@ExcelProperty("链接")
private String url;
@Schema(description = "小程序appid", example = "21567")
@ExcelProperty("小程序appid")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
@ExcelProperty("小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "发送时间")
@ExcelProperty("发送时间")
private LocalDateTime sendTime;
@Schema(description = "发送状态 0成功1失败", example = "2")
@ExcelProperty("发送状态 0成功1失败")
private String sendStatus;
@Schema(description = "发送结果")
@ExcelProperty("发送结果")
private String sendResult;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -1,68 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import java.time.LocalDateTime;
import com.alibaba.fastjson.JSON;
import cn.hutool.core.util.ObjectUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 微信模版消息发送记录新增/修改 Request VO")
@Data
public class MsgTemplateLogSaveReqVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "22254")
private Long id;
@Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "6914")
@NotEmpty(message = "appId不能为空")
private String appId;
@Schema(description = "用户openid")
private String toUser;
@Schema(description = "公众号模板ID", example = "8374")
private String templateId;
@Schema(description = "消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
private String url;
@Schema(description = "小程序appid", example = "21567")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "发送时间")
private LocalDateTime sendTime;
@Schema(description = "发送状态 0成功1失败", example = "2")
private Integer sendStatus;
@Schema(description = "发送结果")
private String sendResult;
public MsgTemplateLogSaveReqVO(WxMpTemplateMessage msg, String appid, Integer sendStatus, String sendResult) {
this.appId = appid;
this.toUser = msg.getToUser();
this.templateId = msg.getTemplateId();
this.url = msg.getUrl();
if (ObjectUtil.isNotEmpty(msg.getMiniProgram())) {
this.miniProgramAppId = msg.getMiniProgram().getAppid();
this.miniProgramPagePath = msg.getMiniProgram().getPagePath();
}
this.sendStatus = sendStatus;
this.data = JSON.toJSONString(msg.getData());
this.sendTime = LocalDateTime.now();
this.sendResult = sendResult;
}
}

View File

@@ -1,51 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import java.time.LocalDateTime;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 消息模板分页 Request VO")
@Data
public class MsgTemplatePageReqVO extends PageParam {
@Schema(description = "appId", example = "9758")
private String appId;
@Schema(description = "公众号账号的编号", example = "9758")
private Long accountId;
@Schema(description = "公众号模板ID", example = "14517")
private String templateId;
@Schema(description = "模版名称", example = "赵六")
private String name;
@Schema(description = "标题")
private String title;
@Schema(description = "模板内容")
private String content;
@Schema(description = "消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
private String url;
@Schema(description = "小程序appid")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "是否有效", example = "1")
private Integer status;
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
}

View File

@@ -1,78 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import java.time.LocalDateTime;
import cn.idev.excel.annotation.ExcelIgnore;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 消息模板 Response VO")
@Data
@ExcelIgnoreUnannotated
public class MsgTemplateRespVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019")
@ExcelProperty("主键")
private Long id;
@Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "9758")
@ExcelProperty("appId")
private String appId;
@Schema(description = "公众号模板ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "14517")
@ExcelProperty("公众号模板ID")
private String templateId;
@Schema(description = "模版名称", example = "赵六")
@ExcelProperty("模版名称")
private String name;
@Schema(description = "标题")
@ExcelProperty("标题")
private String title;
@Schema(description = "模板内容")
@ExcelProperty("模板内容")
private String content;
@Schema(description = "消息内容")
@ExcelProperty("消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
@ExcelProperty("链接")
private String url;
@Schema(description = "小程序appId")
@ExcelProperty("小程序appId")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
@ExcelProperty("小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("是否有效")
private Integer status;
@Schema(description = "公众号是否已移除 0未移除,1已移除", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("公众号是否已移除")
private Integer isRemoved;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "模板消息配置id")
@ExcelIgnore
private Long configId;
@Schema(description = "模板类型")
@ExcelIgnore
private String templateType;
}

View File

@@ -1,56 +0,0 @@
package cn.iocoder.yudao.module.mp.controller.admin.template.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author dengsl
*/
@Schema(description = "管理后台 - 消息模板新增/修改 Request VO")
@Data
public class MsgTemplateSaveReqVO {
@Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019")
private Long id;
@Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "9758")
@NotEmpty(message = "appId不能为空")
private String appId;
@Schema(description = "公众号模板ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "14517")
@NotEmpty(message = "公众号模板ID不能为空")
private String templateId;
@Schema(description = "模版名称", example = "赵六")
private String name;
@Schema(description = "标题")
private String title;
@Schema(description = "模板内容")
private String content;
@Schema(description = "消息内容")
private String data;
@Schema(description = "链接", example = "https://www.iocoder.cn")
private String url;
@Schema(description = "小程序appid")
private String miniProgramAppId;
@Schema(description = "小程序页面路径")
private String miniProgramPagePath;
@Schema(description = "模板所属行业的一级行业")
private String primaryIndustry;
@Schema(description = "模板所属行业的二级行业")
private String deputyIndustry;
@Schema(description = "是否有效", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "是否有效不能为空")
private Integer status;
}

View File

@@ -30,4 +30,5 @@ public class MpUserPageReqVO extends PageParam {
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private List<String> openidList;
}

View File

@@ -4,8 +4,6 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateBatchReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
@@ -25,8 +23,6 @@ public interface MpUserConvert {
MpUserRespVO convert(MpUserDO bean);
MpUserPageReqVO convert(MsgTemplateBatchReqVO bean);
List<MpUserRespVO> convertList(List<MpUserDO> list);
PageResult<MpUserRespVO> convertPage(PageResult<MpUserDO> page);

View File

@@ -1,27 +1,26 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.template;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
/**
* 消息模板 DO
* 公众号模版消息 DO
*
* @author dengsl
*/
@TableName("mp_msg_template")
@KeySequence("mp_msg_template_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@TableName("mp_message_template")
@KeySequence("mp_message_template_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MsgTemplateDO extends BaseDO {
public class MpMessageTemplateDO extends BaseDO {
/**
* 主键
@@ -29,20 +28,21 @@ public class MsgTemplateDO extends BaseDO {
@TableId
private Long id;
/**
* appid
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 公众号模板ID
* 公众号模板 ID
*/
private String templateId;
/**
* 模版名称
*/
private String name;
public String getName() {
return this.templateId;
}
/**
* 标题
@@ -53,21 +53,10 @@ public class MsgTemplateDO extends BaseDO {
*/
private String content;
/**
* 消息内容
* 模板示例
*/
private String data;
/**
* 链接
*/
private String url;
/**
* 小程序appid
*/
private String miniProgramAppId;
/**
* 小程序页面路径
*/
private String miniProgramPagePath;
private String example;
/**
* 模板所属行业的一级行业
*/
@@ -76,17 +65,5 @@ public class MsgTemplateDO extends BaseDO {
* 模板所属行业的二级行业
*/
private String deputyIndustry;
/**
* 模板示例
*/
private String example;
/**
* 是否有效 0有效,1无效
*/
private Integer status;
/**
* 公众号是否已移除 0未移除,1已移除
*/
private Integer isRemoved;
}

View File

@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mp.dal.mysql.message;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MpMessageTemplateDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MpMessageTemplateMapper extends BaseMapperX<MpMessageTemplateDO> {
default List<MpMessageTemplateDO> selectList(MpMessageTemplateListReqVO listReqVO) {
return selectList(MpMessageTemplateDO::getAccountId, listReqVO.getAccountId());
}
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.mp.dal.mysql.template;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateLogDO;
/**
* 微信模版消息发送记录 Mapper
*
* @author dengsl
*/
@Mapper
public interface MsgTemplateLogMapper extends BaseMapperX<MsgTemplateLogDO> {
default PageResult<MsgTemplateLogDO> selectPage(MsgTemplateLogPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MsgTemplateLogDO>()
.eqIfPresent(MsgTemplateLogDO::getAppId, reqVO.getAppId())
.eqIfPresent(MsgTemplateLogDO::getToUser, reqVO.getToUser())
.eqIfPresent(MsgTemplateLogDO::getTemplateId, reqVO.getTemplateId())
.eqIfPresent(MsgTemplateLogDO::getData, reqVO.getData())
.eqIfPresent(MsgTemplateLogDO::getUrl, reqVO.getUrl())
.eqIfPresent(MsgTemplateLogDO::getMiniProgramAppId, reqVO.getMiniProgramAppId())
.eqIfPresent(MsgTemplateLogDO::getMiniProgramPagePath, reqVO.getMiniProgramPagePath())
.betweenIfPresent(MsgTemplateLogDO::getSendTime, reqVO.getSendTime())
.eqIfPresent(MsgTemplateLogDO::getSendStatus, reqVO.getSendStatus())
.eqIfPresent(MsgTemplateLogDO::getSendResult, reqVO.getSendResult())
.betweenIfPresent(MsgTemplateLogDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MsgTemplateLogDO::getId));
}
}

View File

@@ -1,35 +0,0 @@
package cn.iocoder.yudao.module.mp.dal.mysql.template;
import org.apache.ibatis.annotations.Mapper;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplatePageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateDO;
/**
* 消息模板 Mapper
*
* @author dengsl
*/
@Mapper
public interface MsgTemplateMapper extends BaseMapperX<MsgTemplateDO> {
default PageResult<MsgTemplateDO> selectPage(MsgTemplatePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MsgTemplateDO>()
.eqIfPresent(MsgTemplateDO::getAppId, reqVO.getAppId())
.eqIfPresent(MsgTemplateDO::getTemplateId, reqVO.getTemplateId())
.likeIfPresent(MsgTemplateDO::getName, reqVO.getName())
.eqIfPresent(MsgTemplateDO::getTitle, reqVO.getTitle())
.eqIfPresent(MsgTemplateDO::getContent, reqVO.getContent())
.eqIfPresent(MsgTemplateDO::getData, reqVO.getData())
.eqIfPresent(MsgTemplateDO::getUrl, reqVO.getUrl())
.eqIfPresent(MsgTemplateDO::getMiniProgramAppId, reqVO.getMiniProgramAppId())
.eqIfPresent(MsgTemplateDO::getMiniProgramPagePath, reqVO.getMiniProgramPagePath())
.eqIfPresent(MsgTemplateDO::getStatus, reqVO.getStatus())
.betweenIfPresent(MsgTemplateDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(MsgTemplateDO::getId));
}
}

View File

@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.mp.service.template;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MpMessageTemplateDO;
import java.util.List;
/**
* 公众号模版消息 Service 接口
*
* @author dengsl
*/
public interface MpMessageTemplateService {
/**
* 删除模版消息
*
* @param id 编号
*/
void deleteMessageTemplate(Long id);
/**
* 获得模版消息
*
* @param id 编号
* @return 模版消息
*/
MpMessageTemplateDO getMessageTemplate(Long id);
/**
* 获得模版消息列表
*
* @param listReqVO 查询条件
* @return 模版消息列表
*/
List<MpMessageTemplateDO> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO);
/**
* 同步公众号已添加的模版消息
*
* @param accountId 公众号账号的编号
*/
void syncMessageTemplate(Long accountId);
/**
* 使用公众号,给粉丝发送【模版】消息
*
* @param sendReqVO 消息内容
*/
void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO);
}

View File

@@ -0,0 +1,176 @@
package cn.iocoder.yudao.module.mp.service.template;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MpMessageTemplateDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageTemplateMapper;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.template.WxMpTemplate;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
/**
* 公众号模版消息 Service 实现类
*
* @author dengsl
*/
@Service
@Validated
@Slf4j
public class MpMessageTemplateServiceImpl implements MpMessageTemplateService {
@Resource
@Lazy // 延迟加载,为了解决延迟加载
private MpServiceFactory mpServiceFactory;
@Resource
private MpMessageTemplateMapper messageTemplateMapper;
@Resource
private MpAccountService mpAccountService;
@Resource
private MpUserService mpUserService;
@Override
public void deleteMessageTemplate(Long id) {
// 校验存在
MpMessageTemplateDO template = validateMsgTemplateExists(id);
// 第一步,删除模板到公众号平台
try {
mpServiceFactory.getRequiredMpService(template.getAppId())
.getTemplateMsgService().delPrivateTemplate(template.getTemplateId());
} catch (WxErrorException e) {
throw exception(MESSAGE_TEMPLATE_DELETE_FAIL, e.getError().getErrorMsg());
}
// 第二步,删除模板到数据库
messageTemplateMapper.deleteById(id);
}
private MpMessageTemplateDO validateMsgTemplateExists(Long id) {
MpMessageTemplateDO template = messageTemplateMapper.selectById(id);
if (template == null) {
throw exception(MESSAGE_TEMPLATE_NOT_EXISTS);
}
return template;
}
@Override
public MpMessageTemplateDO getMessageTemplate(Long id) {
return messageTemplateMapper.selectById(id);
}
@Override
public List<MpMessageTemplateDO> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) {
return messageTemplateMapper.selectList(listReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void syncMessageTemplate(Long accountId) {
MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
// 第一步,从公众号平台获取最新的模板列表
List<WxMpTemplate> wxTemplates;
try {
wxTemplates = mpServiceFactory.getRequiredMpService(accountId)
.getTemplateMsgService().getAllPrivateTemplate();
} catch (WxErrorException e) {
throw exception(MESSAGE_TEMPLATE_SYNC_FAIL, e.getError().getErrorMsg());
}
// 第二步,合并更新回自己的数据库
Map<String, MpMessageTemplateDO> templateMap = convertMap(
messageTemplateMapper.selectList(new LambdaQueryWrapperX<MpMessageTemplateDO>()
.eq(MpMessageTemplateDO::getAppId, account.getAppId())),
MpMessageTemplateDO::getTemplateId);
wxTemplates.forEach(wxTemplate -> {
MpMessageTemplateDO template = templateMap.remove(wxTemplate.getTemplateId());
// 情况一,不存在,新增
if (template == null) {
template = new MpMessageTemplateDO().setAccountId(account.getId()).setAppId(account.getAppId())
.setTemplateId(wxTemplate.getTemplateId()).setTitle(wxTemplate.getTitle())
.setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample())
.setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry());
messageTemplateMapper.insert(template);
return;
}
// 情况二,存在,则更新
messageTemplateMapper.updateById(new MpMessageTemplateDO().setId(template.getId())
.setTitle(wxTemplate.getTitle()).setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample())
.setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry()));
});
// 情况三,部分模板已经不存在了,删除
if (CollUtil.isNotEmpty(templateMap)) {
messageTemplateMapper.deleteByIds(convertList(templateMap.values(), MpMessageTemplateDO::getId));
}
}
@Override
public void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO) {
// 获得关联信息
MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());
MpMessageTemplateDO template = validateMsgTemplateExists(sendReqVO.getId());
// 发送模版消息
WxMpTemplateMessage templateMessage = buildTemplateMessage(template, user, sendReqVO);
try {
mpServiceFactory.getRequiredMpService(template.getAppId())
.getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (WxErrorException e) {
throw exception(MESSAGE_TEMPLATE_SEND_FAIL, e.getError().getErrorMsg());
}
// 不用记录 MpMessageDO 记录,因为,微信会主动推送,可见文档 https://developers.weixin.qq.com/doc/service/guide/product/template_message/Template_Message_Interface.html
}
private WxMpTemplateMessage buildTemplateMessage(MpMessageTemplateDO msgTemplateDO, MpUserDO user,
MpMessageTemplateSendReqVO sendReqVO) {
List<WxMpTemplateData> data = new ArrayList<>();
WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder()
.templateId(msgTemplateDO.getTemplateId())
.data(data)
.toUser(user.getOpenid());
// 设置跳转链接
if (StrUtil.isNotBlank(sendReqVO.getUrl())) {
builder.url(sendReqVO.getUrl());
}
// 设置小程序跳转
if (StrUtil.isNotBlank(sendReqVO.getMiniprogram())) {
// https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html#Body__miniprogram
builder.miniProgram(JsonUtils.parseObject(sendReqVO.getMiniprogram(), WxMpTemplateMessage.MiniProgram.class));
}
// 设置模板数据
if (sendReqVO.getData() != null) {
sendReqVO.getData().forEach((key, value) -> data.add(new WxMpTemplateData(key, value)));
}
return builder.build();
}
}

View File

@@ -1,63 +0,0 @@
package cn.iocoder.yudao.module.mp.service.template;
import java.util.List;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateLogDO;
import jakarta.validation.Valid;
/**
* 微信模版消息发送记录 Service 接口
*
* @author dengsl
*/
public interface MsgTemplateLogService {
/**
* 创建微信模版消息发送记录
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createMsgTemplateLog(@Valid MsgTemplateLogSaveReqVO createReqVO);
/**
* 更新微信模版消息发送记录
*
* @param updateReqVO 更新信息
*/
void updateMsgTemplateLog(@Valid MsgTemplateLogSaveReqVO updateReqVO);
/**
* 删除微信模版消息发送记录
*
* @param id 编号
*/
void deleteMsgTemplateLog(Long id);
/**
* 批量删除微信模版消息发送记录
*
* @param ids 编号
*/
void deleteMsgTemplateLogListByIds(List<Long> ids);
/**
* 获得微信模版消息发送记录
*
* @param id 编号
* @return 微信模版消息发送记录
*/
MsgTemplateLogDO getMsgTemplateLog(Long id);
/**
* 获得微信模版消息发送记录分页
*
* @param pageReqVO 分页查询
* @return 微信模版消息发送记录分页
*/
PageResult<MsgTemplateLogDO> getMsgTemplateLogPage(MsgTemplateLogPageReqVO pageReqVO);
}

View File

@@ -1,89 +0,0 @@
package cn.iocoder.yudao.module.mp.service.template;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MSG_TEMPLATE_LOG_NOT_EXISTS;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateLogDO;
import cn.iocoder.yudao.module.mp.dal.mysql.template.MsgTemplateLogMapper;
import jakarta.annotation.Resource;
/**
* 微信模版消息发送记录 Service 实现类
*
* @author dengsl
*/
@Service
@Validated
public class MsgTemplateLogServiceImpl implements MsgTemplateLogService {
@Resource
private MsgTemplateLogMapper msgTemplateLogMapper;
@Override
public Long createMsgTemplateLog(MsgTemplateLogSaveReqVO createReqVO) {
// 插入
MsgTemplateLogDO msgTemplateLog = BeanUtils.toBean(createReqVO, MsgTemplateLogDO.class);
msgTemplateLogMapper.insert(msgTemplateLog);
// 返回
return msgTemplateLog.getId();
}
@Override
public void updateMsgTemplateLog(MsgTemplateLogSaveReqVO updateReqVO) {
// 校验存在
validateMsgTemplateLogExists(updateReqVO.getId());
// 更新
MsgTemplateLogDO updateObj = BeanUtils.toBean(updateReqVO, MsgTemplateLogDO.class);
msgTemplateLogMapper.updateById(updateObj);
}
@Override
public void deleteMsgTemplateLog(Long id) {
// 校验存在
validateMsgTemplateLogExists(id);
// 删除
msgTemplateLogMapper.deleteById(id);
}
@Override
public void deleteMsgTemplateLogListByIds(List<Long> ids) {
// 校验存在
validateMsgTemplateLogExists(ids);
// 删除
msgTemplateLogMapper.deleteByIds(ids);
}
private void validateMsgTemplateLogExists(List<Long> ids) {
List<MsgTemplateLogDO> list = msgTemplateLogMapper.selectByIds(ids);
if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
throw exception(MSG_TEMPLATE_LOG_NOT_EXISTS);
}
}
private void validateMsgTemplateLogExists(Long id) {
if (msgTemplateLogMapper.selectById(id) == null) {
throw exception(MSG_TEMPLATE_LOG_NOT_EXISTS);
}
}
@Override
public MsgTemplateLogDO getMsgTemplateLog(Long id) {
return msgTemplateLogMapper.selectById(id);
}
@Override
public PageResult<MsgTemplateLogDO> getMsgTemplateLogPage(MsgTemplateLogPageReqVO pageReqVO) {
return msgTemplateLogMapper.selectPage(pageReqVO);
}
}

View File

@@ -1,87 +0,0 @@
package cn.iocoder.yudao.module.mp.service.template;
import java.util.List;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateBatchReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplatePageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateDO;
import jakarta.validation.Valid;
import me.chanjar.weixin.common.error.WxErrorException;
/**
* 消息模板 Service 接口
*
* @author dengsl
*/
public interface MsgTemplateService {
/**
* 创建消息模板
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createMsgTemplate(@Valid MsgTemplateSaveReqVO createReqVO);
/**
* 更新消息模板
*
* @param updateReqVO 更新信息
*/
void updateMsgTemplate(@Valid MsgTemplateSaveReqVO updateReqVO);
/**
* 删除消息模板
*
* @param id 编号
*/
void deleteMsgTemplate(Long id);
/**
* 批量删除消息模板
*
* @param ids 编号
*/
void deleteMsgTemplateListByIds(List<Long> ids);
/**
* 获得消息模板
*
* @param id 编号
* @return 消息模板
*/
MsgTemplateDO getMsgTemplate(Long id);
/**
* 获得消息模板分页
*
* @param pageReqVO 分页查询
* @return 消息模板分页
*/
PageResult<MsgTemplateDO> getMsgTemplatePage(MsgTemplatePageReqVO pageReqVO);
/**
* 同步公众号已添加的消息模板
*
* @throws WxErrorException
*/
void syncWxTemplate(Long accountId) throws WxErrorException;
/**
* 获得公众号已添加的消息模板
*
* @param appId 公众号 AppId
* @return 模板列表
*/
MsgTemplateDO getWxTemplate(String appId, String templateId);
/**
* 批量消息发送
*
* @param batchReqVO
*/
void sendMsgBatch(MsgTemplateBatchReqVO batchReqVO);
}

View File

@@ -1,313 +0,0 @@
package cn.iocoder.yudao.module.mp.service.template;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MSG_TEMPLATE_NOT_EXISTS;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.text.CharSequenceUtil;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.yulichang.toolkit.MPJWrappers;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateBatchReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateLogSaveReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplatePageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.template.vo.MsgTemplateSaveReqVO;
import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.template.MsgTemplateDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.dal.mysql.template.MsgTemplateMapper;
import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.template.WxMpTemplate;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateData;
import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;
/**
* 消息模板 Service 实现类
*
* @author dengsl
*/
@Service
@Validated
@Slf4j
public class MsgTemplateServiceImpl implements MsgTemplateService {
@Resource
@Lazy // 延迟加载,为了解决延迟加载
private MpServiceFactory mpServiceFactory;
@Resource
private MsgTemplateMapper msgTemplateMapper;
@Resource
private MpUserService mpUserService;
@Resource
private MpAccountService mpAccountService;
@Resource
private MsgTemplateLogService msgTemplateLogService;
@Override
public Long createMsgTemplate(MsgTemplateSaveReqVO createReqVO) {
// 插入
MsgTemplateDO msgTemplate = BeanUtils.toBean(createReqVO, MsgTemplateDO.class);
msgTemplateMapper.insert(msgTemplate);
// 返回
return msgTemplate.getId();
}
@Override
public void updateMsgTemplate(MsgTemplateSaveReqVO updateReqVO) {
// 校验存在
validateMsgTemplateExists(updateReqVO.getId());
// 更新
MsgTemplateDO updateObj = BeanUtils.toBean(updateReqVO, MsgTemplateDO.class);
msgTemplateMapper.updateById(updateObj);
}
@Override
public void deleteMsgTemplate(Long id) {
// 校验存在
validateMsgTemplateExists(id);
// 删除
msgTemplateMapper.deleteById(id);
}
@Override
public void deleteMsgTemplateListByIds(List<Long> ids) {
// 校验存在
validateMsgTemplateExists(ids);
// 删除
msgTemplateMapper.deleteByIds(ids);
}
private void validateMsgTemplateExists(List<Long> ids) {
List<MsgTemplateDO> list = msgTemplateMapper.selectByIds(ids);
if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
throw exception(MSG_TEMPLATE_NOT_EXISTS);
}
}
private void validateMsgTemplateExists(Long id) {
if (msgTemplateMapper.selectById(id) == null) {
throw exception(MSG_TEMPLATE_NOT_EXISTS);
}
}
@Override
public MsgTemplateDO getMsgTemplate(Long id) {
return msgTemplateMapper.selectById(id);
}
@Override
public PageResult<MsgTemplateDO> getMsgTemplatePage(MsgTemplatePageReqVO pageReqVO) {
MpAccountDO account = mpAccountService.getAccount(pageReqVO.getAccountId());
if (account == null) {
throw exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS);
}
pageReqVO.setAppId(account.getAppId());
return msgTemplateMapper.selectPage(pageReqVO);
}
@Override
public void syncWxTemplate(Long accountId) throws WxErrorException {
// 1. 处理新增的模板(在微信中存在但在数据库中不存在)
// 2. 处理已删除的模板(在数据库中存在但在微信中不存在)
// 3. 处理已存在的模板(在两边都存在)- 更新
MpAccountDO mpAccountDO = mpAccountService.getAccount(accountId);
String appId = mpAccountDO.getAppId();
List<WxMpTemplate> wxTemplates = mpServiceFactory.getRequiredMpService(appId).getTemplateMsgService().getAllPrivateTemplate();
if (CollUtil.isNotEmpty(wxTemplates)) {
List<MsgTemplateDO> dbTemplates = msgTemplateMapper.selectList(new QueryWrapper<MsgTemplateDO>()
.lambda().eq(MsgTemplateDO::getAppId, appId));
// 将微信模板转换为Map便于查找
Map<String, WxMpTemplate> wxTemplateMap = wxTemplates.stream()
.collect(Collectors.toMap(WxMpTemplate::getTemplateId, Function.identity()));
// 将数据库模板转换为Map便于查找
Map<String, MsgTemplateDO> dbTemplateMap = dbTemplates.stream()
.collect(Collectors.toMap(MsgTemplateDO::getTemplateId, Function.identity()));
// 1. 处理新增的模板(在微信中存在但在数据库中不存在)
handleNewTemplates(appId, wxTemplateMap, dbTemplateMap);
// 2. 处理已删除的模板(在数据库中存在但在微信中不存在)
handleDeletedTemplates(wxTemplateMap, dbTemplateMap);
// 3. 处理已存在的模板(在两边都存在)- 清空已设置的信息
handleUpdatedTemplates(wxTemplateMap, dbTemplateMap);
return;
}
log.info("没有模板 appId {} ", appId);
}
/**
* 处理新增的模板
*/
private void handleNewTemplates(String appId, Map<String, WxMpTemplate> wxTemplateMap,
Map<String, MsgTemplateDO> dbTemplateMap) {
List<MsgTemplateDO> newTemplates = new ArrayList<>();
wxTemplateMap.forEach((templateId, wxTemplate) -> {
if (!dbTemplateMap.containsKey(templateId)) {
MsgTemplateDO newTemplate = new MsgTemplateDO()
.setAppId(appId)
.setTemplateId(templateId)
.setTitle(wxTemplate.getTitle())
.setPrimaryIndustry(wxTemplate.getPrimaryIndustry())
.setDeputyIndustry(wxTemplate.getDeputyIndustry())
.setContent(wxTemplate.getContent())
.setExample(wxTemplate.getExample());
newTemplates.add(newTemplate);
}
});
if (CollUtil.isNotEmpty(newTemplates)) {
msgTemplateMapper.insertBatch(newTemplates);
log.info("批量新增公众号模板: appId={}, count={}", appId, newTemplates.size());
}
}
/**
* 处理已删除的模板
*/
private void handleDeletedTemplates(Map<String, WxMpTemplate> wxTemplateMap,
Map<String, MsgTemplateDO> dbTemplateMap) {
List<MsgTemplateDO> removedTemplates = new ArrayList<>();
dbTemplateMap.forEach((templateId, dbTemplate) -> {
if (!wxTemplateMap.containsKey(templateId) && dbTemplate.getIsRemoved() == 0) {
dbTemplate.setIsRemoved(1);
removedTemplates.add(dbTemplate);
}
});
if (CollUtil.isNotEmpty(removedTemplates)) {
msgTemplateMapper.update(new LambdaUpdateWrapper<MsgTemplateDO>()
.set(MsgTemplateDO::getIsRemoved, 1)
.in(MsgTemplateDO::getId, removedTemplates.stream().map(MsgTemplateDO::getId).collect(Collectors.toList())));
log.info("批量标记公众号模板为已删除: count={}", removedTemplates.size());
}
}
/**
* 处理已存在的模板(在两边都存在)- 清空已设置的信息
*/
private void handleUpdatedTemplates(Map<String, WxMpTemplate> wxTemplateMap,
Map<String, MsgTemplateDO> dbTemplateMap) {
List<Long> updatedIds = new ArrayList<>();
dbTemplateMap.forEach((templateId, dbTemplate) -> {
WxMpTemplate wxTemplate = wxTemplateMap.get(templateId);
if (wxTemplate != null) {
// 更新数据库模板信息
updatedIds.add(dbTemplate.getId());
}
});
if (CollUtil.isNotEmpty(updatedIds)) {
msgTemplateMapper.update(new LambdaUpdateWrapper<MsgTemplateDO>()
.set(MsgTemplateDO::getData, null)
.set(MsgTemplateDO::getUrl, null)
.set(MsgTemplateDO::getMiniProgramAppId, null)
.set(MsgTemplateDO::getMiniProgramPagePath, null)
.set(MsgTemplateDO::getExample, null)
.in(MsgTemplateDO::getId, updatedIds));
log.info("批量更新公众号模板: count={}", updatedIds.size());
}
}
@Override
public MsgTemplateDO getWxTemplate(String appId, String templateId) {
return msgTemplateMapper.selectOne(new LambdaQueryWrapperX<MsgTemplateDO>()
.eq(MsgTemplateDO::getAppId, appId)
.eq(CharSequenceUtil.isNotEmpty(templateId), MsgTemplateDO::getTemplateId, templateId));
}
@Override
public void sendMsgBatch(MsgTemplateBatchReqVO batchReqVO) {
log.info("批量发送模板消息任务开始, 参数:{}", batchReqVO);
// 获取微信模板信息
MsgTemplateDO msgTemplateDO = getWxTemplate(batchReqVO.getAppId(), batchReqVO.getTemplateId());
if (ObjectUtil.isNull(msgTemplateDO)) {
log.error("未找到对应的模板信息, appId: {}, templateId: {}", batchReqVO.getAppId(), batchReqVO.getTemplateId());
throw exception(MSG_TEMPLATE_NOT_EXISTS);
}
if (msgTemplateDO.getIsRemoved() == 1) {
throw new ServiceException(GlobalErrorCodeConstants.ERROR_CONFIGURATION.getCode(), "模板未发布");
}
if (msgTemplateDO.getStatus() == 1) {
throw new ServiceException(GlobalErrorCodeConstants.ERROR_CONFIGURATION.getCode(), "模板无效");
}
// 构建基础模板消息
WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder()
.templateId(msgTemplateDO.getTemplateId())
.url(msgTemplateDO.getUrl())
.miniProgram(new WxMpTemplateMessage.MiniProgram(
msgTemplateDO.getMiniProgramAppId(),
msgTemplateDO.getMiniProgramPagePath(),
false))
.data(JSON.parseArray(msgTemplateDO.getData(), WxMpTemplateData.class));
int currentPage = 1;
long totalPages = Long.MAX_VALUE;
while (currentPage <= totalPages) {
// 按条件查询用户
batchReqVO.setPageNo(currentPage);
batchReqVO.setPageSize(500);
PageResult<MpUserDO> wxUsers = mpUserService.getUserPage(MpUserConvert.INSTANCE.convert(batchReqVO));
log.info("批量发送模板消息任务, 使用查询条件,处理第{}页,总用户数:{}", currentPage, wxUsers.getTotal());
// 如果没有用户数据,直接退出循环
if (CollUtil.isEmpty(wxUsers.getList())) {
log.warn("当前页无用户数据,结束处理, 当前页:{}", currentPage);
break;
}
// 遍历用户并发送模板消息
wxUsers.getList().forEach(user -> {
WxMpTemplateMessage wxMpTemplateMessage = builder.toUser(user.getOpenid()).build();
try {
String result = mpServiceFactory.getRequiredMpService(batchReqVO.getAppId()).getTemplateMsgService().sendTemplateMsg(wxMpTemplateMessage);
//保存发送日志
MsgTemplateLogSaveReqVO log = new MsgTemplateLogSaveReqVO(wxMpTemplateMessage, batchReqVO.getAppId(), 0, result);
msgTemplateLogService.createMsgTemplateLog(log);
} catch (WxErrorException e) {
log.error("发送模板消息失败, 用户OpenId: {}, 错误信息: {}", user.getOpenid(), e.getMessage(), e);
// 可以选择继续处理其他用户,而不是直接抛出异常
//保存发送日志
MsgTemplateLogSaveReqVO log = new MsgTemplateLogSaveReqVO(wxMpTemplateMessage, batchReqVO.getAppId(), 1, e.getMessage());
msgTemplateLogService.createMsgTemplateLog(log);
}
});
// 更新分页参数
currentPage++;
// 计算总页数
totalPages = (wxUsers.getTotal() + batchReqVO.getPageSize() - 1) / batchReqVO.getPageSize();
}
log.info("批量发送模板消息任务完成");
}
}