Commit 1f98c1f5a6f1fbe70653503d360cb4f0c2f9fb84
1 parent
69c5591b
!222 feat:【mp】完善“模版消息”的功能
Showing
11 changed files
with
541 additions
and
12 deletions
README.md
| @@ -232,18 +232,19 @@ | @@ -232,18 +232,19 @@ | ||
| 232 | 232 | ||
| 233 | ### 微信公众号 | 233 | ### 微信公众号 |
| 234 | 234 | ||
| 235 | -| | 功能 | 描述 | | ||
| 236 | -|-----|--------|-------------------------------| | ||
| 237 | -| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | | ||
| 238 | -| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | | ||
| 239 | -| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | | ||
| 240 | -| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | | ||
| 241 | -| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | | ||
| 242 | -| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | | ||
| 243 | -| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | | ||
| 244 | -| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | | ||
| 245 | -| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | | ||
| 246 | -| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | | 235 | +| | 功能 | 描述 | |
| 236 | +|----|--------|-------------------------------| | ||
| 237 | +| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | | ||
| 238 | +| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | | ||
| 239 | +| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | | ||
| 240 | +| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | | ||
| 241 | +| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 | | ||
| 242 | +| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | | ||
| 243 | +| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | | ||
| 244 | +| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | | ||
| 245 | +| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | | ||
| 246 | +| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | | ||
| 247 | +| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | | ||
| 247 | 248 | ||
| 248 | ### 商城系统 | 249 | ### 商城系统 |
| 249 | 250 |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.http
0 → 100644
| 1 | +### 请求 /mp/message-template/get 接口 => 成功 | ||
| 2 | +GET {{baseUrl}}/mp/message-template/get?id=1 | ||
| 3 | +Content-Type: application/json | ||
| 4 | +Authorization: Bearer {{token}} | ||
| 5 | +tenant-id: {{adminTenantId}} | ||
| 6 | + | ||
| 7 | +### 请求 /mp/message-template/list 接口 => 成功 | ||
| 8 | +GET {{baseUrl}}/mp/message-template/list?accountId=1 | ||
| 9 | +Content-Type: application/json | ||
| 10 | +Authorization: Bearer {{token}} | ||
| 11 | +tenant-id: {{adminTenantId}} | ||
| 12 | + | ||
| 13 | +### 请求 /mp/message-template/delete 接口 => 成功 | ||
| 14 | +DELETE {{baseUrl}}/mp/message-template/delete?id=1 | ||
| 15 | +Content-Type: application/json | ||
| 16 | +Authorization: Bearer {{token}} | ||
| 17 | +tenant-id: {{adminTenantId}} | ||
| 18 | + | ||
| 19 | +### 请求 /mp/message-template/sync 接口 => 成功 | ||
| 20 | +POST {{baseUrl}}/mp/message-template/sync?accountId=5 | ||
| 21 | +Content-Type: application/json | ||
| 22 | +Authorization: Bearer {{token}} | ||
| 23 | +tenant-id: {{adminTenantId}} | ||
| 24 | + | ||
| 25 | +### 请求 /mp/message-template/send 接口 => 成功 | ||
| 26 | +POST {{baseUrl}}/mp/message-template/send | ||
| 27 | +Content-Type: application/json | ||
| 28 | +Authorization: Bearer {{token}} | ||
| 29 | +tenant-id: {{adminTenantId}} | ||
| 30 | + | ||
| 31 | +{ | ||
| 32 | + "id": 66, | ||
| 33 | + "userId": 65, | ||
| 34 | + "url": "https://example.com", | ||
| 35 | + "data": { | ||
| 36 | + "result": "领奖成功", | ||
| 37 | + "withdrawMoney": "1000.00元", | ||
| 38 | + "withdrawTime": "2024-01-01 10:00:00", | ||
| 39 | + "cardInfo": "工商银行(尾号1234)", | ||
| 40 | + "arrivedTime": "2024-01-01 10:30:00" | ||
| 41 | + } | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +// "miniprogram": "{\"appid\":\"wx1234567890\",\"pagepath\":\"pages/index/index\"}", |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/MpMessageTemplateController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.message; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||
| 5 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; | ||
| 6 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateRespVO; | ||
| 7 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; | ||
| 9 | +import cn.iocoder.yudao.module.mp.service.message.MpMessageTemplateService; | ||
| 10 | +import io.swagger.v3.oas.annotations.Operation; | ||
| 11 | +import io.swagger.v3.oas.annotations.Parameter; | ||
| 12 | +import io.swagger.v3.oas.annotations.tags.Tag; | ||
| 13 | +import jakarta.annotation.Resource; | ||
| 14 | +import jakarta.validation.Valid; | ||
| 15 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 16 | +import org.springframework.validation.annotation.Validated; | ||
| 17 | +import org.springframework.web.bind.annotation.*; | ||
| 18 | + | ||
| 19 | +import java.util.List; | ||
| 20 | + | ||
| 21 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 22 | + | ||
| 23 | +@Tag(name = "管理后台 - 公众号模版消息") | ||
| 24 | +@RestController | ||
| 25 | +@RequestMapping("/mp/message-template") | ||
| 26 | +@Validated | ||
| 27 | +public class MpMessageTemplateController { | ||
| 28 | + | ||
| 29 | + @Resource | ||
| 30 | + private MpMessageTemplateService messageTemplateService; | ||
| 31 | + | ||
| 32 | + @DeleteMapping("/delete") | ||
| 33 | + @Operation(summary = "删除模版消息") | ||
| 34 | + @Parameter(name = "id", description = "编号", required = true) | ||
| 35 | + @PreAuthorize("@ss.hasPermission('mp:message-template:delete')") | ||
| 36 | + public CommonResult<Boolean> deleteMessageTemplate(@RequestParam("id") Long id) { | ||
| 37 | + messageTemplateService.deleteMessageTemplate(id); | ||
| 38 | + return success(true); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + @GetMapping("/get") | ||
| 42 | + @Operation(summary = "获得模版消息") | ||
| 43 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | ||
| 44 | + @PreAuthorize("@ss.hasPermission('mp:message-template:query')") | ||
| 45 | + public CommonResult<MpMessageTemplateRespVO> getMessageTemplate(@RequestParam("id") Long id) { | ||
| 46 | + MpMessageTemplateDO msgTemplate = messageTemplateService.getMessageTemplate(id); | ||
| 47 | + return success(BeanUtils.toBean(msgTemplate, MpMessageTemplateRespVO.class)); | ||
| 48 | + } | ||
| 49 | + | ||
| 50 | + @GetMapping("/list") | ||
| 51 | + @Operation(summary = "获得模版消息列表") | ||
| 52 | + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048") | ||
| 53 | + @PreAuthorize("@ss.hasPermission('mp:message-template:query')") | ||
| 54 | + public CommonResult<List<MpMessageTemplateRespVO>> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) { | ||
| 55 | + List<MpMessageTemplateDO> list = messageTemplateService.getMessageTemplateList(listReqVO); | ||
| 56 | + return success(BeanUtils.toBean(list, MpMessageTemplateRespVO.class)); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + @PostMapping("/sync") | ||
| 60 | + @Operation(summary = "同步公众号模板") | ||
| 61 | + @Parameter(name = "accountId", description = "公众号账号的编号", required = true, example = "2048") | ||
| 62 | + @PreAuthorize("@ss.hasPermission('mp:message-template:sync')") | ||
| 63 | + public CommonResult<Boolean> syncMessageTemplate(@RequestParam("accountId") Long accountId) { | ||
| 64 | + messageTemplateService.syncMessageTemplate(accountId); | ||
| 65 | + return success(true); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @PostMapping("/send") | ||
| 69 | + @Operation(summary = "给粉丝发送模版消息") | ||
| 70 | + @PreAuthorize("@ss.hasPermission('mp:message-template:send')") | ||
| 71 | + public CommonResult<Boolean> sendMessageTemplate(@Valid @RequestBody MpMessageTemplateSendReqVO sendReqVO) { | ||
| 72 | + messageTemplateService.sendMessageTempalte(sendReqVO); | ||
| 73 | + return success(true); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | +} | ||
| 0 | \ No newline at end of file | 77 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateListReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +@Schema(description = "管理后台 - 公众号模版消息列表 Request VO") | ||
| 8 | +@Data | ||
| 9 | +public class MpMessageTemplateListReqVO { | ||
| 10 | + | ||
| 11 | + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | ||
| 12 | + @NotNull(message = "公众号账号的编号不能为空") | ||
| 13 | + private Long accountId; | ||
| 14 | + | ||
| 15 | +} |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import java.time.LocalDateTime; | ||
| 7 | + | ||
| 8 | +@Schema(description = "管理后台 - 公众号模版消息 Response VO") | ||
| 9 | +@Data | ||
| 10 | +public class MpMessageTemplateRespVO { | ||
| 11 | + | ||
| 12 | + @Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019") | ||
| 13 | + private Long id; | ||
| 14 | + | ||
| 15 | + @Schema(description = "公众号账号的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 16 | + private Long accountId; | ||
| 17 | + | ||
| 18 | + @Schema(description = "appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "wx1234567890abcdef") | ||
| 19 | + private String appId; | ||
| 20 | + | ||
| 21 | + @Schema(description = "公众号模板ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "IjkGxO9M_mC9pE5Yl7QYJk1h0Dj2N4lC3oOp6rRsT8u") | ||
| 22 | + private String templateId; | ||
| 23 | + | ||
| 24 | + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单状态提醒") | ||
| 25 | + private String title; | ||
| 26 | + | ||
| 27 | + @Schema(description = "模板内容", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 28 | + private String content; | ||
| 29 | + | ||
| 30 | + @Schema(description = "模板示例") | ||
| 31 | + private String example; | ||
| 32 | + | ||
| 33 | + @Schema(description = "模板所属行业的一级行业", example = "电商") | ||
| 34 | + private String primaryIndustry; | ||
| 35 | + | ||
| 36 | + @Schema(description = "模板所属行业的二级行业", example = "商品售后") | ||
| 37 | + private String deputyIndustry; | ||
| 38 | + | ||
| 39 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||
| 40 | + private LocalDateTime createTime; | ||
| 41 | + | ||
| 42 | +} | ||
| 0 | \ No newline at end of file | 43 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/message/vo/template/MpMessageTemplateSendReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.message.vo.template; | ||
| 2 | + | ||
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +import java.util.Map; | ||
| 8 | + | ||
| 9 | +@Schema(description = "管理后台 - 公众号消息模版发送 Request VO") // 关联 https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html 文档 | ||
| 10 | +@Data | ||
| 11 | +public class MpMessageTemplateSendReqVO { | ||
| 12 | + | ||
| 13 | + @Schema(description = "模版主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "7019") | ||
| 14 | + @NotNull(message = "模版主键不能为空") | ||
| 15 | + private Long id; | ||
| 16 | + | ||
| 17 | + @Schema(description = "公众号粉丝的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | ||
| 18 | + @NotNull(message = "公众号粉丝的编号不能为空") | ||
| 19 | + private Long userId; | ||
| 20 | + | ||
| 21 | + @Schema(description = "模板跳转链接") | ||
| 22 | + private String url; | ||
| 23 | + | ||
| 24 | + @Schema(description = "跳转小程序时填写") | ||
| 25 | + private String miniprogram; | ||
| 26 | + | ||
| 27 | + @Schema(description = "模板内容") | ||
| 28 | + private Map<String, String> data; | ||
| 29 | + | ||
| 30 | +} | ||
| 0 | \ No newline at end of file | 31 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/message/MpMessageTemplateDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.dal.dataobject.message; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 4 | +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.KeySequence; | ||
| 6 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 8 | +import lombok.*; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 公众号模版消息 DO | ||
| 12 | + * | ||
| 13 | + * @author dengsl | ||
| 14 | + */ | ||
| 15 | +@TableName("mp_message_template") | ||
| 16 | +@KeySequence("mp_message_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 | ||
| 17 | +@Data | ||
| 18 | +@EqualsAndHashCode(callSuper = true) | ||
| 19 | +@ToString(callSuper = true) | ||
| 20 | +@Builder | ||
| 21 | +@NoArgsConstructor | ||
| 22 | +@AllArgsConstructor | ||
| 23 | +public class MpMessageTemplateDO extends BaseDO { | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 主键 | ||
| 27 | + */ | ||
| 28 | + @TableId | ||
| 29 | + private Long id; | ||
| 30 | + /** | ||
| 31 | + * 公众号账号的编号 | ||
| 32 | + * | ||
| 33 | + * 关联 {@link MpAccountDO#getId()} | ||
| 34 | + */ | ||
| 35 | + private Long accountId; | ||
| 36 | + /** | ||
| 37 | + * 公众号 appId | ||
| 38 | + * | ||
| 39 | + * 冗余 {@link MpAccountDO#getAppId()} | ||
| 40 | + */ | ||
| 41 | + private String appId; | ||
| 42 | + /** | ||
| 43 | + * 公众号模板 ID | ||
| 44 | + */ | ||
| 45 | + private String templateId; | ||
| 46 | + | ||
| 47 | + /** | ||
| 48 | + * 标题 | ||
| 49 | + */ | ||
| 50 | + private String title; | ||
| 51 | + /** | ||
| 52 | + * 模板内容 | ||
| 53 | + */ | ||
| 54 | + private String content; | ||
| 55 | + /** | ||
| 56 | + * 模板示例 | ||
| 57 | + */ | ||
| 58 | + private String example; | ||
| 59 | + | ||
| 60 | + /** | ||
| 61 | + * 模板所属行业的一级行业 | ||
| 62 | + */ | ||
| 63 | + private String primaryIndustry; | ||
| 64 | + /** | ||
| 65 | + * 模板所属行业的二级行业 | ||
| 66 | + */ | ||
| 67 | + private String deputyIndustry; | ||
| 68 | + | ||
| 69 | +} | ||
| 0 | \ No newline at end of file | 70 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/message/MpMessageTemplateMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.dal.mysql.message; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 4 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; | ||
| 6 | +import org.apache.ibatis.annotations.Mapper; | ||
| 7 | + | ||
| 8 | +import java.util.List; | ||
| 9 | + | ||
| 10 | +@Mapper | ||
| 11 | +public interface MpMessageTemplateMapper extends BaseMapperX<MpMessageTemplateDO> { | ||
| 12 | + | ||
| 13 | + default List<MpMessageTemplateDO> selectList(MpMessageTemplateListReqVO listReqVO) { | ||
| 14 | + return selectList(MpMessageTemplateDO::getAccountId, listReqVO.getAccountId()); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | +} | ||
| 0 | \ No newline at end of file | 18 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java
| @@ -61,4 +61,10 @@ public interface ErrorCodeConstants { | @@ -61,4 +61,10 @@ public interface ErrorCodeConstants { | ||
| 61 | ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); | 61 | ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, "操作失败,原因:已存在该消息类型的回复"); |
| 62 | ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复"); | 62 | ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, "操作失败,原因:已关在该关键字的回复"); |
| 63 | 63 | ||
| 64 | + // ========== 公众号模版消息 1-006-010-000 ============ | ||
| 65 | + ErrorCode MESSAGE_TEMPLATE_NOT_EXISTS = new ErrorCode(1_006_010_000, "模版消息不存在"); | ||
| 66 | + ErrorCode MESSAGE_TEMPLATE_DELETE_FAIL = new ErrorCode(1_006_010_002, "删除模版消息失败,原因:{}"); | ||
| 67 | + ErrorCode MESSAGE_TEMPLATE_SYNC_FAIL = new ErrorCode(1_006_010_003, "同步模版消息失败,原因:{}"); | ||
| 68 | + ErrorCode MESSAGE_TEMPLATE_SEND_FAIL = new ErrorCode(1_006_010_004, "发送模版消息失败,原因:{}"); | ||
| 69 | + | ||
| 64 | } | 70 | } |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.service.message; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; | ||
| 4 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 公众号模版消息 Service 接口 | ||
| 11 | + * | ||
| 12 | + * @author dengsl | ||
| 13 | + */ | ||
| 14 | +public interface MpMessageTemplateService { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 删除模版消息 | ||
| 18 | + * | ||
| 19 | + * @param id 编号 | ||
| 20 | + */ | ||
| 21 | + void deleteMessageTemplate(Long id); | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 获得模版消息 | ||
| 25 | + * | ||
| 26 | + * @param id 编号 | ||
| 27 | + * @return 模版消息 | ||
| 28 | + */ | ||
| 29 | + MpMessageTemplateDO getMessageTemplate(Long id); | ||
| 30 | + | ||
| 31 | + /** | ||
| 32 | + * 获得模版消息列表 | ||
| 33 | + * | ||
| 34 | + * @param listReqVO 查询条件 | ||
| 35 | + * @return 模版消息列表 | ||
| 36 | + */ | ||
| 37 | + List<MpMessageTemplateDO> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO); | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 同步公众号已添加的模版消息 | ||
| 41 | + * | ||
| 42 | + * @param accountId 公众号账号的编号 | ||
| 43 | + */ | ||
| 44 | + void syncMessageTemplate(Long accountId); | ||
| 45 | + | ||
| 46 | + /** | ||
| 47 | + * 使用公众号,给粉丝发送【模版】消息 | ||
| 48 | + * | ||
| 49 | + * @param sendReqVO 消息内容 | ||
| 50 | + */ | ||
| 51 | + void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO); | ||
| 52 | + | ||
| 53 | +} | ||
| 0 | \ No newline at end of file | 54 | \ No newline at end of file |
yudao-module-mp/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageTemplateServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.service.message; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | ||
| 6 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 7 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateListReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.mp.controller.admin.message.vo.template.MpMessageTemplateSendReqVO; | ||
| 9 | +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO; | ||
| 10 | +import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageTemplateDO; | ||
| 11 | +import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO; | ||
| 12 | +import cn.iocoder.yudao.module.mp.dal.mysql.message.MpMessageTemplateMapper; | ||
| 13 | +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | ||
| 14 | +import cn.iocoder.yudao.module.mp.service.account.MpAccountService; | ||
| 15 | +import cn.iocoder.yudao.module.mp.service.user.MpUserService; | ||
| 16 | +import jakarta.annotation.Resource; | ||
| 17 | +import lombok.extern.slf4j.Slf4j; | ||
| 18 | +import me.chanjar.weixin.common.error.WxErrorException; | ||
| 19 | +import me.chanjar.weixin.mp.bean.template.WxMpTemplate; | ||
| 20 | +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; | ||
| 21 | +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; | ||
| 22 | +import org.springframework.context.annotation.Lazy; | ||
| 23 | +import org.springframework.stereotype.Service; | ||
| 24 | +import org.springframework.transaction.annotation.Transactional; | ||
| 25 | +import org.springframework.validation.annotation.Validated; | ||
| 26 | + | ||
| 27 | +import java.util.ArrayList; | ||
| 28 | +import java.util.List; | ||
| 29 | +import java.util.Map; | ||
| 30 | + | ||
| 31 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 32 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||
| 33 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||
| 34 | +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*; | ||
| 35 | + | ||
| 36 | +/** | ||
| 37 | + * 公众号模版消息 Service 实现类 | ||
| 38 | + * | ||
| 39 | + * @author dengsl | ||
| 40 | + */ | ||
| 41 | +@Service | ||
| 42 | +@Validated | ||
| 43 | +@Slf4j | ||
| 44 | +public class MpMessageTemplateServiceImpl implements MpMessageTemplateService { | ||
| 45 | + | ||
| 46 | + @Resource | ||
| 47 | + @Lazy // 延迟加载,为了解决延迟加载 | ||
| 48 | + private MpServiceFactory mpServiceFactory; | ||
| 49 | + | ||
| 50 | + @Resource | ||
| 51 | + private MpMessageTemplateMapper messageTemplateMapper; | ||
| 52 | + | ||
| 53 | + @Resource | ||
| 54 | + private MpAccountService mpAccountService; | ||
| 55 | + | ||
| 56 | + @Resource | ||
| 57 | + private MpUserService mpUserService; | ||
| 58 | + | ||
| 59 | + @Override | ||
| 60 | + public void deleteMessageTemplate(Long id) { | ||
| 61 | + // 校验存在 | ||
| 62 | + MpMessageTemplateDO template = validateMsgTemplateExists(id); | ||
| 63 | + | ||
| 64 | + // 第一步,删除模板到公众号平台 | ||
| 65 | + try { | ||
| 66 | + mpServiceFactory.getRequiredMpService(template.getAppId()) | ||
| 67 | + .getTemplateMsgService().delPrivateTemplate(template.getTemplateId()); | ||
| 68 | + } catch (WxErrorException e) { | ||
| 69 | + throw exception(MESSAGE_TEMPLATE_DELETE_FAIL, e.getError().getErrorMsg()); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + // 第二步,删除模板到数据库 | ||
| 73 | + messageTemplateMapper.deleteById(id); | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + private MpMessageTemplateDO validateMsgTemplateExists(Long id) { | ||
| 77 | + MpMessageTemplateDO template = messageTemplateMapper.selectById(id); | ||
| 78 | + if (template == null) { | ||
| 79 | + throw exception(MESSAGE_TEMPLATE_NOT_EXISTS); | ||
| 80 | + } | ||
| 81 | + return template; | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + @Override | ||
| 85 | + public MpMessageTemplateDO getMessageTemplate(Long id) { | ||
| 86 | + return messageTemplateMapper.selectById(id); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + @Override | ||
| 90 | + public List<MpMessageTemplateDO> getMessageTemplateList(MpMessageTemplateListReqVO listReqVO) { | ||
| 91 | + return messageTemplateMapper.selectList(listReqVO); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + @Override | ||
| 95 | + @Transactional(rollbackFor = Exception.class) | ||
| 96 | + public void syncMessageTemplate(Long accountId) { | ||
| 97 | + MpAccountDO account = mpAccountService.getRequiredAccount(accountId); | ||
| 98 | + | ||
| 99 | + // 第一步,从公众号平台获取最新的模板列表 | ||
| 100 | + List<WxMpTemplate> wxTemplates; | ||
| 101 | + try { | ||
| 102 | + wxTemplates = mpServiceFactory.getRequiredMpService(accountId) | ||
| 103 | + .getTemplateMsgService().getAllPrivateTemplate(); | ||
| 104 | + } catch (WxErrorException e) { | ||
| 105 | + throw exception(MESSAGE_TEMPLATE_SYNC_FAIL, e.getError().getErrorMsg()); | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + // 第二步,合并更新回自己的数据库 | ||
| 109 | + Map<String, MpMessageTemplateDO> templateMap = convertMap( | ||
| 110 | + messageTemplateMapper.selectList(new LambdaQueryWrapperX<MpMessageTemplateDO>() | ||
| 111 | + .eq(MpMessageTemplateDO::getAppId, account.getAppId())), | ||
| 112 | + MpMessageTemplateDO::getTemplateId); | ||
| 113 | + wxTemplates.forEach(wxTemplate -> { | ||
| 114 | + MpMessageTemplateDO template = templateMap.remove(wxTemplate.getTemplateId()); | ||
| 115 | + // 情况一,不存在,新增 | ||
| 116 | + if (template == null) { | ||
| 117 | + template = new MpMessageTemplateDO().setAccountId(account.getId()).setAppId(account.getAppId()) | ||
| 118 | + .setTemplateId(wxTemplate.getTemplateId()).setTitle(wxTemplate.getTitle()) | ||
| 119 | + .setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample()) | ||
| 120 | + .setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry()); | ||
| 121 | + messageTemplateMapper.insert(template); | ||
| 122 | + return; | ||
| 123 | + } | ||
| 124 | + // 情况二,存在,则更新 | ||
| 125 | + messageTemplateMapper.updateById(new MpMessageTemplateDO().setId(template.getId()) | ||
| 126 | + .setTitle(wxTemplate.getTitle()).setContent(wxTemplate.getContent()).setExample(wxTemplate.getExample()) | ||
| 127 | + .setPrimaryIndustry(wxTemplate.getPrimaryIndustry()).setDeputyIndustry(wxTemplate.getDeputyIndustry())); | ||
| 128 | + }); | ||
| 129 | + // 情况三,部分模板已经不存在了,删除 | ||
| 130 | + if (CollUtil.isNotEmpty(templateMap)) { | ||
| 131 | + messageTemplateMapper.deleteByIds(convertList(templateMap.values(), MpMessageTemplateDO::getId)); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + @Override | ||
| 136 | + public void sendMessageTempalte(MpMessageTemplateSendReqVO sendReqVO) { | ||
| 137 | + // 获得关联信息 | ||
| 138 | + MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId()); | ||
| 139 | + MpMessageTemplateDO template = validateMsgTemplateExists(sendReqVO.getId()); | ||
| 140 | + | ||
| 141 | + // 发送模版消息 | ||
| 142 | + WxMpTemplateMessage templateMessage = buildTemplateMessage(template, user, sendReqVO); | ||
| 143 | + try { | ||
| 144 | + mpServiceFactory.getRequiredMpService(template.getAppId()) | ||
| 145 | + .getTemplateMsgService().sendTemplateMsg(templateMessage); | ||
| 146 | + } catch (WxErrorException e) { | ||
| 147 | + throw exception(MESSAGE_TEMPLATE_SEND_FAIL, e.getError().getErrorMsg()); | ||
| 148 | + } | ||
| 149 | + | ||
| 150 | + // 不用记录 MpMessageDO 记录,因为,微信会主动推送,可见文档 https://developers.weixin.qq.com/doc/service/guide/product/template_message/Template_Message_Interface.html | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + private WxMpTemplateMessage buildTemplateMessage(MpMessageTemplateDO msgTemplateDO, MpUserDO user, | ||
| 154 | + MpMessageTemplateSendReqVO sendReqVO) { | ||
| 155 | + List<WxMpTemplateData> data = new ArrayList<>(); | ||
| 156 | + WxMpTemplateMessage.WxMpTemplateMessageBuilder builder = WxMpTemplateMessage.builder() | ||
| 157 | + .templateId(msgTemplateDO.getTemplateId()) | ||
| 158 | + .data(data) | ||
| 159 | + .toUser(user.getOpenid()); | ||
| 160 | + // 设置跳转链接 | ||
| 161 | + if (StrUtil.isNotBlank(sendReqVO.getUrl())) { | ||
| 162 | + builder.url(sendReqVO.getUrl()); | ||
| 163 | + } | ||
| 164 | + // 设置小程序跳转 | ||
| 165 | + if (StrUtil.isNotBlank(sendReqVO.getMiniprogram())) { | ||
| 166 | + // https://developers.weixin.qq.com/doc/service/api/notify/template/api_sendtemplatemessage.html#Body__miniprogram | ||
| 167 | + builder.miniProgram(JsonUtils.parseObject(sendReqVO.getMiniprogram(), WxMpTemplateMessage.MiniProgram.class)); | ||
| 168 | + } | ||
| 169 | + // 设置模板数据 | ||
| 170 | + if (sendReqVO.getData() != null) { | ||
| 171 | + sendReqVO.getData().forEach((key, value) -> data.add(new WxMpTemplateData(key, value))); | ||
| 172 | + } | ||
| 173 | + return builder.build(); | ||
| 174 | + } | ||
| 175 | + | ||
| 176 | +} | ||
| 0 | \ No newline at end of file | 177 | \ No newline at end of file |