diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/Area.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/Area.java index 262bc263ce..3027085871 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/Area.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/Area.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.framework.ip.core; import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; +import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; import lombok.AllArgsConstructor; import lombok.Data; @@ -54,7 +55,7 @@ public class Area { /** * 子节点 */ - @JsonManagedReference + @JsonBackReference private List children; } diff --git a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java index fbe6acfb6f..e7bb1f3101 100644 --- a/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java @@ -106,6 +106,10 @@ public class RandomUtils { return randomString() + "@qq.com"; } + public static String randomMobile() { + return "13800138" + RandomUtil.randomNumbers(3); + } + public static String randomURL() { return "https://www.iocoder.cn/" + randomString(); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java index de12ee1e05..fd3cdf7869 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/AiImageController.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.ai.controller.admin.image; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 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.module.ai.controller.admin.image.vo.AiImageDrawReqVO; @@ -41,7 +40,7 @@ public class AiImageController { @GetMapping("/my-page") @Operation(summary = "获取【我的】绘图分页") - public CommonResult> getImagePageMy(@Validated PageParam pageReqVO) { + public CommonResult> getImagePageMy(@Validated AiImagePageReqVO pageReqVO) { PageResult pageResult = imageService.getImagePageMy(getLoginUserId(), pageReqVO); return success(BeanUtils.toBean(pageResult, AiImageRespVO.class)); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java index bdf329c618..3b48686b9c 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/image/vo/AiImagePageReqVO.java @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.ai.controller.admin.image.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; @@ -21,6 +19,9 @@ public class AiImagePageReqVO extends PageParam { @Schema(description = "平台", example = "OpenAI") private String platform; + @Schema(description = "提示词", example = "1") + private String prompt; + @Schema(description = "绘画状态", example = "1") private Integer status; diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java index fd6e4b398c..847a5c2042 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/image/AiImageMapper.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.ai.dal.mysql.image; -import cn.iocoder.yudao.framework.common.pojo.PageParam; 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; @@ -19,7 +18,7 @@ import java.util.List; public interface AiImageMapper extends BaseMapperX { default AiImageDO selectByTaskId(String taskId) { - return this.selectOne(AiImageDO::getTaskId, taskId); + return selectOne(AiImageDO::getTaskId, taskId); } default PageResult selectPage(AiImagePageReqVO reqVO) { @@ -32,9 +31,13 @@ public interface AiImageMapper extends BaseMapperX { .orderByDesc(AiImageDO::getId)); } - default PageResult selectPage(Long userId, PageParam pageReqVO) { - return selectPage(pageReqVO, new LambdaQueryWrapperX() - .eq(AiImageDO::getUserId, userId) + default PageResult selectPageMy(Long userId, AiImagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(AiImageDO::getPrompt, reqVO.getPrompt()) + // 情况一:公开 + .eq(Boolean.TRUE.equals(reqVO.getPublicStatus()), AiImageDO::getPublicStatus, reqVO.getPublicStatus()) + // 情况二:私有 + .eq(Boolean.FALSE.equals(reqVO.getPublicStatus()), AiImageDO::getUserId, userId) .orderByDesc(AiImageDO::getId)); } diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java index 716c7ea8af..b4d76b315a 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageService.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.ai.service.image; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImagePageReqVO; @@ -27,7 +26,7 @@ public interface AiImageService { * @param pageReqVO 分页条件 * @return 绘图分页 */ - PageResult getImagePageMy(Long userId, PageParam pageReqVO); + PageResult getImagePageMy(Long userId, AiImagePageReqVO pageReqVO); /** * 获得绘图记录 diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java index 3a8ff83460..020546ae96 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java @@ -10,7 +10,6 @@ import cn.hutool.extra.spring.SpringUtil; import cn.hutool.http.HttpUtil; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; -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.module.ai.controller.admin.image.vo.AiImageDrawReqVO; @@ -67,8 +66,8 @@ public class AiImageServiceImpl implements AiImageService { private AiApiKeyService apiKeyService; @Override - public PageResult getImagePageMy(Long userId, PageParam pageReqVO) { - return imageMapper.selectPage(userId, pageReqVO); + public PageResult getImagePageMy(Long userId, AiImagePageReqVO pageReqVO) { + return imageMapper.selectPageMy(userId, pageReqVO); } @Override diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java index 72be20c547..df5296df29 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/mindmap/AiMindMapServiceImpl.java @@ -124,7 +124,7 @@ public class AiMindMapServiceImpl implements AiMindMapService { if (role != null && role.getModelId() != null) { model = chatModalService.getChatModel(role.getModelId()); } - if (model != null) { + if (model == null) { model = chatModalService.getRequiredDefaultChatModel(); } Assert.notNull(model, "[AI] 获取不到模型"); diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java index 2fae31d590..b8f22a2fa6 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/write/AiWriteServiceImpl.java @@ -114,7 +114,7 @@ public class AiWriteServiceImpl implements AiWriteService { if (Objects.nonNull(writeRole) && Objects.nonNull(writeRole.getModelId())) { model = chatModalService.getChatModel(writeRole.getModelId()); } - if (Objects.isNull(model)) { + if (model == null) { model = chatModalService.getRequiredDefaultChatModel(); } Assert.notNull(model, "[AI] 获取不到模型"); diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java index e0d47f4092..0b9e6a13b8 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java @@ -1,24 +1,30 @@ package cn.iocoder.yudao.module.product.controller.admin.history; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO; import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryRespVO; import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO; +import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO; import cn.iocoder.yudao.module.product.service.history.ProductBrowseHistoryService; +import cn.iocoder.yudao.module.product.service.spu.ProductSpuService; import io.swagger.v3.oas.annotations.Operation; 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.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.annotation.Resource; -import javax.validation.Valid; +import java.util.Map; +import java.util.Optional; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @Tag(name = "管理后台 - 商品浏览记录") @RestController @@ -28,13 +34,24 @@ public class ProductBrowseHistoryController { @Resource private ProductBrowseHistoryService browseHistoryService; + @Resource + private ProductSpuService productSpuService; @GetMapping("/page") @Operation(summary = "获得商品浏览记录分页") @PreAuthorize("@ss.hasPermission('product:browse-history:query')") public CommonResult> getBrowseHistoryPage(@Valid ProductBrowseHistoryPageReqVO pageReqVO) { PageResult pageResult = browseHistoryService.getBrowseHistoryPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, ProductBrowseHistoryRespVO.class)); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty()); + } + + // 得到商品 spu 信息 + Map spuMap = productSpuService.getSpuMap( + convertSet(pageResult.getList(), ProductBrowseHistoryDO::getSpuId)); + return success(BeanUtils.toBean(pageResult, ProductBrowseHistoryRespVO.class, + vo -> Optional.ofNullable(spuMap.get(vo.getSpuId())) + .ifPresent(spu -> vo.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setPrice(spu.getPrice())))); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java index 0e2e0cbede..012f1602c6 100644 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java @@ -7,28 +7,28 @@ import lombok.Data; import java.time.LocalDateTime; +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + @Schema(description = "管理后台 - 商品浏览记录 Response VO") @Data @ExcelIgnoreUnannotated public class ProductBrowseHistoryRespVO { - @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26055") - @ExcelProperty("记录编号") + @Schema(description = "编号", requiredMode = REQUIRED, example = "1") private Long id; - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4314") - @ExcelProperty("用户编号") - private Long userId; - - @Schema(description = "用户是否删除", example = "false") - private Boolean userDeleted; - - @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "42") - @ExcelProperty("商品 SPU 编号") + @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") private Long spuId; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; + // ========== 商品相关字段 ========== + + @Schema(description = "商品 SPU 名称", example = "赵六") + private String spuName; + + @Schema(description = "商品封面图", example = "https://domain/pic.png") + private String picUrl; + + @Schema(description = "商品单价", example = "100") + private Integer price; } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java index 63b5dd6ba3..75ef9272cc 100755 --- a/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java +++ b/yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.product.service.spu; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuPageReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuSaveReqVO; import cn.iocoder.yudao.module.product.controller.admin.spu.vo.ProductSpuUpdateStatusReqVO; @@ -58,6 +59,17 @@ public interface ProductSpuService { */ List getSpuList(Collection ids); + /** + * 获得商品 SPU Map + * + * @param ids 编号数组 + * @return 商品 SPU Map + */ + default Map getSpuMap(Collection ids) { + List list = getSpuList(ids); + return CollectionUtils.convertMap(list, ProductSpuDO::getId); + } + /** * 获得指定状态的商品 SPU 列表 * diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/MessageTemplateConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/MessageTemplateConstants.java new file mode 100644 index 0000000000..9a6bdd41df --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/MessageTemplateConstants.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.promotion.enums; + +/** + * 通知模板枚举类 + * + * @author HUIHUI + */ +public interface MessageTemplateConstants { + + //======================= 小程序订阅消息模版 ======================= + + String COMBINATION_SUCCESS = "拼团结果通知"; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java index 809003a3dd..f011b72d82 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationRecordServiceImpl.java @@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; @@ -23,9 +24,12 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationP import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO; import cn.iocoder.yudao.module.promotion.dal.mysql.combination.CombinationRecordMapper; import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.trade.api.order.TradeOrderApi; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -40,6 +44,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.afterNow; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.beforeNow; import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.promotion.enums.MessageTemplateConstants.COMBINATION_SUCCESS; // TODO 芋艿:等拼团记录做完,完整 review 下 @@ -66,8 +71,10 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { private ProductSkuApi productSkuApi; @Resource - @Lazy + @Lazy // 延迟加载,避免循环依赖 private TradeOrderApi tradeOrderApi; + @Resource + public SocialClientApi socialClientApi; // TODO @芋艿:在详细预览下; @Override @@ -205,7 +212,25 @@ public class CombinationRecordServiceImpl implements CombinationRecordService { } updateRecords.add(updateRecord); }); - combinationRecordMapper.updateBatch(updateRecords); + Boolean updateSuccess = combinationRecordMapper.updateBatch(updateRecords); + + // 3. 拼团成功发送订阅消息 + if (updateSuccess && isFull) { + records.forEach(item -> { + getSelf().sendCombinationResultMessage(item); + }); + } + } + + @Async + public void sendCombinationResultMessage(CombinationRecordDO record) { + // 构建并发送模版消息 + socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() + .setUserId(record.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) + .setTemplateTitle(COMBINATION_SUCCESS) + .setPage("pages/order/detail?id=" + record.getOrderId()) // 订单详情页 + .addMessage("thing1", "商品拼团活动") // 活动标题 + .addMessage("thing2", "恭喜您拼团成功!我们将尽快为您发货。")); // 温馨提示 } @Override diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java index 5041139b46..56c4c90055 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/MessageTemplateConstants.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.trade.enums; -// TODO @芋艿:枚举 /** * 通知模板枚举类 * @@ -8,9 +7,15 @@ package cn.iocoder.yudao.module.trade.enums; */ public interface MessageTemplateConstants { - String ORDER_DELIVERY = "order_delivery"; // 短信模版编号 + // ======================= 短信消息模版 ======================= - String BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过) - String BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过) + String SMS_ORDER_DELIVERY = "order_delivery"; // 短信模版编号 + + String SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过) + String SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过) + + // ======================= 小程序订阅消息模版 ======================= + + String WXA_ORDER_DELIVERY = "订单发货通知"; } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java index d92db8a3c7..9559a2586a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java @@ -33,6 +33,7 @@ import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; @@ -56,6 +57,7 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; public class AfterSaleServiceImpl implements AfterSaleService { @Resource + @Lazy // 延迟加载,避免循环依赖 private TradeOrderUpdateService tradeOrderUpdateService; @Resource private TradeOrderQueryService tradeOrderQueryService; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java index b6b4034ce0..117428f000 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java @@ -77,14 +77,14 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { String templateCode; if (BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.equals(status)) { - templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_APPROVE; + templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE; // 3.1 通过时佣金转余额 if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) { // todo 疯狂: } // TODO 疯狂:调用转账接口 } else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) { - templateCode = MessageTemplateConstants.BROKERAGE_WITHDRAW_AUDIT_REJECT; + templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT; // 3.2 驳回时需要退还用户佣金 brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT, String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle()); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/message/TradeMessageServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/message/TradeMessageServiceImpl.java index b9cdf9476b..a64a58bac4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/message/TradeMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/message/TradeMessageServiceImpl.java @@ -37,7 +37,7 @@ public class TradeMessageServiceImpl implements TradeMessageService { notifyMessageSendApi.sendSingleMessageToMember( new NotifySendSingleToUserReqDTO() .setUserId(reqBO.getUserId()) - .setTemplateCode(MessageTemplateConstants.ORDER_DELIVERY) + .setTemplateCode(MessageTemplateConstants.SMS_ORDER_DELIVERY) .setTemplateParams(msgMap)); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 75323a719b..e549f8b9cf 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.trade.service.order; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -19,6 +21,8 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi; import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderRemarkReqVO; import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderUpdateAddressReqVO; @@ -50,6 +54,7 @@ import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO; import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO; import cn.iocoder.yudao.module.trade.service.price.calculator.TradePriceCalculatorHelper; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -67,6 +72,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.min import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getTerminal; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants.WXA_ORDER_DELIVERY; /** * 交易订单【写】Service 实现类 @@ -103,6 +109,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { private MemberAddressApi addressApi; @Resource private ProductCommentApi productCommentApi; + @Resource + public SocialClientApi socialClientApi; @Resource private TradeOrderProperties tradeOrderProperties; @@ -364,9 +372,26 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { MapUtil.builder().put("expressName", express != null ? express.getName() : "无") .put("logisticsNo", express != null ? deliveryReqVO.getLogisticsNo() : "无").build()); - // 4. 发送站内信 + // 4.1 发送站内信 tradeMessageService.sendMessageWhenDeliveryOrder(new TradeOrderMessageWhenDeliveryOrderReqBO() .setOrderId(order.getId()).setUserId(order.getUserId()).setMessage(null)); + // 4.2 发送订阅消息 + getSelf().sendDeliveryOrderMessage(order, deliveryReqVO); + } + + @Async + public void sendDeliveryOrderMessage(TradeOrderDO order, TradeOrderDeliveryReqVO deliveryReqVO) { + // 构建并发送模版消息 + Long orderId = order.getId(); + socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() + .setUserId(order.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) + .setTemplateTitle(WXA_ORDER_DELIVERY) + .setPage("pages/order/detail?id=" + orderId) // 订单详情页 + .addMessage("character_string3", String.valueOf(orderId)) // 订单编号 + .addMessage("phrase6", TradeOrderStatusEnum.DELIVERED.getName()) // 订单状态 + .addMessage("date4", LocalDateTimeUtil.formatNormal(LocalDateTime.now()))// 发货时间 + .addMessage("character_string5", StrUtil.blankToDefault(deliveryReqVO.getLogisticsNo(), "-")) // 快递单号 + .addMessage("thing9", order.getReceiverDetailAddress())); // 收货地址 } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java index 87d0ec4b59..875967e78d 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum; import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum; import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -28,11 +29,13 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.ORDER_DELIV public class TradeCombinationOrderHandler implements TradeOrderHandler { @Resource + @Lazy // 延迟加载,避免循环依赖 private TradeOrderUpdateService orderUpdateService; @Resource private TradeOrderQueryService orderQueryService; @Resource + @Lazy // 延迟加载,避免循环依赖 private CombinationRecordApi combinationRecordApi; @Override diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 15c952690a..e8de23c2b6 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -5,24 +5,19 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserRespVO; -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; -import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialWxQrcodeReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.*; import cn.iocoder.yudao.module.system.api.social.SocialClientApi; import cn.iocoder.yudao.module.system.api.social.SocialUserApi; -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; 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.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.annotation.Resource; -import javax.validation.Valid; +import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -73,4 +68,11 @@ public class AppSocialUserController { return success(Base64.encode(wxQrcode)); } + @GetMapping("/get-subscribe-template-list") + @Operation(summary = "获得微信小程订阅模板列表") + public CommonResult> getSubscribeTemplateList() { + List template = socialClientApi.getWxaSubscribeTemplateList(UserTypeEnum.MEMBER.getValue()); + return success(BeanUtils.toBean(template, AppSocialWxSubscribeTemplateRespVO.class)); + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxSubscribeTemplateRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxSubscribeTemplateRespVO.java new file mode 100644 index 0000000000..3fdc38b698 --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxSubscribeTemplateRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.member.controller.app.social.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "用户 APP - 获得小程序订阅模版 Response VO") +@Data +public class AppSocialWxSubscribeTemplateRespVO { + + @Schema(description = "模版编号", requiredMode = Schema.RequiredMode.REQUIRED, + example = "9Aw5ZV1j9xdWTFEkqCpZ7mIBbSC34khK55OtzUPl0rU") + private String id; + + @Schema(description = "模版标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "订单支付通知") + private String title; + + @Schema(description = "模版内容", requiredMode = Schema.RequiredMode.REQUIRED, + example = "{ {result.DATA} }\\n\\n领奖金额:{ {withdrawMoney.DATA} }\\n领奖时间: { {withdrawTime.DATA} }") + private String content; + + @Schema(description = "模板内容示例", requiredMode = Schema.RequiredMode.REQUIRED, example = "下单时间:2016年8月8日") + private String example; + + @Schema(description = "模版类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; // 2 为一次性订阅,3 为长期订阅 + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java new file mode 100644 index 0000000000..9497d776b2 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/MessageTemplateConstants.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.pay.enums; + +/** + * 通知模板枚举类 + * + * @author HUIHUI + */ +public interface MessageTemplateConstants { + + // ======================= 小程序订阅消息 ======================= + + String WXA_WALLET_RECHARGER_PAID = "充值成功通知"; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java index 2de531c899..94c9fa6116 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.pay.service.wallet; +import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; @@ -13,16 +15,19 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO; import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper; -import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; import java.time.Duration; import java.time.LocalDateTime; import java.util.Objects; @@ -31,8 +36,10 @@ import static cn.hutool.core.util.ObjectUtil.notEqual; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.framework.common.util.number.MoneyUtils.fenToYuanStr; import static cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert.INSTANCE; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID; import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*; /** @@ -61,6 +68,8 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { private PayRefundService payRefundService; @Resource private PayWalletRechargePackageService payWalletRechargePackageService; + @Resource + public SocialClientApi socialClientApi; @Override @Transactional(rollbackFor = Exception.class) @@ -96,7 +105,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { @Override public PageResult getWalletRechargePackagePage(Long userId, Integer userType, - PageParam pageReqVO, Boolean payStatus) { + PageParam pageReqVO, Boolean payStatus) { PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType); return walletRechargeMapper.selectPage(pageReqVO, wallet.getId(), payStatus); } @@ -126,6 +135,24 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { // TODO 需要钱包中加个可提现余额 payWalletService.addWalletBalance(walletRecharge.getWalletId(), String.valueOf(id), PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice()); + + // 4. 发送订阅消息 + getSelf().sendWalletRechargerPaidMessage(payOrderId, walletRecharge); + } + + @Async + public void sendWalletRechargerPaidMessage(Long payOrderId, PayWalletRechargeDO walletRecharge) { + // 1. 获得会员钱包信息 + PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId()); + // 2. 构建并发送模版消息 + socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO() + .setUserId(wallet.getUserId()).setUserType(wallet.getUserType()) + .setTemplateTitle(WXA_WALLET_RECHARGER_PAID) + .setPage("pages/user/wallet/money") // 钱包详情界面 + .addMessage("character_string1", String.valueOf(payOrderId)) // 支付单编号 + .addMessage("amount2", fenToYuanStr(walletRecharge.getTotalPrice())) // 充值金额 + .addMessage("time3", LocalDateTimeUtil.formatNormal(walletRecharge.getCreateTime())) // 充值时间 + .addMessage("phrase4", "充值成功")); // 充值状态 } @Override @@ -282,4 +309,13 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { return payOrder; } + /** + * 获得自身的代理对象,解决 AOP 生效问题 + * + * @return 自己 + */ + private PayWalletRechargeServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java index cd6fc31be9..ef02dd1086 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java @@ -1,12 +1,12 @@ package cn.iocoder.yudao.module.system.api.social; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import javax.validation.Valid; +import java.util.List; + /** * 社交应用的 API 接口 * @@ -17,8 +17,8 @@ public interface SocialClientApi { /** * 获得社交平台的授权 URL * - * @param socialType 社交平台的类型 {@link SocialTypeEnum} - * @param userType 用户类型 + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 * @param redirectUri 重定向 URL * @return 社交平台的授权 URL */ @@ -28,15 +28,17 @@ public interface SocialClientApi { * 创建微信公众号 JS SDK 初始化所需的签名 * * @param userType 用户类型 - * @param url 访问的 URL 地址 + * @param url 访问的 URL 地址 * @return 签名 */ SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url); + //======================= 微信小程序独有 ======================= + /** * 获得微信小程序的手机信息 * - * @param userType 用户类型 + * @param userType 用户类型 * @param phoneCode 手机授权码 * @return 手机信息 */ @@ -50,4 +52,18 @@ public interface SocialClientApi { */ byte[] getWxaQrcode(@Valid SocialWxQrcodeReqDTO reqVO); + /** + * 获得微信小程订阅模板 + * + * @return 小程序订阅消息模版 + */ + List getWxaSubscribeTemplateList(Integer userType); + + /** + * 发送微信小程序订阅消息 + * + * @param reqDTO 请求 + */ + void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java new file mode 100644 index 0000000000..2677a3485a --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微信小程序订阅消息发送 Request DTO + * + * @author HUIHUI + */ +@Data +public class SocialWxaSubscribeMessageSendReqDTO { + + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + * 关联 AdminUserDO 的 id 编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 消息模版标题 + */ + @NotEmpty(message = "消息模版标题不能为空") + private String templateTitle; + + /** + * 点击模板卡片后的跳转页面,仅限本小程序内的页面 + * + * 支持带参数,(示例 index?foo=bar )。该字段不填则模板无跳转。 + */ + private String page; + + /** + * 模板内容的参数 + */ + private Map messages; + + public SocialWxaSubscribeMessageSendReqDTO addMessage(String key, String value) { + if (messages == null) { + messages = new HashMap<>(); + } + messages.put(key, value); + return this; + } + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java new file mode 100644 index 0000000000..ae14cc5432 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import lombok.Data; + + +/** + * 小程序订阅消息模版 Response DTO + * + * @author HUIHUI + */ +@Data +public class SocialWxaSubscribeTemplateRespDTO { + + /** + * 模版编号 + */ + private String id; + + /** + * 模版标题 + */ + private String title; + + /** + * 模版内容 + */ + private String content; + + /** + * 模板内容示例 + */ + private String example; + + /** + * 模版类型 + * + * 2:为一次性订阅 + * 3:为长期订阅 + */ + private Integer type; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 585308dd69..e360a426b9 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -117,8 +117,10 @@ public interface ErrorCodeConstants { ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_200, "获得手机号失败"); ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败"); - ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_202, "社交客户端不存在"); - ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_203, "社交客户端已存在配置"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败"); + ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在"); + ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置"); // ========== OAuth2 客户端 1-002-020-000 ========= diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java index 654857fafb..b69221a0c6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java @@ -1,16 +1,24 @@ package cn.iocoder.yudao.module.system.api.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.service.social.SocialClientService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import javax.annotation.Resource; +import java.util.List; + +import static cn.hutool.core.collection.CollUtil.findOne; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 社交应用的 API 实现类 @@ -19,10 +27,13 @@ import javax.annotation.Resource; */ @Service @Validated +@Slf4j public class SocialClientApiImpl implements SocialClientApi { @Resource private SocialClientService socialClientService; + @Resource + public SocialUserApi socialUserApi; @Override public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) { @@ -35,6 +46,8 @@ public class SocialClientApiImpl implements SocialClientApi { return BeanUtils.toBean(signature, SocialWxJsapiSignatureRespDTO.class); } + //======================= 微信小程序独有 ======================= + @Override public SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode) { WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode); @@ -46,4 +59,38 @@ public class SocialClientApiImpl implements SocialClientApi { return socialClientService.getWxaQrcode(reqVO); } + @Override + public List getWxaSubscribeTemplateList(Integer userType) { + List list = socialClientService.getSubscribeTemplateList(userType); + return convertList(list, item -> BeanUtils.toBean(item, SocialWxaSubscribeTemplateRespDTO.class).setId(item.getPriTmplId())); + } + + @Override + public void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO) { + // 1.1 获得订阅模版列表 + List templateList = getWxaSubscribeTemplateList(reqDTO.getUserType()); + if (CollUtil.isEmpty(templateList)) { + log.warn("[sendSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return; + } + // 1.2 获得需要使用的模版 + SocialWxaSubscribeTemplateRespDTO template = findOne(templateList, item -> + ObjUtil.equal(item.getTitle(), reqDTO.getTemplateTitle())); + if (template == null) { + log.warn("[sendSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return; + } + + // 2. 获得社交用户 + SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(), + SocialTypeEnum.WECHAT_MINI_APP.getType()); + if (StrUtil.isBlankIfStr(socialUser.getOpenid())) { + log.warn("[sendSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO); + return; + } + + // 3. 发送订阅消息 + socialClientService.sendSubscribeMessage(reqDTO, template.getId(), socialUser.getOpenid()); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http new file mode 100644 index 0000000000..5ab64392a7 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http @@ -0,0 +1,20 @@ +### 请求 /system/social-client/send-subscribe-message 接口 => 发送测试订阅消息 +POST {{baseUrl}}/system/social-client/send-subscribe-message +Authorization: Bearer {{token}} +Content-Type: application/json +#Authorization: Bearer test100 +tenant-id: {{adminTenentId}} + +{ + "userId": 247, + "userType": 1, + "socialType": 34, + "templateTitle": "充值成功通知", + "page": "", + "messages": { + "character_string1":"5616122165165", + "amount2":"1000.00", + "time3":"2024-01-01 10:10:10", + "phrase4": "充值成功" + } +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java index c9b5f4da71..e91a3d8a0b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.system.controller.admin.socail; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientRespVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; @@ -11,13 +13,12 @@ import cn.iocoder.yudao.module.system.service.social.SocialClientService; 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 javax.annotation.Resource; -import javax.validation.Valid; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 社交客户端") @@ -28,6 +29,8 @@ public class SocialClientController { @Resource private SocialClientService socialClientService; + @Resource + private SocialClientApi socialClientApi; @PostMapping("/create") @Operation(summary = "创建社交客户端") @@ -70,4 +73,11 @@ public class SocialClientController { return success(BeanUtils.toBean(pageResult, SocialClientRespVO.class)); } + @PostMapping("/send-subscribe-message") + @Operation(summary = "发送订阅消息") // 用于测试 + @PreAuthorize("@ss.hasPermission('system:social-client:query')") + public void sendSubscribeMessage(@RequestBody SocialWxaSubscribeMessageSendReqDTO reqDTO) { + socialClientApi.sendWxaSubscribeMessage(reqDTO); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index 8b9e7681f5..b400ede02d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -98,4 +98,13 @@ public interface RedisKeyConstants { * VALUE 数据格式:String 模版信息 */ String SMS_TEMPLATE = "sms_template"; + + /** + * 小程序订阅模版的缓存 + * + * KEY 格式:wxa_subscribe_template:{userType} + * VALUE 数据格式 String, 模版信息 + */ + String WXA_SUBSCRIBE_TEMPLATE = "wxa_subscribe_template"; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java index b44673ac11..71c716c520 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java @@ -3,12 +3,16 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.xingyuv.jushauth.model.AuthUser; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; + +import java.util.List; import javax.validation.Valid; @@ -70,6 +74,25 @@ public interface SocialClientService { */ byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO); + /** + * 获得微信小程订阅模板 + * + * 缓存的目的:考虑到微信小程序订阅消息选择好模版后几乎不会变动,缓存增加查询效率 + * + * @param userType 用户类型 + * @return 微信小程订阅模板 + */ + List getSubscribeTemplateList(Integer userType); + + /** + * 发送微信小程序订阅消息 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + */ + void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId); + // =================== 客户端管理 =================== /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 9fa6642fba..6907fa54bd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -1,10 +1,14 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.WxMaSubscribeService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ReflectUtil; @@ -15,10 +19,12 @@ import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper; +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; @@ -35,20 +41,25 @@ import com.xingyuv.justauth.AuthRequestFactory; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.Duration; +import java.util.List; +import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -62,7 +73,7 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; public class SocialClientServiceImpl implements SocialClientService { /** - * 小程序版本 + * 小程序码要打开的小程序版本 * * 1. release:正式版 * 2. trial:体验版 @@ -70,6 +81,15 @@ public class SocialClientServiceImpl implements SocialClientService { */ @Value("${yudao.wxa-code.env-version:release}") public String envVersion; + /** + * 订阅消息跳转小程序类型 + * + * 1. developer:开发版 + * 2. trial:体验版 + * 3. formal:正式版 + */ + @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}") + public String miniprogramState; @Resource private AuthRequestFactory authRequestFactory; @@ -254,11 +274,58 @@ public class SocialClientServiceImpl implements SocialClientService { null, ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE)); } catch (WxErrorException e) { - log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e); + log.error("[getWxQrcode][reqVO({}) 获得小程序码失败]", reqVO, e); throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); } } + @Override + @Cacheable(cacheNames = RedisKeyConstants.WXA_SUBSCRIBE_TEMPLATE, key = "#userType", condition = "#result != null") + public List getSubscribeTemplateList(Integer userType) { + WxMaService service = getWxMaService(userType); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + return subscribeService.getTemplateList(); + } catch (WxErrorException e) { + log.error("[getSubscribeTemplate][userType({}) 获得小程序订阅消息模版]", userType, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR); + } + } + + @Override + public void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId) { + WxMaService service = getWxMaService(reqDTO.getUserType()); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + subscribeService.sendSubscribeMsg(buildMessageSendReqDTO(reqDTO, templateId, openId)); + } catch (WxErrorException e) { + log.error("[sendSubscribeMessage][reqVO({}) templateId({}) openId({}) 发送小程序订阅消息失败]", reqDTO, templateId, openId, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR); + } + } + + /** + * 构建发送消息请求参数 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + * @return 微信小程序订阅消息请求参数 + */ + private WxMaSubscribeMessage buildMessageSendReqDTO(SocialWxaSubscribeMessageSendReqDTO reqDTO, + String templateId, String openId) { + // 设置订阅消息基本参数 + WxMaSubscribeMessage subscribeMessage = new WxMaSubscribeMessage().setLang(WxMaConstants.MiniProgramLang.ZH_CN) + .setMiniprogramState(miniprogramState).setTemplateId(templateId).setToUser(openId).setPage(reqDTO.getPage()); + // 设置具体消息参数 + Map messages = reqDTO.getMessages(); + if (CollUtil.isNotEmpty(messages)) { + reqDTO.getMessages().keySet().forEach(key -> findAndThen(messages, key, value -> + subscribeMessage.addData(new WxMaSubscribeMessage.MsgData(key, value)))); + } + return subscribeMessage; + } + /** * 获得 clientId + clientSecret 对应的 WxMpService 对象 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 93ea16ab07..7ef0073864 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; @@ -31,13 +32,14 @@ import com.google.common.annotations.VisibleForTesting; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import javax.annotation.Resource; import java.io.InputStream; import java.time.LocalDateTime; import java.util.*; @@ -443,7 +445,14 @@ public class AdminUserServiceImpl implements AdminUserService { UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>()) .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build(); importUsers.forEach(importUser -> { - // 校验,判断是否有不符合的原因 + // 2.1.1 校验字段是否符合要求 + try { + ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword)); + } catch (ConstraintViolationException ex){ + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + // 2.1.2 校验,判断是否有不符合的原因 try { validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), importUser.getDeptId(), null); @@ -451,7 +460,8 @@ public class AdminUserServiceImpl implements AdminUserService { respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); return; } - // 判断如果不存在,在进行插入 + + // 2.2.1 判断如果不存在,在进行插入 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); if (existUser == null) { userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class) @@ -459,7 +469,7 @@ public class AdminUserServiceImpl implements AdminUserService { respVO.getCreateUsernames().add(importUser.getUsername()); return; } - // 如果存在,判断是否允许更新 + // 2.2.2 如果存在,判断是否允许更新 if (!isUpdateSupport) { respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); return; diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index a5c1962b8c..44153449dd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java @@ -430,6 +430,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { public void testImportUserList_01() { // 准备参数 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock 方法,模拟失败 doThrow(new ServiceException(DEPT_NOT_FOUND)).when(deptService).validateDeptList(any()); @@ -452,6 +454,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { @@ -486,6 +490,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { @@ -516,6 +522,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 5407d70429..49497d74c8 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -174,6 +174,7 @@ logging: cn.iocoder.yudao.module.statistics.dal.mysql: debug cn.iocoder.yudao.module.crm.dal.mysql: debug cn.iocoder.yudao.module.erp.dal.mysql: debug + cn.iocoder.yudao.module.ai.dal.mysql: debug org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 debug: false @@ -195,10 +196,12 @@ wx: miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 # appid: wx62056c0d5e8db250 # 测试号(牛希尧提供的) # secret: 333ae72f41552af1e998fe1f54e1584a - appid: wx63c280fe3248a3e7 # wenhualian的接口测试号 - secret: 6f270509224a7ae1296bbf1c8cb97aed +# appid: wx63c280fe3248a3e7 # wenhualian的接口测试号 +# secret: 6f270509224a7ae1296bbf1c8cb97aed # appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的) # secret: 4a1a04e07f6a4a0751b39c3064a92c8b + appid: wx66186af0759f47c9 # 测试号(puhui 提供的) + secret: 3218bcbd112cbc614c7264ceb20144ac config-storage: type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 key-prefix: wa # Redis Key 的前缀 @@ -220,6 +223,8 @@ yudao: demo: false # 关闭演示模式 wxa-code: env-version: develop # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop" + wxa-subscribe-message: + miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”;体验版为 “trial”为;正式版为 “formal” tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc justauth: