diff --git a/README.md b/README.md index 683a2c450..b0d141db6 100644 --- a/README.md +++ b/README.md @@ -235,18 +235,19 @@ ### 微信公众号 -| | 功能 | 描述 | -|-----|--------|-------------------------------| -| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | -| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | -| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | -| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | -| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | -| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | -| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | -| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | -| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | -| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | +| | 功能 | 描述 | +|----|--------|-------------------------------| +| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | +| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | +| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | +| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | +| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 | +| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | +| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | +| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | +| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | +| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | +| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | ### 商城系统 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..aebe57dd6 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,10 @@ public interface ErrorCodeConstants { ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复"); + // ========== 公众号模版消息 1-006-010-000 ============ + ErrorCode MESSAGE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_006_010_000, "模版消息不存在"); + ErrorCode MESSAGE_TEMPLATE_DELETE_FAIL = new ErrorCode(1_006_010_002, "删除模版消息失败,原因:{}"); + ErrorCode MESSAGE_TEMPLATE_SYNC_FAIL = new ErrorCode(1_006_010_003, "同步模版消息失败,原因:{}"); + ErrorCode MESSAGE_TEMPLATE_SEND_FAIL = new ErrorCode(1_006_010_004, "发送模版消息失败,原因:{}"); + } diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.http b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.http new file mode 100644 index 000000000..e42b52fe2 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.http @@ -0,0 +1,44 @@ +### 请求 /mp/message-template/get 接口 => 成功 +GET {{baseUrl}}/mp/message-template/get?id=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/message-template/list 接口 => 成功 +GET {{baseUrl}}/mp/message-template/list?accountId=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/message-template/delete 接口 => 成功 +DELETE {{baseUrl}}/mp/message-template/delete?id=1 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/message-template/sync 接口 => 成功 +POST {{baseUrl}}/mp/message-template/sync?accountId=5 +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +### 请求 /mp/message-template/send 接口 => 成功 +POST {{baseUrl}}/mp/message-template/send +Content-Type: application/json +Authorization: Bearer {{token}} +tenant-id: {{adminTenantId}} + +{ + "id": 66, + "userId": 65, + "url": "https://example.com", + "data": { + "result": "领奖成功", + "withdrawMoney": "1000.00元", + "withdrawTime": "2024-01-01 10:00:00", + "cardInfo": "工商银行(尾号1234)", + "arrivedTime": "2024-01-01 10:30:00" + } +} + +// "miniprogram": "{\"appid\":\"wx1234567890\",\"pagepath\":\"pages/index/index\"}", diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.java new file mode 100644 index 000000000..81cb1104e --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.java @@ -0,0 +1,76 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateRespVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; +import cn.iocoder.yudao.module.mp.service.message.MpMessageTemplateService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 公众号模版消息") +@RestController +@RequestMapping("/mp/message-template") +@Validated +public class MpMessageTemplateController { + + @Resource + private MpMessageTemplateService messageTemplateService; + + @DeleteMapping("/delete") + @Operation(summary = "删除模版消息") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('mp:message-template:delete')") + public CommonResult deleteMessageTemplate(@RequestParam("id") Long id) { + messageTemplateService.deleteMessageTemplate(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得模版消息") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('mp:message-template:query')") + public CommonResult getMessageTemplate(@RequestParam("id") Long id) { + MpMessageTemplateDO msgTemplate = messageTemplateService.getMessageTemplate(id); + return success(BeanUtils.toBean(msgTemplate, MpMessageTemplateRespVO.class)); + } + + @GetMapping("/list") + @Operation(summary = "获得模版消息列表") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('mp:message-template:query')") + public CommonResult> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) { + List list = messageTemplateService.getMessageTemplateList(listReqVO); + return success(BeanUtils.toBean(list, MpMessageTemplateRespVO.class)); + } + + @PostMapping("/sync") + @Operation(summary = "同步公众号模板") + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048") + @PreAuthorize("@ss.hasPermission('mp:message-template:sync')") + public CommonResult syncMessageTemplate(@RequestParam("accountId") Long accountId) { + messageTemplateService.syncMessageTemplate(accountId); + return success(true); + } + + @PostMapping("/send") + @Operation(summary = "给粉丝发送模版消息") + @PreAuthorize("@ss.hasPermission('mp:message-template:send')") + public CommonResult sendMessageTemplate(@Valid @RequestBody MpMessageTemplateSendReqVO sendReqVO) { + messageTemplateService.sendMessageTempalte(sendReqVO); + 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/message/vo/template/MpMessageTemplateListReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateListReqVO.java new file mode 100644 index 000000000..f118b0e91 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateListReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 公众号模版消息列表 Request VO") +@Data +public class MpMessageTemplateListReqVO { + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + @NotNull(message = "公众号账号的编号不能为空") + private Long accountId; + +} diff --git a/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateRespVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateRespVO.java new file mode 100644 index 000000000..c0abbebc8 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 公众号模版消息 Response VO") +@Data +public class MpMessageTemplateRespVO { + + @Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019") + private Long id; + + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long accountId; + + @Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890abcdef") + private String appId; + + @Schema(description = "公众号模板ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "IjkGxO9M_mC9pE5Yl7QYJk1h0Dj2N4lC3oOp6rRsT8u") + private String templateId; + + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单状态提醒") + private String title; + + @Schema(description = "模板内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "模板示例") + private String example; + + @Schema(description = "模板所属行业的一级行业", example = "电商") + private String primaryIndustry; + + @Schema(description = "模板所属行业的二级行业", example = "商品售后") + private String deputyIndustry; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ 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/message/vo/template/MpMessageTemplateSendReqVO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateSendReqVO.java new file mode 100644 index 000000000..5a82124e0 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateSendReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "管理后台 - 公众号消息模版发送 Request VO") // 关联 https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html 文档 +@Data +public class MpMessageTemplateSendReqVO { + + @Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019") + @NotNull(message = "模版主键不能为空") + private Long id; + + @Schema(description = "公众号粉丝的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "公众号粉丝的编号不能为空") + private Long userId; + + @Schema(description = "模板跳转链接") + private String url; + + @Schema(description = "跳转小程序时填写") + private String miniprogram; + + @Schema(description = "模板内容") + private Map data; + +} \ 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/message/MpMessageTemplateDO.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageTemplateDO.java new file mode 100644 index 000000000..0675fa3b6 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageTemplateDO.java @@ -0,0 +1,69 @@ +package cn.iocoder.yudao.module.mp.dal.dataobject.message; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 公众号模版消息 DO + * + * @author dengsl + */ +@TableName("mp_message_template") +@KeySequence("mp_message_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MpMessageTemplateDO extends BaseDO { + + /** + * 主键 + */ + @TableId + private Long id; + /** + * 公众号账号的编号 + * + * 关联 {@link MpAccountDO#getId()} + */ + private Long accountId; + /** + * 公众号 appId + * + * 冗余 {@link MpAccountDO#getAppId()} + */ + private String appId; + /** + * 公众号模板 ID + */ + private String templateId; + + /** + * 标题 + */ + private String title; + /** + * 模板内容 + */ + private String content; + /** + * 模板示例 + */ + private String example; + + /** + * 模板所属行业的一级行业 + */ + private String primaryIndustry; + /** + * 模板所属行业的二级行业 + */ + private String deputyIndustry; + +} \ 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/message/MpMessageTemplateMapper.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageTemplateMapper.java new file mode 100644 index 000000000..8c795905d --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageTemplateMapper.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.module.mp.dal.mysql.message; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MpMessageTemplateMapper extends BaseMapperX { + + default List selectList(MpMessageTemplateListReqVO listReqVO) { + return selectList(MpMessageTemplateDO::getAccountId, listReqVO.getAccountId()); + } + +} \ 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/message/MpMessageTemplateService.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateService.java new file mode 100644 index 000000000..f4ae143c6 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateService.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; + +import java.util.List; + +/** + * 公众号模版消息 Service 接口 + * + * @author dengsl + */ +public interface MpMessageTemplateService { + + /** + * 删除模版消息 + * + * @param id 编号 + */ + void deleteMessageTemplate(Long id); + + /** + * 获得模版消息 + * + * @param id 编号 + * @return 模版消息 + */ + MpMessageTemplateDO getMessageTemplate(Long id); + + /** + * 获得模版消息列表 + * + * @param listReqVO 查询条件 + * @return 模版消息列表 + */ + List getMessageTemplateList(MpMessageTemplateListReqVO listReqVO); + + /** + * 同步公众号已添加的模版消息 + * + * @param accountId 公众号账号的编号 + */ + void syncMessageTemplate(Long accountId); + + /** + * 使用公众号,给粉丝发送【模版】消息 + * + * @param sendReqVO 消息内容 + */ + void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO); + +} \ 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/message/MpMessageTemplateServiceImpl.java b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateServiceImpl.java new file mode 100644 index 000000000..d45a95cf9 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-server/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateServiceImpl.java @@ -0,0 +1,176 @@ +package cn.iocoder.yudao.module.mp.service.message; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageTemplateMapper; +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; +import cn.iocoder.yudao.module.mp.service.user.MpUserService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.template.WxMpTemplate; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; + +/** + * 公众号模版消息 Service 实现类 + * + * @author dengsl + */ +@Service +@Validated +@Slf4j +public class MpMessageTemplateServiceImpl implements MpMessageTemplateService { + + @Resource + @Lazy // 延迟加载,为了解决延迟加载 + private MpServiceFactory mpServiceFactory; + + @Resource + private MpMessageTemplateMapper messageTemplateMapper; + + @Resource + private MpAccountService mpAccountService; + + @Resource + private MpUserService mpUserService; + + @Override + public void deleteMessageTemplate(Long id) { + // 校验存在 + MpMessageTemplateDO template = validateMsgTemplateExists(id); + + // 第一步,删除模板到公众号平台 + try { + mpServiceFactory.getRequiredMpService(template.getAppId()) + .getTemplateMsgService().delPrivateTemplate(template.getTemplateId()); + } catch (WxErrorException e) { + throw exception(MESSAGE_TEMPLATE_DELETE_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,删除模板到数据库 + messageTemplateMapper.deleteById(id); + } + + private MpMessageTemplateDO validateMsgTemplateExists(Long id) { + MpMessageTemplateDO template = messageTemplateMapper.selectById(id); + if (template == null) { + throw exception(MESSAGE_TEMPLATE_NOT_EXISTS); + } + return template; + } + + @Override + public MpMessageTemplateDO getMessageTemplate(Long id) { + return messageTemplateMapper.selectById(id); + } + + @Override + public List getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) { + return messageTemplateMapper.selectList(listReqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncMessageTemplate(Long accountId) { + MpAccountDO account = mpAccountService.getRequiredAccount(accountId); + + // 第一步,从公众号平台获取最新的模板列表 + List wxTemplates; + try { + wxTemplates = mpServiceFactory.getRequiredMpService(accountId) + .getTemplateMsgService().getAllPrivateTemplate(); + } catch (WxErrorException e) { + throw exception(MESSAGE_TEMPLATE_SYNC_FAIL, e.getError().getErrorMsg()); + } + + // 第二步,合并更新回自己的数据库 + Map templateMap = convertMap( + messageTemplateMapper.selectList(new LambdaQueryWrapperX() + .eq(MpMessageTemplateDO::getAppId, account.getAppId())), + MpMessageTemplateDO::getTemplateId); + wxTemplates.forEach(wxTemplate -> { + MpMessageTemplateDO template = templateMap.remove(wxTemplate.getTemplateId()); + // 情况一,不存在,新增 + if (template == null) { + template = new MpMessageTemplateDO().setAccountId(account.getId()).setAppId(account.getAppId()) + .setTemplateId(wxTemplate.getTemplateId()).setTitle(wxTemplate.getTitle()) + .setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample()) + .setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry()); + messageTemplateMapper.insert(template); + return; + } + // 情况二,存在,则更新 + messageTemplateMapper.updateById(new MpMessageTemplateDO().setId(template.getId()) + .setTitle(wxTemplate.getTitle()).setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample()) + .setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry())); + }); + // 情况三,部分模板已经不存在了,删除 + if (CollUtil.isNotEmpty(templateMap)) { + messageTemplateMapper.deleteByIds(convertList(templateMap.values(), MpMessageTemplateDO::getId)); + } + } + + @Override + public void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO) { + // 获得关联信息 + MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId()); + MpMessageTemplateDO template = validateMsgTemplateExists(sendReqVO.getId()); + + // 发送模版消息 + WxMpTemplateMessage templateMessage = buildTemplateMessage(template, user, sendReqVO); + try { + mpServiceFactory.getRequiredMpService(template.getAppId()) + .getTemplateMsgService().sendTemplateMsg(templateMessage); + } catch (WxErrorException e) { + throw exception(MESSAGE_TEMPLATE_SEND_FAIL, e.getError().getErrorMsg()); + } + + // 不用记录 MpMessageDO 记录,因为,微信会主动推送,可见文档 https://developers.weixin.qq.com/doc/service/guide/product/template_message/Template_Message_Interface.html + } + + private WxMpTemplateMessage buildTemplateMessage(MpMessageTemplateDO msgTemplateDO, MpUserDO user, + MpMessageTemplateSendReqVO sendReqVO) { + List data = new ArrayList<>(); + WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder() + .templateId(msgTemplateDO.getTemplateId()) + .data(data) + .toUser(user.getOpenid()); + // 设置跳转链接 + if (StrUtil.isNotBlank(sendReqVO.getUrl())) { + builder.url(sendReqVO.getUrl()); + } + // 设置小程序跳转 + if (StrUtil.isNotBlank(sendReqVO.getMiniprogram())) { + // https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html#Body__miniprogram + builder.miniProgram(JsonUtils.parseObject(sendReqVO.getMiniprogram(), WxMpTemplateMessage.MiniProgram.class)); + } + // 设置模板数据 + if (sendReqVO.getData() != null) { + sendReqVO.getData().forEach((key, value) -> data.add(new WxMpTemplateData(key, value))); + } + return builder.build(); + } + +} \ No newline at end of file