diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java index 34c94fd00..a6a7ca70e 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java @@ -61,4 +61,7 @@ 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, "微信模版消息发送记录不存在"); } diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateController.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateController.java new file mode 100644 index 000000000..726578a69 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateController.java @@ -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 createMsgTemplate(@Valid @RequestBody MsgTemplateSaveReqVO createReqVO) { + return success(msgTemplateService.createMsgTemplate(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新消息模板") + @PreAuthorize("@ss.hasPermission('mp:template:update')") + public CommonResult 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 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 deleteMsgTemplateList(@RequestBody List 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 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> getMsgTemplatePage(@Valid MsgTemplatePageReqVO pageReqVO) { + PageResult 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 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 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 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); + } +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateLogController.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateLogController.java new file mode 100644 index 000000000..04a9de589 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/MsgTemplateLogController.java @@ -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 createMsgTemplateLog(@Valid @RequestBody MsgTemplateLogSaveReqVO createReqVO) { + return success(msgTemplateLogService.createMsgTemplateLog(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新微信模版消息发送记录") + @PreAuthorize("@ss.hasPermission('mp:template-log:update')") + public CommonResult 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 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 deleteMsgTemplateLogList(@RequestParam("ids") List 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 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> getMsgTemplateLogPage(@Valid MsgTemplateLogPageReqVO pageReqVO) { + PageResult 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 list = msgTemplateLogService.getMsgTemplateLogPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "微信模版消息发送记录.xls", "数据", MsgTemplateLogRespVO.class, + BeanUtils.toBean(list, MsgTemplateLogRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateBatchReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateBatchReqVO.java new file mode 100644 index 000000000..3abc18d18 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateBatchReqVO.java @@ -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; +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogPageReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogPageReqVO.java new file mode 100644 index 000000000..fec6c48af --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogPageReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogRespVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogRespVO.java new file mode 100644 index 000000000..e0997c299 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogRespVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogSaveReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogSaveReqVO.java new file mode 100644 index 000000000..6cea435f7 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateLogSaveReqVO.java @@ -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; + } + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplatePageReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplatePageReqVO.java new file mode 100644 index 000000000..7ccd7e8cb --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplatePageReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateRespVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateRespVO.java new file mode 100644 index 000000000..694be83a9 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateRespVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateSaveReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateSaveReqVO.java new file mode 100644 index 000000000..519f3cd92 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/template/vo/MsgTemplateSaveReqVO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java index cf602e659..037f05d57 100644 --- a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/user/vo/MpUserPageReqVO.java @@ -7,6 +7,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import java.util.List; + @Schema(description = "管理后台 - 公众号粉丝分页 Request VO") @Data @EqualsAndHashCode(callSuper = true) @@ -26,4 +28,6 @@ public class MpUserPageReqVO extends PageParam { @Schema(description = "公众号粉丝昵称,模糊匹配", example = "芋艿") private String nickname; + @Schema(description = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M") + private List openidList; } diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java index 13ed0b34a..483c14c9b 100644 --- a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/convert/user/MpUserConvert.java @@ -4,6 +4,8 @@ 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; @@ -23,6 +25,8 @@ public interface MpUserConvert { MpUserRespVO convert(MpUserDO bean); + MpUserPageReqVO convert(MsgTemplateBatchReqVO bean); + List convertList(List list); PageResult convertPage(PageResult page); diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateDO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateDO.java new file mode 100644 index 000000000..0c527ba14 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateDO.java @@ -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; + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateLogDO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateLogDO.java new file mode 100644 index 000000000..fdf0d9248 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/template/MsgTemplateLogDO.java @@ -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; + + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateLogMapper.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateLogMapper.java new file mode 100644 index 000000000..c254acf3e --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateLogMapper.java @@ -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 { + + default PageResult selectPage(MsgTemplateLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .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)); + } + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateMapper.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateMapper.java new file mode 100644 index 000000000..04a486911 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/template/MsgTemplateMapper.java @@ -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 { + + default PageResult selectPage(MsgTemplatePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .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)); + } + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogService.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogService.java new file mode 100644 index 000000000..b7ecf98d7 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogService.java @@ -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 ids); + + /** + * 获得微信模版消息发送记录 + * + * @param id 编号 + * @return 微信模版消息发送记录 + */ + MsgTemplateLogDO getMsgTemplateLog(Long id); + + /** + * 获得微信模版消息发送记录分页 + * + * @param pageReqVO 分页查询 + * @return 微信模版消息发送记录分页 + */ + PageResult getMsgTemplateLogPage(MsgTemplateLogPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogServiceImpl.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogServiceImpl.java new file mode 100644 index 000000000..8b7f70916 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateLogServiceImpl.java @@ -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 ids) { + // 校验存在 + validateMsgTemplateLogExists(ids); + // 删除 + msgTemplateLogMapper.deleteByIds(ids); + } + + private void validateMsgTemplateLogExists(List ids) { + List 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 getMsgTemplateLogPage(MsgTemplateLogPageReqVO pageReqVO) { + return msgTemplateLogMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateService.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateService.java new file mode 100644 index 000000000..65e3ea26b --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateService.java @@ -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 ids); + + /** + * 获得消息模板 + * + * @param id 编号 + * @return 消息模板 + */ + MsgTemplateDO getMsgTemplate(Long id); + + /** + * 获得消息模板分页 + * + * @param pageReqVO 分页查询 + * @return 消息模板分页 + */ + PageResult 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); +} \ No newline at end of file diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateServiceImpl.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateServiceImpl.java new file mode 100644 index 000000000..528110335 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/template/MsgTemplateServiceImpl.java @@ -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 ids) { + // 校验存在 + validateMsgTemplateExists(ids); + // 删除 + msgTemplateMapper.deleteByIds(ids); + } + + private void validateMsgTemplateExists(List ids) { + List 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 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 wxTemplates = mpServiceFactory.getRequiredMpService(appId).getTemplateMsgService().getAllPrivateTemplate(); + if (CollUtil.isNotEmpty(wxTemplates)) { + List dbTemplates = msgTemplateMapper.selectList(new QueryWrapper() + .lambda().eq(MsgTemplateDO::getAppId, appId)); + // 将微信模板转换为Map,便于查找 + Map wxTemplateMap = wxTemplates.stream() + .collect(Collectors.toMap(WxMpTemplate::getTemplateId, Function.identity())); + + // 将数据库模板转换为Map,便于查找 + Map 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 wxTemplateMap, + Map dbTemplateMap) { + List 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 wxTemplateMap, + Map dbTemplateMap) { + List 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() + .set(MsgTemplateDO::getIsRemoved, 1) + .in(MsgTemplateDO::getId, removedTemplates.stream().map(MsgTemplateDO::getId).collect(Collectors.toList()))); + log.info("批量标记公众号模板为已删除: count={}", removedTemplates.size()); + } + } + + /** + * 处理已存在的模板(在两边都存在)- 清空已设置的信息 + */ + private void handleUpdatedTemplates(Map wxTemplateMap, + Map dbTemplateMap) { + List 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() + .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() + .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 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("批量发送模板消息任务完成"); + } +} \ No newline at end of file