feat(mp): 新增公众号消息模板功能

- 新增消息模板和模板发送记录的增删改查接口
- 新增消息模板和发送记录的分页查询及导出功能
- 新增批量发送模板消息功能
- 新增同步公众号模板功能
- 添加相关数据对象和错误码定义
- 扩展用户分页请求参数支持openid列表查询
- 完善模板消息相关的VO类和转换逻辑
This commit is contained in:
wuKong
2025-11-05 11:41:31 +08:00
parent d64f4e9343
commit 0ec835244d
20 changed files with 1435 additions and 0 deletions

View File

@@ -61,4 +61,7 @@ public interface ErrorCodeConstants {
ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); 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, "操作失败,原因:已关在该关键字的回复"); 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, "微信模版消息发送记录不存在");
} }

View File

@@ -0,0 +1,136 @@
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

@@ -0,0 +1,105 @@
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

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,55 @@
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

@@ -0,0 +1,66 @@
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

@@ -0,0 +1,68 @@
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

@@ -0,0 +1,51 @@
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

@@ -0,0 +1,78 @@
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

@@ -0,0 +1,56 @@
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

@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import java.util.List;
@Schema(description = "管理后台 - 公众号粉丝分页 Request VO") @Schema(description = "管理后台 - 公众号粉丝分页 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@@ -26,4 +28,6 @@ public class MpUserPageReqVO extends PageParam {
@Schema(description = "公众号粉丝昵称,模糊匹配", example = "芋艿") @Schema(description = "公众号粉丝昵称,模糊匹配", example = "芋艿")
private String nickname; private String nickname;
@Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private List<String> openidList;
} }

View File

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

View File

@@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.template;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
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
*
* @author dengsl
*/
@TableName("mp_msg_template")
@KeySequence("mp_msg_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MsgTemplateDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* appid
*/
private String appId;
/**
* 公众号模板ID
*/
private String templateId;
/**
* 模版名称
*/
private String name;
public String getName() {
return this.templateId;
}
/**
* 标题
*/
private String title;
/**
* 模板内容
*/
private String content;
/**
* 消息内容
*/
private String data;
/**
* 链接
*/
private String url;
/**
* 小程序appid
*/
private String miniProgramAppId;
/**
* 小程序页面路径
*/
private String miniProgramPagePath;
/**
* 模板所属行业的一级行业
*/
private String primaryIndustry;
/**
* 模板所属行业的二级行业
*/
private String deputyIndustry;
/**
* 模板示例
*/
private String example;
/**
* 是否有效 0有效,1无效
*/
private Integer status;
/**
* 公众号是否已移除 0未移除,1已移除
*/
private Integer isRemoved;
}

View File

@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.template;
import lombok.*;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 微信模版消息发送记录 DO
*
* @author dengsl
*/
@TableName("mp_msg_template_log")
@KeySequence("mp_msg_template_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MsgTemplateLogDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* appId
*/
private String appId;
/**
* 用户openid
*/
private String toUser;
/**
* 公众号模板ID
*/
private String templateId;
/**
* 消息内容
*/
private String data;
/**
* 链接
*/
private String url;
/**
* 小程序appid
*/
private String miniProgramAppId;
/**
* 小程序页面路径
*/
private String miniProgramPagePath;
/**
* 发送时间
*/
private LocalDateTime sendTime;
/**
* 发送状态 0成功1失败
*/
private Integer sendStatus;
/**
* 发送结果
*/
private String sendResult;
}

View File

@@ -0,0 +1,35 @@
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

@@ -0,0 +1,35 @@
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,63 @@
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

@@ -0,0 +1,89 @@
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

@@ -0,0 +1,87 @@
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

@@ -0,0 +1,313 @@
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("批量发送模板消息任务完成");
}
}