From d54edcd88b707cf230e57ffcee48c48fe949e75f Mon Sep 17 00:00:00 2001 From: maimaishu <14610861+maimaishu@user.noreply.gitee.com> Date: Mon, 5 Jan 2026 17:46:31 +0800 Subject: [PATCH 01/10] =?UTF-8?q?[#]=E4=BF=AE=E6=94=B9=E9=83=A8=E9=97=A8?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E6=B6=88=E6=81=AF=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/api/dept/dto/DeptMsgRespDTO.java | 58 ++++ .../zt/plat/module/system/api/esp/EspApi.java | 16 +- .../module/system/enums/ApiConstants.java | 1 - .../system/enums/ErrorCodeConstants.java | 2 + .../module/system/api/esp/EspApiImpl.java | 22 +- .../system/dal/dataobject/dept/DeptDO.java | 9 +- .../system/service/dept/EspServiceImpl.java | 270 +++++++++++++++++- .../system/service/dept/IEspService.java | 14 +- 8 files changed, 368 insertions(+), 24 deletions(-) create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptMsgRespDTO.java diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptMsgRespDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptMsgRespDTO.java new file mode 100644 index 00000000..784ce6d5 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/dept/dto/DeptMsgRespDTO.java @@ -0,0 +1,58 @@ +package com.zt.plat.module.system.api.dept.dto; + +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 公司部门推送消息 Response DTO + * + * @author ZT + */ +@Schema(description = "RPC 服务 - 部门推送消息 Response DTO") +@Data +public class DeptMsgRespDTO { + + + /** + * 主键编号 + */ + private Long id; + + /** + * 本系统部门 ID + */ + private Long deptId; + + /** + * 外部系统标识 + */ + private String systemCode; + + /** + * 外部系统组织编码 + */ + private String externalDeptCode; + + /** + * 外部系统组织名称 + */ + private String externalDeptName; + + /** + * 映射状态 + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + /** + * 备注 + */ + private String remark; + + /** + * 是否发送消息 + */ + private Integer isSendMsg; + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java index 54e560a9..b52dbe78 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/esp/EspApi.java @@ -2,23 +2,27 @@ package com.zt.plat.module.system.api.esp; import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.module.system.api.dept.dto.*; -import com.zt.plat.module.system.api.esp.dto.EspDto; import com.zt.plat.module.system.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; - import java.util.List; @FeignClient(name = ApiConstants.NAME) -@Tag(name = "RPC 服务 - 部门") +@Tag(name = "RPC 服务 - 部门推送消息") public interface EspApi { - String PREFIX = ApiConstants.PREFIX + "/dept"; + String PREFIX = ApiConstants.PREFIX + "/dept-esp"; + + + @PostMapping(PREFIX + "/create") + @Operation(summary = "新增部门") + CommonResult createDept(@RequestBody DeptSaveReqDTO createReqVO); + @PostMapping(PREFIX + "/pushMsg") - @Operation(summary = "推送消息") - CommonResult> pushMsg(@RequestBody DeptSaveReqDTO syncReqDTO); + @Operation(summary = "查询部门消息") + CommonResult> selectDepMsg(@RequestBody DeptSaveReqDTO syncReqDTO); } diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ApiConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ApiConstants.java index 71f2cfac..6d12eb2b 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ApiConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ApiConstants.java @@ -11,7 +11,6 @@ public class ApiConstants { /** * 服务名 - * * 注意,需要保证和 spring.application.name 保持一致 */ public static final String NAME = "system-server"; diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java index fd2aee37..dea75c74 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java @@ -206,6 +206,8 @@ public interface ErrorCodeConstants { // ========== 用户与部门关系 1-002-029-000 ========== ErrorCode USER_DEPT_NOT_EXISTS = new ErrorCode(1_002_029_000, "用户与部门关系不存在"); + ErrorCode USER_DEPT_SAVE_EXISTS = new ErrorCode(1_002_029_001, "插入用户部门失败"); + // ========== 系统序列号分段明细 1-002-030-000 ========== ErrorCode SEQUENCE_DETAIL_NOT_EXISTS = new ErrorCode(1_002_030_000, "系统序列号分段明细不存在"); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java index 97c8a031..c787504d 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/esp/EspApiImpl.java @@ -2,15 +2,17 @@ package com.zt.plat.module.system.api.esp; import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants; import com.zt.plat.framework.common.pojo.CommonResult; -import com.zt.plat.framework.common.util.object.ObjectUtils; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO; import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO; -import com.zt.plat.module.system.api.esp.dto.EspDto; +import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; import com.zt.plat.module.system.service.dept.IEspService; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Objects; +import static com.zt.plat.framework.common.pojo.CommonResult.success; @RestController @Validated @@ -18,16 +20,26 @@ public class EspApiImpl implements EspApi { @Resource - private IEspService deptService; + private IEspService espService; + + @Override - public CommonResult> pushMsg(DeptSaveReqDTO syncReqDTO) + public CommonResult createDept(DeptSaveReqDTO createReqVO) { + DeptSaveReqVO reqVO = BeanUtils.toBean(createReqVO, DeptSaveReqVO.class); + Long deptId = espService.createDept(reqVO); + return success(deptId); + } + + + @Override + public CommonResult> selectDepMsg(DeptSaveReqDTO syncReqDTO) { if(Objects.isNull(syncReqDTO) || null == syncReqDTO.getId()) { return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "ID不能为空"); } - return CommonResult.success(deptService.pushMsg(syncReqDTO)); + return espService.selectDepMsg(syncReqDTO); } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptDO.java index 9d581a65..f6b50724 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptDO.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/dept/DeptDO.java @@ -74,14 +74,15 @@ public class DeptDO extends TenantBaseDO { * 枚举 {@link CommonStatusEnum} */ private Integer status; - /** - * 是否公司 - */ - private Boolean isCompany; + /** * 是否集团 */ private Boolean isGroup; + /** + * 是否公司 + */ + private Boolean isCompany; /** * 部门来源类型 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java index 17a19662..80dc9097 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/EspServiceImpl.java @@ -1,26 +1,37 @@ package com.zt.plat.module.system.service.dept; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.google.common.annotations.VisibleForTesting; import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants; +import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO; import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO; -import com.zt.plat.module.system.api.esp.dto.EspDto; import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO; import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo; +import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptDO; import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO; import com.zt.plat.module.system.dal.mysql.dept.DeptMapper; import com.zt.plat.module.system.dal.mysql.dept.EspMapper; import com.zt.plat.module.system.dal.redis.RedisKeyConstants; +import com.zt.plat.module.system.enums.dept.DeptSourceEnum; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; -import org.apache.seata.common.result.Result; +import org.apache.seata.spring.annotation.GlobalTransactional; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Objects; import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -33,13 +44,31 @@ import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; @Validated public class EspServiceImpl implements IEspService { + + @Resource + private DeptExternalCodeService deptExternalCodeService; + @Resource private EspMapper espMapper; + @Resource private DeptMapper deptMapper; + @Resource private CacheManager cacheManager; + @Resource + private com.zt.plat.module.system.mq.producer.databus.DatabusChangeProducer databusChangeProducer; + + private static final String ROOT_CODE_PREFIX = "ZT"; + private static final String EXTERNAL_CODE_PREFIX = "CU"; + private static final int CODE_SEGMENT_LENGTH = 3; + private static final int MAX_SEQUENCE = 999; + private static final int BATCH_SIZE = 1000; + private static final Comparator DEPT_COMPARATOR = Comparator + .comparing(DeptDO::getSort, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(DeptDO::getId, Comparator.nullsLast(Comparator.naturalOrder())); + @Override @CacheEvict(cacheNames = RedisKeyConstants.DEPT_EXTERNAL_CODE_LIST, key = "#createReqVO.deptId", beforeInvocation = false) public Long createDeptPushMsg(EspSaveRespVo createReqVO) { @@ -103,12 +132,243 @@ public class EspServiceImpl implements IEspService { return espMapper.selectListByDeptId(deptId); } - @Override - public List pushMsg(DeptSaveReqDTO syncReqDTO) { - return BeanUtils.toBean(espMapper.selectpushMsg(syncReqDTO), EspDto.class); + @Override + @GlobalTransactional(rollbackFor = Exception.class) + @Transactional(rollbackFor = Exception.class) + @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, + allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 + public Long createDept(DeptSaveReqVO createReqVO) { + // 允许上级组织为空,视为顶级组织 + createReqVO.setParentId(normalizeParentId(createReqVO.getParentId())); + // 创建时默认有效 + createReqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + // 默认部门来源:未指定时视为外部部门 + if (createReqVO.getDeptSource() == null) { + createReqVO.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + } + // 校验父部门的有效性 + validateParentDept(null, createReqVO.getParentId()); + // 校验部门名的唯一性 + validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); + // 生成并校验部门编码(所有来源统一走生成逻辑,iWork 不再豁免) + if (Boolean.TRUE.equals(createReqVO.getDelayCodeGeneration())) { + createReqVO.setCode(null); + } else { + String resolvedCode = generateDeptCode(createReqVO.getParentId(), createReqVO.getDeptSource()); + validateDeptCodeUnique(null, resolvedCode); + createReqVO.setCode(resolvedCode); + } + + // 插入部门 + DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); + // 设置部门来源(前置已默认化,此处兜底) + if (dept.getDeptSource() == null) { + dept.setDeptSource(DeptSourceEnum.EXTERNAL.getSource()); + } + int insert = deptMapper.insert(dept); + if (insert == 0) { + throw exception(USER_DEPT_SAVE_EXISTS); + } + // 外部编码映射 + upsertExternalMappingIfPresent(dept.getId(), createReqVO); + // 发布部门创建事件 + databusChangeProducer.sendDeptCreatedMessage(dept); + //推送消息 + DeptSaveReqDTO deptSaveReqDTO = new DeptSaveReqDTO(); + BeanUtils.copyProperties(dept,deptSaveReqDTO); + return dept.getId(); } + @Override + public CommonResult> selectDepMsg(DeptSaveReqDTO syncReqDTO) { + if (syncReqDTO == null) { + return CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if(syncReqDTO.getId() != null){ + wrapper.eq(DeptPushMsgDO::getId, syncReqDTO.getId()); + } + + List list = espMapper.selectList(wrapper); + if (CollectionUtils.isEmpty(list)){ + return CommonResult.error(GlobalErrorCodeConstants.NOT_FOUND); + } + + ArrayList lists = new ArrayList<>(list.size()); + for (DeptPushMsgDO deptPushMsgDO : list){ + DeptMsgRespDTO deptMsgRespDTO = new DeptMsgRespDTO(); + BeanUtils.copyProperties(deptPushMsgDO, deptMsgRespDTO); + lists.add(deptMsgRespDTO); + } + return CommonResult.success(lists); + } + + private Long normalizeParentId(Long parentId) { + return parentId == null ? DeptDO.PARENT_ID_ROOT : parentId; + } + + @VisibleForTesting + void validateParentDept(Long id, Long parentId) { + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + return; + } + // 1. 不能设置自己为父部门 + if (Objects.equals(id, parentId)) { + throw exception(DEPT_PARENT_ERROR); + } + // 2. 父部门不存在 + DeptDO parentDept = deptMapper.selectById(parentId); + if (parentDept == null) { + return; + } + // 3. 递归校验父部门,如果父部门是自己的子部门,则报错,避免形成环路 + if (id == null) { // id 为空,说明新增,不需要考虑环路 + return; + } + for (int i = 0; i < Short.MAX_VALUE; i++) { + // 3.1 校验环路 + parentId = parentDept.getParentId(); + if (Objects.equals(id, parentId)) { + throw exception(DEPT_PARENT_IS_CHILD); + } + // 3.2 继续递归下一级父部门 + if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { + break; + } + parentDept = deptMapper.selectById(parentId); + if (parentDept == null) { + break; + } + } + } + + + private String generateDeptCode(Long parentId, Integer deptSource) { + Long effectiveParentId = normalizeParentId(parentId); + String prefix = resolveCodePrefix(effectiveParentId, deptSource); + int nextSequence = determineNextSequence(effectiveParentId, prefix); + assertSequenceRange(nextSequence); + return prefix + formatSequence(nextSequence); + } + + + @VisibleForTesting + void validateDeptNameUnique(Long id, Long parentId, String name) { + Long effectiveParentId = normalizeParentId(parentId); + if (Objects.equals(effectiveParentId, DeptDO.PARENT_ID_ROOT)) { + return; + } + DeptDO dept = deptMapper.selectByParentIdAndName(effectiveParentId, name); + if (dept == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的部门 + if (id == null) { + throw exception(DEPT_NAME_DUPLICATE); + } + if (ObjectUtil.notEqual(dept.getId(), id)) { + throw exception(DEPT_NAME_DUPLICATE); + } + } + + + @VisibleForTesting + void validateDeptCodeUnique(Long id, String code) { + if (StrUtil.isBlank(code)) { + return; + } + DeptDO dept = deptMapper.selectByCode(code); + if (dept == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的部门 + if (id == null) { + throw exception(DEPT_CODE_DUPLICATE); + } + if (ObjectUtil.notEqual(dept.getId(), id)) { + throw exception(DEPT_CODE_DUPLICATE); + } + } + + private void upsertExternalMappingIfPresent(Long deptId, DeptSaveReqVO reqVO) { + String systemCode = StrUtil.trimToNull(reqVO.getExternalSystemCode()); + String externalCode = StrUtil.trimToNull(reqVO.getExternalDeptCode()); + if (StrUtil.hasEmpty(systemCode, externalCode) || deptId == null) { + return; + } + String externalName = StrUtil.trimToNull(reqVO.getExternalDeptName()); + deptExternalCodeService.saveOrUpdateDeptExternalCode(deptId, systemCode, externalCode, externalName, reqVO.getStatus()); + } + + + private String resolveCodePrefix(Long parentId, Integer deptSource) { + boolean isExternal = Objects.equals(deptSource, DeptSourceEnum.EXTERNAL.getSource()); + if (DeptDO.PARENT_ID_ROOT.equals(parentId)) { + return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX; + } + + DeptDO parentDept = deptMapper.selectById(parentId); + if (parentDept == null || StrUtil.isBlank(parentDept.getCode())) { + return isExternal ? EXTERNAL_CODE_PREFIX : ROOT_CODE_PREFIX; + } + + String parentCode = parentDept.getCode(); + if (isExternal) { + if (parentCode.startsWith(EXTERNAL_CODE_PREFIX)) { + return parentCode; + } + if (parentCode.startsWith(ROOT_CODE_PREFIX)) { + return EXTERNAL_CODE_PREFIX + parentCode.substring(ROOT_CODE_PREFIX.length()); + } + return EXTERNAL_CODE_PREFIX; + } + return parentCode; + } + + + private int determineNextSequence(Long parentId, String prefix) { + DeptDO lastChild = deptMapper.selectLastChildByCode(parentId, prefix); + Integer sequence = parseSequence(lastChild != null ? lastChild.getCode() : null, prefix); + if (sequence != null) { + return sequence + 1; + } + return deptMapper.selectListByParentId(parentId, null).stream() + .map(DeptDO::getCode) + .map(code -> parseSequence(code, prefix)) + .filter(Objects::nonNull) + .max(Integer::compareTo) + .map(val -> val + 1) + .orElse(1); + } + + + private void assertSequenceRange(int sequence) { + if (sequence > MAX_SEQUENCE) { + throw exception(DEPT_CODE_OUT_OF_RANGE); + } + } + + private String formatSequence(int sequence) { + return StrUtil.padPre(String.valueOf(sequence), CODE_SEGMENT_LENGTH, '0'); + } + + private Integer parseSequence(String code, String prefix) { + if (StrUtil.isBlank(code) || StrUtil.isBlank(prefix) || !code.startsWith(prefix)) { + return null; + } + String suffix = code.substring(prefix.length()); + if (suffix.length() != CODE_SEGMENT_LENGTH || !StrUtil.isNumeric(suffix)) { + return null; + } + return Integer.parseInt(suffix); + } + + + + + private DeptPushMsgDO validateExists(Long id) { if (id == null) { throw exception(DEPT_EXTERNAL_RELATION_NOT_EXISTS); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java index 7678def2..9394d8d8 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/dept/IEspService.java @@ -1,13 +1,14 @@ package com.zt.plat.module.system.service.dept; +import com.zt.plat.framework.common.pojo.CommonResult; import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.api.dept.dto.DeptMsgRespDTO; import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO; -import com.zt.plat.module.system.api.esp.dto.EspDto; import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspPageReqVO; import com.zt.plat.module.system.controller.admin.dept.vo.depexternalcode.EspSaveRespVo; +import com.zt.plat.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; import com.zt.plat.module.system.dal.dataobject.dept.DeptPushMsgDO; import java.util.List; - /** * 部门推送消息 Service 接口 */ @@ -47,11 +48,18 @@ public interface IEspService { */ List getPushMsgByDeptId(Long deptId); + /** + * 创建部门 + * @param reqVO 部门信息 + * @return 部门编号 + */ + Long createDept(DeptSaveReqVO reqVO); /** * 推送部门数据到外部系统 * @param syncReqDTO 同步请求 */ - List pushMsg(DeptSaveReqDTO syncReqDTO); + CommonResult> selectDepMsg(DeptSaveReqDTO syncReqDTO); + } From a07c71f1a06aa5f7ea11fb35bb5fdcf8ac556845 Mon Sep 17 00:00:00 2001 From: maimaishu <14610861+maimaishu@user.noreply.gitee.com> Date: Wed, 7 Jan 2026 08:44:35 +0800 Subject: [PATCH 02/10] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E5=9B=BD=E5=AF=86SM?= =?UTF-8?q?4=E6=8E=A5=E5=8F=A3=E5=8A=A0=E8=A7=A3=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zt-framework/zt-common/pom.xml | 7 ++ .../util/security/CryptoSignatureUtils.java | 103 +++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/zt-framework/zt-common/pom.xml b/zt-framework/zt-common/pom.xml index db7d9ceb..216e0c4b 100644 --- a/zt-framework/zt-common/pom.xml +++ b/zt-framework/zt-common/pom.xml @@ -163,6 +163,13 @@ mockito-core test + + + org.bouncycastle + bcprov-jdk18on + 1.78.1 + compile + diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java index b0d9be83..6546cb55 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java @@ -1,8 +1,10 @@ package com.zt.plat.framework.common.util.security; import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.symmetric.SM4; import com.zt.plat.framework.common.util.json.JsonUtils; - +import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -10,11 +12,13 @@ import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.Security; import java.util.*; /** * 通用的签名、加解密工具类 */ +@Slf4j public final class CryptoSignatureUtils { public static final String ENCRYPT_TYPE_AES = "AES"; @@ -25,6 +29,15 @@ public final class CryptoSignatureUtils { private static final String AES_TRANSFORMATION = "AES/ECB/PKCS5Padding"; public static final String SIGNATURE_FIELD = "signature"; + private static final String CHARSET = "UTF-8"; + + //@Value("${sa.encrypt.sm4.key}") + private static String SM4_KEY = "1234567890123456"; + + static { + Security.addProvider(new BouncyCastleProvider()); + } + private CryptoSignatureUtils() { } @@ -215,4 +228,92 @@ public final class CryptoSignatureUtils { } return sb.toString(); } + + +//-------------------------------- 国密方式2 ------------------------------------------------------- + + /** + * 加密 + */ + public static String enCode(String data) { + try { + // 第一步: SM4 加密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + String encryptHex = sm4.encryptHex(data); + + // 第二步: Base64 编码 + return new String(Base64.getEncoder().encode(encryptHex.getBytes(CHARSET)), CHARSET); + + } catch (Exception e) { + log.error("国密加密失败{}",e.getMessage(),e); + System.out.println("加密失败"+e); + return ""; + } + } + + /** + * 解密 + */ + public static String deCode(String data) { + try { + + // 第一步: Base64 解码 + byte[] base64Decode = Base64.getDecoder().decode(data); + + // 第二步: SM4 解密 + SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY))); + return sm4.decryptStr(new String(base64Decode)); + + } catch (Exception e) { + log.error("国密解密失败{}",e.getMessage(),e); + return ""; + } + } + + + + public static String stringToHex(String input) { + char[] chars = input.toCharArray(); + StringBuilder hex = new StringBuilder(); + for (char c : chars) { + hex.append(Integer.toHexString((int) c)); + } + return hex.toString(); + } + + /** + * 16 进制串转字节数组 + * + * @param hex 16进制字符串 + * @return byte数组 + */ + public static byte[] hexToBytes(String hex) { + int length = hex.length(); + byte[] result; + if (length % 2 == 1) { + length++; + result = new byte[(length / 2)]; + hex = "0" + hex; + } else { + result = new byte[(length / 2)]; + } + int j = 0; + for (int i = 0; i < length; i += 2) { + result[j] = hexToByte(hex.substring(i, i + 2)); + j++; + } + return result; + } + + /** + * 16 进制字符转字节 + * + * @param hex 16进制字符 0x00到0xFF + * @return byte + */ + private static byte hexToByte(String hex) { + return (byte) Integer.parseInt(hex, 16); + } + + } From 1fd704fe9d3df706d2def4fb341c41a2b62a56b5 Mon Sep 17 00:00:00 2001 From: lzx <1234> Date: Fri, 9 Jan 2026 15:33:33 +0800 Subject: [PATCH 03/10] =?UTF-8?q?fix(databus):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=90=8C=E6=AD=A5=E7=9A=84=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E8=BF=87=E6=BB=A4=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将用户同步的数据源从 userSource = 2 更新为 userSource = 3 - 修正了数据总线用户提供者API中的查询条件配置 --- .../module/system/api/databus/DatabusUserProviderApiImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java index f6b9050a..4e2f53e8 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java @@ -54,8 +54,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { // 构建游标查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - // ⚠️ 只同步 userSource = 2 的用户 - queryWrapper.eq(AdminUserDO::getUserSource, 2); + // ⚠️ 只同步 userSource = 3 的用户 + queryWrapper.eq(AdminUserDO::getUserSource, 3); // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) if (!reqDTO.isFirstPage()) { From f8f584b9ff41405173060c4f616e3090b08cc334 Mon Sep 17 00:00:00 2001 From: lzx <1234> Date: Fri, 9 Jan 2026 15:50:18 +0800 Subject: [PATCH 04/10] =?UTF-8?q?fix(databus):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=90=8C=E6=AD=A5=E7=9A=84=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E8=BF=87=E6=BB=A4=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将用户同步的数据源从 userSource = 2 更新为 userSource = 3 - 修正了数据总线用户提供者API中的查询条件配置 --- .../system/api/databus/DatabusUserProviderApiImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java index 4e2f53e8..3157ad2c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusUserProviderApiImpl.java @@ -103,8 +103,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { Long total = null; if (reqDTO.isFirstPage()) { LambdaQueryWrapper countWrapper = new LambdaQueryWrapper<>(); - // ⚠️ 只统计 userSource = 2 的用户 - countWrapper.eq(AdminUserDO::getUserSource, 2); + // ⚠️ 只统计 userSource = 3 的用户 + countWrapper.eq(AdminUserDO::getUserSource, 3); if (reqDTO.getTenantId() != null) { countWrapper.eq(AdminUserDO::getTenantId, reqDTO.getTenantId()); } @@ -148,8 +148,8 @@ public class DatabusUserProviderApiImpl implements DatabusUserProviderApi { @Override public CommonResult count(Long tenantId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - // ⚠️ 只统计 userSource = 2 的用户 - queryWrapper.eq(AdminUserDO::getUserSource, 2); + // ⚠️ 只统计 userSource = 3 的用户 + queryWrapper.eq(AdminUserDO::getUserSource, 3); if (tenantId != null) { queryWrapper.eq(AdminUserDO::getTenantId, tenantId); } From 43c4c8a94ecb310edba5481a57b388ae6994fa6f Mon Sep 17 00:00:00 2001 From: lzx <1234> Date: Fri, 9 Jan 2026 17:02:02 +0800 Subject: [PATCH 05/10] =?UTF-8?q?fix(databus):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=83=A8=E9=97=A8=E6=95=B0=E6=8D=AE=E6=9F=A5=E8=AF=A2=E4=B8=AD?= =?UTF-8?q?=E7=BC=BA=E5=B0=91=E6=95=B0=E6=8D=AE=E6=BA=90=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在游标分页查询中添加部门数据源过滤条件 - 在总数统计查询中添加部门数据源过滤条件 - 在计数接口中添加部门数据源过滤条件 --- .../module/system/api/databus/DatabusDeptProviderApiImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java index 909845dc..9c5e55b7 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/databus/DatabusDeptProviderApiImpl.java @@ -41,7 +41,7 @@ public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi { public CommonResult> getPageByCursor(CursorPageReqDTO reqDTO) { // 构建游标查询条件 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - + queryWrapper.eq(DeptDO::getDeptSource, 3); // 游标条件:create_time > cursorTime OR (create_time = cursorTime AND id > cursorId) if (!reqDTO.isFirstPage()) { queryWrapper.and(w -> w @@ -105,6 +105,7 @@ public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi { Long total = null; if (reqDTO.isFirstPage()) { LambdaQueryWrapper countWrapper = new LambdaQueryWrapper<>(); + countWrapper.eq(DeptDO::getDeptSource, 3); if (reqDTO.getTenantId() != null) { countWrapper.eq(DeptDO::getTenantId, reqDTO.getTenantId()); } @@ -175,6 +176,7 @@ public class DatabusDeptProviderApiImpl implements DatabusDeptProviderApi { @Override public CommonResult count(Long tenantId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DeptDO::getDeptSource, 3); if (tenantId != null) { queryWrapper.eq(DeptDO::getTenantId, tenantId); } From 041a89bb5984438f045f40ec619ca0264816b0b0 Mon Sep 17 00:00:00 2001 From: lzx <1234> Date: Fri, 9 Jan 2026 17:46:43 +0800 Subject: [PATCH 06/10] =?UTF-8?q?fix(user-dept):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=9D=A5=E6=BA=90=E7=AD=9B=E9=80=89=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将用户来源筛选条件从 user_source = 2 更新为 user_source = 3 - 更新了查询用户部门关系的SQL条件 - 更新了统计用户部门关系数量的SQL条件 - 修正了相关注释说明 --- .../module/system/dal/mysql/userdept/UserDeptMapper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java index bf21f41d..2ec5ad18 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/userdept/UserDeptMapper.java @@ -59,7 +59,7 @@ public interface UserDeptMapper extends BaseMapperX { @Select("") Long countWithUserSource(@Param("tenantId") Long tenantId); -} \ No newline at end of file +} From 2489a7f73e4fc6109121b6b665f5476a8439db0d Mon Sep 17 00:00:00 2001 From: maimaishu <14610861+maimaishu@user.noreply.gitee.com> Date: Mon, 12 Jan 2026 11:05:28 +0800 Subject: [PATCH 07/10] =?UTF-8?q?[+]=E5=A2=9E=E5=8A=A0=E5=9B=BD=E5=AF=86SM?= =?UTF-8?q?4=E5=8A=A0=E8=A7=A3=E5=AF=86=E5=B7=A5=E5=85=B7=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- sql/dm/短信渠道中铝e办支持_DM8_20260109.sql | 9 + zt-framework/zt-common/pom.xml | 20 + .../framework/common/pojo/PageResult.java | 2 +- .../common/util/http/HttpClientUtils.java | 382 +++++++++ .../util/security/CryptoSignatureUtils.java | 1 - .../common/util/security/DESUtil.java | 195 +++++ .../util/validation/MatcherSolveUtils.java | 210 +++++ .../TenantRocketMQSendMessageHook.java | 2 +- .../mybatis/core/mapper/BaseMapperX.java | 2 +- .../module/system/api/msg/MsgSendApi.java | 72 ++ .../module/system/api/sms/dto/code/User.java | 44 ++ .../dto/send/SmsSendSingleToUserReqDTO.java | 1 - .../system/enums/ErrorCodeConstants.java | 4 + .../zt-module-system-server/pom.xml | 7 + .../module/system/api/msg/MsgSendApiImpl.java | 84 ++ .../admin/sms/MsgCallBackController.java | 18 + .../admin/sms/SmsCallbackController.java | 11 +- .../admin/sms/SmsTemplateController.java | 27 +- .../admin/sms/vo/msg/BaseMessage.java | 48 ++ .../controller/admin/sms/vo/msg/Btn.java | 34 + .../admin/sms/vo/msg/ContentItem.java | 22 + .../admin/sms/vo/msg/FileMessage.java | 30 + .../admin/sms/vo/msg/ImageMessage.java | 31 + .../admin/sms/vo/msg/InteractiveTaskCard.java | 40 + .../vo/msg/InteractiveTaskCardMessage.java | 29 + .../controller/admin/sms/vo/msg/Markdown.java | 17 + .../admin/sms/vo/msg/MarkdownMessage.java | 25 + .../controller/admin/sms/vo/msg/Media.java | 28 + .../admin/sms/vo/msg/MiniProgramNotice.java | 44 ++ .../sms/vo/msg/MiniProgramNoticeMessage.java | 28 + .../admin/sms/vo/msg/MpNewsMessage.java | 36 + .../admin/sms/vo/msg/NewsMessage.java | 31 + .../controller/admin/sms/vo/msg/Text.java | 43 + .../admin/sms/vo/msg/TextCardMessage.java | 29 + .../admin/sms/vo/msg/TextMessage.java | 33 + .../admin/sms/vo/msg/VideoMessage.java | 30 + .../admin/sms/vo/msg/VoiceMessage.java | 25 + .../sms/vo/template/MsgTemplateSendReqVO.java | 31 + .../dal/dataobject/msg/MsgChannelDO.java | 69 ++ .../dal/dataobject/msg/MsgTemplateDO.java | 87 +++ .../dal/dataobject/msg/SysActiveMq.java | 56 ++ .../dal/dataobject/msg/SysActiveMqLog.java | 56 ++ .../dal/dataobject/msg/SysWxAuditConfig.java | 89 +++ .../system/dal/mysql/msg/SysActiveMqDao.java | 12 + .../sms/core/client/dto/msg/Article.java | 28 + .../sms/core/client/dto/msg/MpArticle.java | 44 ++ .../sms/core/client/dto/msg/MpNews.java | 19 + .../sms/core/client/dto/msg/News.java | 19 + .../sms/core/client/dto/msg/TextCard.java | 37 + .../client/impl/SmsClientFactoryImpl.java | 2 +- .../sms/core/client/impl/ZleSmsClient.java | 171 ++++ .../sms/core/enums/AuditConstants.java | 155 ++++ .../framework/sms/core/enums/JmsConstant.java | 55 ++ .../sms/core/enums/SmsChannelEnum.java | 1 + .../sms/core/enums/WxMsgTypeConstant.java | 94 +++ .../mq/consumer/msg/MsgSendConsumer.java | 176 +++++ .../system/mq/message/msg/MsgSendMessage.java | 43 + .../system/mq/producer/msg/MsgProducer.java | 38 + .../service/msg/IMonitorMsgService.java | 68 ++ .../system/service/msg/IMsgLogService.java | 88 +++ .../system/service/msg/ISendMsgService.java | 125 +++ .../system/service/msg/ISendWxMsgService.java | 108 +++ .../service/msg/ISysRocketMqService.java | 48 ++ .../service/msg/SysWxAuditConfigService.java | 14 + .../service/msg/config/HttpUrlConfig.java | 296 +++++++ .../service/msg/config/WeiXinProperties.java | 376 +++++++++ .../service/msg/impl/MsgLogServiceImpl.java | 78 ++ .../msg/impl/RocketmqMsgQueueReceiver.java | 106 +++ .../service/msg/impl/SendMsgServiceImpl.java | 732 ++++++++++++++++++ .../msg/impl/SendWxMsgServiceImpl.java | 587 ++++++++++++++ .../msg/impl/SysActiveMqServiceImpl.java | 100 +++ .../msg/impl/SysWxAuditConfigImpl.java | 66 ++ .../service/sms/SmsSendServiceImpl.java | 2 +- .../module/system/util/StringSolveUtils.java | 24 + .../src/main/resources/config.properties | 155 ++++ .../src/main/resources/qywx.properties | 43 + .../service/msg/MsgSendServiceImplTest.java | 305 ++++++++ 78 files changed, 6284 insertions(+), 17 deletions(-) create mode 100644 sql/dm/短信渠道中铝e办支持_DM8_20260109.sql create mode 100644 zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/http/HttpClientUtils.java create mode 100644 zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/DESUtil.java create mode 100644 zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/validation/MatcherSolveUtils.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApi.java create mode 100644 zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/code/User.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApiImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/MsgCallBackController.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/BaseMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Btn.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ContentItem.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/FileMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ImageMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCard.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCardMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Markdown.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MarkdownMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Media.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNotice.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNoticeMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MpNewsMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/NewsMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Text.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextCardMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VideoMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VoiceMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/template/MsgTemplateSendReqVO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgChannelDO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgTemplateDO.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMq.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMqLog.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysWxAuditConfig.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/msg/SysActiveMqDao.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/Article.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpArticle.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpNews.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/News.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/TextCard.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/ZleSmsClient.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/AuditConstants.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/JmsConstant.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/WxMsgTypeConstant.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/consumer/msg/MsgSendConsumer.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/msg/MsgSendMessage.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/msg/MsgProducer.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMonitorMsgService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMsgLogService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendMsgService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendWxMsgService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISysRocketMqService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/SysWxAuditConfigService.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/HttpUrlConfig.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/WeiXinProperties.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/MsgLogServiceImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/RocketmqMsgQueueReceiver.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendMsgServiceImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendWxMsgServiceImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysActiveMqServiceImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysWxAuditConfigImpl.java create mode 100644 zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/util/StringSolveUtils.java create mode 100644 zt-module-system/zt-module-system-server/src/main/resources/config.properties create mode 100644 zt-module-system/zt-module-system-server/src/main/resources/qywx.properties create mode 100644 zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java diff --git a/pom.xml b/pom.xml index 16bfa99f..b548ae4d 100644 --- a/pom.xml +++ b/pom.xml @@ -282,13 +282,13 @@ - chenbowen + haodongqiang dev 172.16.46.63:30848 - chenbowen + haodongqiang DEFAULT_GROUP nacos P@ssword25 diff --git a/sql/dm/短信渠道中铝e办支持_DM8_20260109.sql b/sql/dm/短信渠道中铝e办支持_DM8_20260109.sql new file mode 100644 index 00000000..6e8441ce --- /dev/null +++ b/sql/dm/短信渠道中铝e办支持_DM8_20260109.sql @@ -0,0 +1,9 @@ +-- 短信渠道新增鸿联九五支持(达梦8) +-- 1) system_sms_channel 表新增 epid 字段 +-- 2) system_sms_channel_code 字典新增 HL95 选项 + +ALTER TABLE system_sms_channel ADD COLUMN epid VARCHAR(64); +COMMENT ON COLUMN system_sms_channel.epid IS '企业编号(epid)'; + +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) +VALUES (1593, 5, '中铝e办', 'ZLE', 'system_sms_channel_code', 0, '', '', '', '1', '2026-01-09 00:00:00', '1', '2026-01-09 00:00:00', '0'); diff --git a/zt-framework/zt-common/pom.xml b/zt-framework/zt-common/pom.xml index 216e0c4b..4939018b 100644 --- a/zt-framework/zt-common/pom.xml +++ b/zt-framework/zt-common/pom.xml @@ -170,6 +170,26 @@ 1.78.1 compile + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents + httpclient + 4.5.14 + compile + + + org.apache.httpcomponents + httpmime + 4.5.14 + compile + diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/pojo/PageResult.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/pojo/PageResult.java index 103230f5..cd58d76e 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/pojo/PageResult.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/pojo/PageResult.java @@ -15,7 +15,7 @@ import java.util.List; import java.util.Map; @Schema(description = "分页结果") -@Data +@Data //TODO 分页结果参考这个 public final class PageResult implements Serializable { @Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/http/HttpClientUtils.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/http/HttpClientUtils.java new file mode 100644 index 00000000..f57ac42a --- /dev/null +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/http/HttpClientUtils.java @@ -0,0 +1,382 @@ +package com.zt.plat.framework.common.util.http; + +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +/** + * HttpClient工具类 + * + * @author luzemin + */ +@Slf4j +public class HttpClientUtils { + /** + * 请求配置对象 + */ + private static final RequestConfig REQUEST_CONFIG; + + static { + /* 设置请求和传输超时时间 */ + REQUEST_CONFIG = RequestConfig.custom().setSocketTimeout(60000).setConnectTimeout(60000).build(); + } + + /** + * post请求传输json参数 + * + * @param url url地址 + * @param jsonParam 参数 + * @return JSONObject 请求结果对象 + */ + public static JSONObject httpPost(String url, JSONObject jsonParam) { + /* 请求返回结果 */ + JSONObject jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + try { + /* 构建请求体 */ + if (StringUtils.isNotBlank(jsonParam.toJSONString())) { + StringEntity entity = new StringEntity(jsonParam.toJSONString(), StandardCharsets.UTF_8); + entity.setContentEncoding(StandardCharsets.UTF_8.name()); + entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setEntity(entity); + } + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + jsonResult = JSONObject.parseObject(result); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + /** + * post请求传输String参数 例如:name=Jack&sex=1&type=2 + * Content-type:application/x-www-form-urlencoded + * + * @param url url地址 + * @param strParam 参数 + * @return JSONObject 请求结果对象 + */ + public static JSONObject httpPost(String url, String strParam) { + /* 请求返回结果 */ + JSONObject jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + try { + /* 构建请求体 */ + if (StringUtils.isNotBlank(strParam)) { + StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8); + entity.setContentEncoding(StandardCharsets.UTF_8.name()); + entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setEntity(entity); + } + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + jsonResult = JSONObject.parseObject(result); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + /** + * post请求传输String参数 例如:name=Jack&sex=1&type=2 + * Content-type:application/x-www-form-urlencoded + * + * @param url url地址 + * @param strParam 参数 + * @param token 身份认证令牌 + * @return JSONObject 请求结果对象 + */ + public static JSONObject httpPostByToken(String url, String strParam, String token) { + /* 请求返回结果 */ + JSONObject jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + try { + /* 构建请求体 */ + if (StringUtils.isNotBlank(strParam)) { + StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8); + entity.setContentEncoding(StandardCharsets.UTF_8.name()); + entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setEntity(entity); + } + if (StringUtils.isNotBlank(token)) { + httpPost.setHeader("token", token); + } + + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + jsonResult = JSONObject.parseObject(result); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + /** + * post请求传输String参数 例如:name=Jack&sex=1&type=2 + * Content-type:application/x-www-form-urlencoded + * + * @param url url地址 + * @param strParam 参数 + * @return String 请求结果对象 + */ + public static String httpPostStr(String url, String strParam) { + /* 请求返回结果 */ + String jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + try { + /* 构建请求体 */ + if (StringUtils.isNotBlank(strParam)) { + StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8); + entity.setContentEncoding(StandardCharsets.UTF_8.name()); + entity.setContentType(ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + httpPost.setEntity(entity); + } + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + + /** + * post请求传输String参数 例如:name=Jack&sex=1&type=2 + * Content-type:application/x-www-form-urlencoded + * + * @param url url地址 + * @param strParam 参数 + * @param token 身份认证字符串 + * @return String 请求结果对象 + */ + public static String httpPostStrByToken(String url, String strParam, String token) { + /* 请求返回结果 */ + String jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + try { + /* 构建请求体 */ + if (StringUtils.isNotBlank(strParam)) { + StringEntity entity = new StringEntity(strParam, StandardCharsets.UTF_8); + entity.setContentEncoding(StandardCharsets.UTF_8.name()); + entity.setContentType(ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setEntity(entity); + } + + if (StringUtils.isNotBlank(token)) { + httpPost.setHeader("token", token); + } + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + /** + * 根据指定的url地址上传文件 + * + * @param mediaName 服务器定义的读取文件流的name + * @param url 文件上传url + * @param file 文件对象 + * @return JSONObject 文件上传结果 + */ + public static JSONObject httpFileUpload(String mediaName, String url, MultipartFile file) { + /* 请求返回结果 */ + JSONObject jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(REQUEST_CONFIG); + + /* 构建请求头 */ + String boundaryStr = UUID.randomUUID().toString(); + httpPost.setHeader(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name()); + httpPost.setHeader(HttpHeaders.CONNECTION, "Keep-Alive"); + httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.MULTIPART_FORM_DATA.getMimeType() + ";boundary=" + boundaryStr); + + try { + /* 构建文件对象 */ + MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); + multipartEntityBuilder.setBoundary(boundaryStr) + .setContentType(ContentType.APPLICATION_OCTET_STREAM) + .setCharset(StandardCharsets.UTF_8) + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + multipartEntityBuilder.addBinaryBody(mediaName, file.getInputStream(), ContentType.APPLICATION_OCTET_STREAM, file.getOriginalFilename()); + HttpEntity entity = multipartEntityBuilder.build(); + httpPost.setEntity(entity); + + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpPost); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + jsonResult = JSONObject.parseObject(result); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpPost.releaseConnection(); + } + return jsonResult; + } + + /** + * 发送get请求 + * + * @param url 路径 + * @return JSONObject 请求结果对象 + */ + public static JSONObject httpGet(String url) { + /* 请求返回结果 */ + JSONObject jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(REQUEST_CONFIG); + + try { + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpGet); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + String result = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + jsonResult = JSONObject.parseObject(result); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpGet.releaseConnection(); + } + return jsonResult; + } + + /** + * 发送get请求 + * + * @param url 路径 + * @return JSONObject 请求结果对象 + */ + public static String httpGetStr(String url) { + /* 请求返回结果 */ + String jsonResult = null; + + /* 构建连接对象 */ + CloseableHttpClient httpClient = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(REQUEST_CONFIG); + + try { + /* 提交请求,构建响应对象 */ + CloseableHttpResponse response = httpClient.execute(httpGet); + + /* 处理请求结果 */ + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity responseEntity = response.getEntity(); + jsonResult = EntityUtils.toString(responseEntity, StandardCharsets.UTF_8); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + httpGet.releaseConnection(); + } + return jsonResult; + } + +} \ No newline at end of file diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java index 6546cb55..94dbca7d 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/CryptoSignatureUtils.java @@ -246,7 +246,6 @@ public final class CryptoSignatureUtils { } catch (Exception e) { log.error("国密加密失败{}",e.getMessage(),e); - System.out.println("加密失败"+e); return ""; } } diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/DESUtil.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/DESUtil.java new file mode 100644 index 00000000..fe0a257f --- /dev/null +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/security/DESUtil.java @@ -0,0 +1,195 @@ +package com.zt.plat.framework.common.util.security; + +import javax.crypto.Cipher; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 3DES加密解密工具类 + * 注意:如果websphere下报错:Could not find class 'com.sun.crypto.provider.SunJCE' + * 解决方法如下: 下载sunjce_provider.jar放到jdk目录\jre\lib\ext里即可解决 + * + * @author apple + */ + +public class DESUtil { + + /** + * 默认的密钥 + */ + private static final String strDefaultKey = "cymco-20160329000000000000000ABCGHDYFYSSPPOEWWWDDDSSXX-cymco"; + + private Cipher encryptCipher = null; + + private Cipher decryptCipher = null; + + /** + * 将byte数组转换为表示16进制值的字符串, 如:byte[]{8,18}转换为:0813, 和public static byte[] + * hexStr2ByteArr(String strIn) 互为可逆的转换过程 + * @param arrB 需要转换的byte数组 + * @return 转换后的字符串 + * @throws Exception 本方法不处理任何异常,所有异常全部抛出 + */ + public static String byteArr2HexStr(byte[] arrB) throws Exception { + int iLen = arrB.length; + // 每个byte用两个字符才能表示,所以字符串的长度是数组长度的两倍 + StringBuffer sb = new StringBuffer(iLen * 2); + for (int i = 0; i < iLen; i++) { + int intTmp = arrB[i]; + // 把负数转换为正数 + while (intTmp < 0) { + intTmp = intTmp + 256; + } + // 小于0F的数需要在前面补0 + if (intTmp < 16) { + sb.append("0"); + } + sb.append(Integer.toString(intTmp, 16)); + } + return sb.toString(); + } + + /** + * 将表示16进制值的字符串转换为byte数组, 和public static String byteArr2HexStr(byte[] arrB) + * 互为可逆的转换过程 + * @param strIn 需要转换的字符串 + * @return 转换后的byte数组 + * @throws Exception 本方法不处理任何异常,所有异常全部抛出 + */ + public static byte[] hexStr2ByteArr(String strIn) throws Exception { + byte[] arrB = strIn.getBytes(StandardCharsets.UTF_8); + int iLen = arrB.length; + + // 两个字符表示一个字节,所以字节数组长度是字符串长度除以2 + byte[] arrOut = new byte[iLen / 2]; + for (int i = 0; i < iLen; i = i + 2) { + String strTmp = new String(arrB, i, 2); + arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16); + } + return arrOut; + } + + /** + * 默认构造方法,使用默认密钥 + * @throws Exception + */ + public DESUtil() throws Exception { + this(strDefaultKey); + } + + /** + * 指定密钥构造方法 + * @param strKey 指定的密钥 + * @throws Exception + */ + public DESUtil(String strKey) throws Exception { + //Security.addProvider(new com.sun.crypto.provider.SunJCE()); + Key key = getKey(strKey.getBytes()); + + encryptCipher = Cipher.getInstance("DES"); + encryptCipher.init(Cipher.ENCRYPT_MODE, key); + + decryptCipher = Cipher.getInstance("DES"); + decryptCipher.init(Cipher.DECRYPT_MODE, key); + } + + /** + * 加密字节数组 + * @param arrB 需加密的字节数组 + * @return 加密后的字节数组 + * @throws Exception + */ + public byte[] encrypt(byte[] arrB) throws Exception { + return encryptCipher.doFinal(arrB); + } + + /** + * 加密字符串 + * @param strIn 需加密的字符串 + * @return 加密后的字符串 + * @throws Exception + */ + public String encrypt(String strIn) throws Exception { + return byteArr2HexStr(encrypt(strIn.getBytes())); + } + + /** + * 解密字节数组 + * @param arrB 需解密的字节数组 + * @return 解密后的字节数组 + * @throws Exception + */ + public byte[] decrypt(byte[] arrB) throws Exception { + return decryptCipher.doFinal(arrB); + } + + /** + * 解密字符串 + * @param strIn 需解密的字符串 + * @return 解密后的字符串 + * @throws Exception + */ + public String decrypt(String strIn) throws Exception { + return new String(decrypt(hexStr2ByteArr(strIn))); + } + + /** + * 从指定字符串生成密钥,密钥所需的字节数组长度为8位 不足8位时后面补0,超出8位只取前8位 + * @param arrBTmp 构成该字符串的字节数组 + * @return 生成的密钥 + * @throws Exception + */ + private Key getKey(byte[] arrBTmp) throws Exception { + // 创建一个空的8位字节数组(默认值为0) + byte[] arrB = new byte[8]; + + // 将原始字节数组转换为8位 + for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) { + arrB[i] = arrBTmp[i]; + } + + // 生成密钥 + Key key = new javax.crypto.spec.SecretKeySpec(arrB, "DES"); + + return key; + } + + public static String SHA1(String decript) throws NoSuchAlgorithmException { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(decript.getBytes()); + byte messageDigest[] = digest.digest(); + // Create Hex String + StringBuffer hexString = new StringBuffer(); + // 字节数组转换为 十六进制 数 + for (int i = 0; i < messageDigest.length; i++) { + String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); + if (shaHex.length() < 2) { + hexString.append(0); + } + hexString.append(shaHex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return ""; + } + + public static void main(String [] args){ + DESUtil des; + try { + des = new DESUtil(); + String en= des.encrypt("fls123,12121"); + System.out.println(en); + String de = des.decrypt(en); + System.out.println(de); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/validation/MatcherSolveUtils.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/validation/MatcherSolveUtils.java new file mode 100644 index 00000000..c3de6c2e --- /dev/null +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/util/validation/MatcherSolveUtils.java @@ -0,0 +1,210 @@ +package com.zt.plat.framework.common.util.validation; + +import java.util.regex.Pattern; + +/** + * 正则校验工具 + * + * @author luzemin + **/ +public class MatcherSolveUtils { + + /** + * 正则校验正数、负数、和小数 + */ + private static final Pattern IS_NUM = Pattern.compile("^(\\-|\\+)?\\d+(\\.\\d+)?$"); + /** + * 正则校验大于0的数字 + */ + private static final Pattern IS_GT_0_NUM = Pattern.compile("(^[0-9]+\\.\\d+$)|(^[0-9]+$)"); + /** + * 正则校验大于等于0的数字 + */ + private static final Pattern IS_GE_EQ_0_NUM = Pattern.compile("(^[0-9]+\\.\\d+$)|(^[1-9]+\\d*$)"); + /** + * 正则校验字母或者数字组成的字符串 + */ + private static final Pattern EN_NUM = Pattern.compile("^[A-Za-z0-9]+$"); + /** + * 中文、英文、数字但不包括下划线等符号 + */ + private static final Pattern EN_CN_NUM = Pattern.compile("^[\\u4E00-\\u9FA5A-Za-z0-9]+$"); + /** + * Email地址 + */ + private static final Pattern EMAIL = Pattern.compile("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"); + /** + * 域名 + */ + private static final Pattern WWW = Pattern.compile("[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\\.?"); + /** + * InternetURL + */ + private static final Pattern URL = Pattern.compile("(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"); + /** + * 手机号码 + */ + private static final Pattern MOBILE_NUM = Pattern.compile("^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$"); + /** + * 车牌号 + */ + private static final Pattern TRUCK_NO = Pattern.compile("^([京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][ABCDEFGHJKLMNPQRSTUVWXY][1-9DF][1-9ABCDEFGHJKLMNPQRSTUVWXYZ]\\d{3}[1-9DF]|[京津晋冀蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新][ABCDEFGHJKLMNPQRSTUVWXY][\\dABCDEFGHJKLNMxPQRSTUVWXYZ]{5})$"); + + /** + * 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号) + */ + private static final Pattern PHONE_NUM = Pattern.compile("((\\d{11})|^((\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})|(\\d{4}|\\d{3})-(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1})|(\\d{7,8})-(\\d{4}|\\d{3}|\\d{2}|\\d{1}))$)"); + /** + * 身份证 + */ + private static final Pattern ID_CARD = Pattern.compile("(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)"); + /** + * 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) + */ + private static final Pattern ACCOUNT = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]{4,15}$"); + /** + * 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线) + */ + private static final Pattern PASSWORD = Pattern.compile("^[a-zA-Z]\\w{5,17}$"); + /** + * 强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-18之间) + */ + private static final Pattern PASSWORD_STRONG = Pattern.compile("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,18}$"); + + /** + * 正则校验正数、负数、和小数 + * + * @param str 待校验字符串 + */ + public static boolean checkIsNum(String str) { + return IS_NUM.matcher(str).matches(); + } + + /** + * 正则校验大于0的数字 + * + * @param str 待校验字符串t + */ + public static boolean checkIsGt0Num(String str) { + return IS_GT_0_NUM.matcher(str).matches(); + } + + /** + * 正则校验大于等于0的数字 + * + * @param str 待校验字符串 + */ + public static boolean checkIsGtEq0Num(String str) { + return IS_GE_EQ_0_NUM.matcher(str).matches(); + } + + /** + * 正则校验字母或者数字组成的字符串 + * + * @param str 待校验字符串 + */ + public static boolean checkEnNum(String str) { + return EN_NUM.matcher(str).matches(); + } + + /** + * 中文、英文、数字但不包括下划线等符号 + * + * @param str 待校验字符串 + */ + public static boolean checkEnCnNum(String str) { + return EN_CN_NUM.matcher(str).matches(); + } + + /** + * Email地址 + * + * @param str 待校验字符串 + */ + public static boolean checkEmail(String str) { + return EMAIL.matcher(str).matches(); + } + + /** + * 域名 + * + * @param str 待校验字符串 + */ + public static boolean checkWww(String str) { + return WWW.matcher(str).matches(); + } + + /** + * InternetURL + * + * @param str 待校验字符串 + */ + public static boolean checkInternetURL(String str) { + return URL.matcher(str).matches(); + } + + /** + * 手机号码 + * + * @param str 待校验字符串 + */ + public static boolean checkMobileNum(String str) { + return MOBILE_NUM.matcher(str).matches(); + } + + + /** + * 车牌号 + * + * @param str 待校验字符串 + * @return 校验通过-true,反之-false + */ + public static boolean checkTruckNo(String str) { + return TRUCK_NO.matcher(str).matches(); + } + + /** + * 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号) + * + * @param str 待校验字符串 + */ + public static boolean checkPhoneNum(String str) { + return PHONE_NUM.matcher(str).matches(); + } + + /** + * 身份证 + * + * @param str 待校验字符串 + */ + public static boolean checkIdCard(String str) { + return ID_CARD.matcher(str).matches(); + } + + /** + * 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线) + * + * @param str 待校验字符串 + */ + public static boolean checkAccount(String str) { + return ACCOUNT.matcher(str).matches(); + } + + /** + * 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线) + * + * @param str 待校验字符串 + */ + public static boolean checkPassword(String str) { + return PASSWORD.matcher(str).matches(); + } + + /** + * 强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-18之间) + * + * @param str 待校验字符串 + */ + public static boolean checkPasswordStrong(String str) { + return PASSWORD_STRONG.matcher(str).matches(); + } +} diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java index 8382799d..f399b742 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java @@ -11,7 +11,7 @@ import static com.zt.plat.framework.web.core.util.WebFrameworkUtils.HEADER_TENAN * * Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中 * - * @author ZT + * @author ZT TODO */ public class TenantRocketMQSendMessageHook implements SendMessageHook { diff --git a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/mapper/BaseMapperX.java b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/mapper/BaseMapperX.java index 701d322e..ccbf57ce 100644 --- a/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/mapper/BaseMapperX.java +++ b/zt-framework/zt-spring-boot-starter-mybatis/src/main/java/com/zt/plat/framework/mybatis/core/mapper/BaseMapperX.java @@ -48,7 +48,7 @@ public interface BaseMapperX extends MPJBaseMapper { PageSumSupport.tryAttachSummary(this, queryWrapper, pageResult); return pageResult; } - + //TODO 分页结果参考这个 =============================== // MyBatis Plus 查询 IPage mpPage = MyBatisUtils.buildPage(pageParam, sortingFields); selectPage(mpPage, queryWrapper); diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApi.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApi.java new file mode 100644 index 00000000..d701e989 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApi.java @@ -0,0 +1,72 @@ +package com.zt.plat.module.system.api.msg; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.system.api.sms.dto.log.SmsLogRespDTO; +import com.zt.plat.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import com.zt.plat.module.system.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 消息发送") +public interface MsgSendApi { + + String PREFIX = ApiConstants.PREFIX + "/msg/send"; + + @PostMapping(PREFIX + "/sendTextMsg") + @Operation(summary = "发送企业微信文本消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号") + CommonResult sendTextMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/sendImageMsg") + @Operation(summary = "发送企业微信图片消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号") + CommonResult sendImageMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @GetMapping(PREFIX + "/sendVoiceMsg") + @Operation(summary = "发送企业微信语音消息") + CommonResult getSmsLog(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/sendVideoMsg") + @Operation(summary = "发送企业微信视频消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号") + CommonResult sendVideoMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/sendFileMsg") + @Operation(summary = "发送企业微信文件消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号") + CommonResult sendFileMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @GetMapping(PREFIX + "/sendTextCardMsg") + @Operation(summary = "发送企业微信文本卡片消息") + CommonResult sendTextCardMsg(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/sendTextCardMsgPich01") + @Operation(summary = "发送企业微信文本卡片消息 -物资存货智能管理 预警信息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号") + CommonResult sendTextCardMsgPich01(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/sendNewsMsg") + @Operation(summary = "发送企业微信图文消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号") + CommonResult sendNewsMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @GetMapping(PREFIX + "/sendMpNewsMsg") + @Operation(summary = "发送企业微信图文消息(mpnews)") + CommonResult sendMpNewsMsg(@RequestParam("id") Long id); + + @PostMapping(PREFIX + "/sendMarkdownMsg") + @Operation(summary = "发送企业微信小程序通知消息", description = "在 mobile 为空时,使用 userId 加载对应 Admin 的手机号") + CommonResult sendMarkdownMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @PostMapping(PREFIX + "/sendMiniProgramNoticeMsg") + @Operation(summary = "发送企业微信图片消息", description = "在 mobile 为空时,使用 userId 加载对应 Member 的手机号") + CommonResult sendMiniProgramNoticeMsg(@Valid @RequestBody SmsSendSingleToUserReqDTO reqDTO); + + @GetMapping(PREFIX + "/sendInteractiveTaskCardMsg") + @Operation(summary = "发送企业微信任务卡片消息") + CommonResult sendInteractiveTaskCardMsg(@RequestParam("id") Long id); + + +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/code/User.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/code/User.java new file mode 100644 index 00000000..dfc3eea0 --- /dev/null +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/code/User.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.system.api.sms.dto.code; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +public class User implements Serializable { + /** + * 返回码 + */ + private Integer errcode; + /** + * 对返回码的文本描述内容 + */ + private String errmsg; + + /** + * 成员UserID + */ + private String UserId; + + /** + * 手机设备号(由中铝集团在安装时随机生成,删除重装会改变,升级不受影响) + */ + private String DeviceId; + + /** + * 成员身份信息,2:超级管理员, 4:分级管理员,5:普通成员 + */ + private Integer usertype; + + + /** + * 判断受否授权成功 + * + * @return true-授权成功、false-授权失败 + */ + public boolean isAuthorized() { + return this.getErrcode() == 0; + } +} diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java index 9e26df98..78c8a992 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java @@ -3,7 +3,6 @@ package com.zt.plat.module.system.api.sms.dto.send; import com.zt.plat.framework.common.validation.Mobile; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; - import jakarta.validation.constraints.NotEmpty; import java.util.Map; diff --git a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java index dea75c74..41f4e40e 100644 --- a/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java +++ b/zt-module-system/zt-module-system-api/src/main/java/com/zt/plat/module/system/enums/ErrorCodeConstants.java @@ -108,6 +108,9 @@ public interface ErrorCodeConstants { ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, "短信渠道不处于开启状态,不允许选择"); ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, "无法删除,该短信渠道还有短信模板"); ErrorCode SMS_CHANNEL_BALANCE_UNSUPPORTED = new ErrorCode(1_002_011_003, "该短信渠道不支持余额查询"); + ErrorCode MSG_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_004, "消息渠道不存在"); + + // ========== 短信模板 1-002-012-000 ========== ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, "短信模板不存在"); @@ -122,6 +125,7 @@ public interface ErrorCodeConstants { ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, "模板参数({})缺失"); ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, "短信模板不存在"); ErrorCode SMS_CALLBACK_SIGN_INVALID = new ErrorCode(1_002_013_100, "短信回调签名校验失败"); + ErrorCode MSG_CALLBACK_SIGN_INVALID = new ErrorCode(1_002_013_101, "消息回调签名校验失败"); // ========== 短信验证码 1-002-014-000 ========== ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); diff --git a/zt-module-system/zt-module-system-server/pom.xml b/zt-module-system/zt-module-system-server/pom.xml index 71cc87f0..0dfa4456 100644 --- a/zt-module-system/zt-module-system-server/pom.xml +++ b/zt-module-system/zt-module-system-server/pom.xml @@ -191,6 +191,13 @@ zt-spring-boot-starter-mq + + org.apache.commons + commons-text + 1.13.1 + compile + + diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApiImpl.java new file mode 100644 index 00000000..5ab314c8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/msg/MsgSendApiImpl.java @@ -0,0 +1,84 @@ +package com.zt.plat.module.system.api.msg; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.api.sms.dto.log.SmsLogRespDTO; +import com.zt.plat.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO; +import com.zt.plat.module.system.service.sms.SmsLogService; +import com.zt.plat.module.system.service.sms.SmsSendService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; +import static com.zt.plat.framework.common.pojo.CommonResult.success; + +@RestController +@Validated +public class MsgSendApiImpl implements MsgSendApi { + + @Resource + private SmsSendService smsSendService; + @Resource + private SmsLogService smsLogService; + + + @Override + public CommonResult sendTextMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendImageMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult getSmsLog(Long id) { + return success(BeanUtils.toBean(smsLogService.getSmsLog(id), SmsLogRespDTO.class)); + } + + @Override + public CommonResult sendVideoMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendFileMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendTextCardMsg(Long id) { + return null; + } + + @Override + public CommonResult sendTextCardMsgPich01(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendNewsMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendMpNewsMsg(Long id) { + return null; + } + + @Override + public CommonResult sendMarkdownMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendMiniProgramNoticeMsg(SmsSendSingleToUserReqDTO reqDTO) { + return null; + } + + @Override + public CommonResult sendInteractiveTaskCardMsg(Long id) { + return null; + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/MsgCallBackController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/MsgCallBackController.java new file mode 100644 index 00000000..6f1d0afb --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/MsgCallBackController.java @@ -0,0 +1,18 @@ +package com.zt.plat.module.system.controller.admin.sms; + +import com.zt.plat.module.system.service.sms.SmsSendService; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "管理后台 - 消息回调") +@RestController +@RequestMapping("/system/sms/callback") +public class MsgCallBackController { + + + @Resource + private SmsSendService smsSendService; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java index 6ec32e1d..c83a1c54 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsCallbackController.java @@ -8,11 +8,9 @@ import com.zt.plat.module.system.service.sms.SmsSendService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; - import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.servlet.http.HttpServletRequest; - import static com.zt.plat.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 短信回调") @@ -71,4 +69,13 @@ public class SmsCallbackController { return success(true); } + @PostMapping("/zle") + @PermitAll + @TenantIgnore + @Operation(summary = "中铝e办短信的回调") + public CommonResult receiveZleSmsStatus(@RequestBody String requestBody) throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.ZLE.getCode(), requestBody); + return success(true); + } + } \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsTemplateController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsTemplateController.java index a14d8776..e80ce844 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsTemplateController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/SmsTemplateController.java @@ -6,11 +6,10 @@ import com.zt.plat.framework.common.pojo.PageParam; import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.framework.excel.core.util.ExcelUtils; -import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO; -import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateRespVO; -import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO; -import com.zt.plat.module.system.controller.admin.sms.vo.template.SmsTemplateSendReqVO; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextMessage; +import com.zt.plat.module.system.controller.admin.sms.vo.template.*; import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; +import com.zt.plat.module.system.service.msg.ISendMsgService; import com.zt.plat.module.system.service.sms.SmsSendService; import com.zt.plat.module.system.service.sms.SmsTemplateService; import io.swagger.v3.oas.annotations.Operation; @@ -19,13 +18,13 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; +import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; - import java.io.IOException; import java.util.List; - import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.zt.plat.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; import static com.zt.plat.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 短信模板") @@ -37,6 +36,8 @@ public class SmsTemplateController { private SmsTemplateService smsTemplateService; @Resource private SmsSendService smsSendService; + @Resource + private ISendMsgService sendMsgService; @PostMapping("/create") @Operation(summary = "创建短信模板") @@ -100,4 +101,18 @@ public class SmsTemplateController { sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams())); } + @PostMapping("/send-msg") + @Operation(summary = "发送消息") + @PreAuthorize("@ss.hasPermission('system:sms-template:send-msg')") + public CommonResult sendMsg(@Valid @RequestBody MsgTemplateSendReqVO sendReqVO,TextMessage textMessage) throws Exception{ + + String msgtype = textMessage.getMsgtype(); + if (StringUtils.isBlank(msgtype)){ + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s",msgtype)); + } + //发送消息到MQ + CommonResult objectCommonResult = sendMsgService.sendTextMsg(sendReqVO,textMessage); + return success(objectCommonResult); + } + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/BaseMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/BaseMessage.java new file mode 100644 index 00000000..f77c17eb --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/BaseMessage.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import lombok.experimental.Accessors; +import java.io.Serializable; + +/** + * 企业微信消息发送对象父类 + * + * @author dongqiang.hao + */ +@Data +@Accessors(chain = true) +public class BaseMessage implements Serializable { + + /** + * 指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。特殊情况:指定为”@all”,则向该企业应用的全部成员发送 + */ + private String touser; + /** + * 指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为”@all”时忽略本参数 + */ + private String toparty; + /** + * 指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。当touser为”@all”时忽略本参数 + */ + private String totag; + /** + * 企业应用的id,整型。企业内部开发,可在应用的设置页面查看;第三方服务商,可通过接口 获取企业授权信息 获取该参数值 + */ + private String agentid; + /** + * 消息类型 + */ + private String msgtype; + /** + * 发送消息的自建应用类型 + */ + private String appType; + /** + * 表示是否开启重复消息检查,0表示否,1表示是,默认0 + */ + private Integer enable_duplicate_check = 0; + /** + * 表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时 + */ + private Integer duplicate_check_interval; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Btn.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Btn.java new file mode 100644 index 00000000..67e7a14f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Btn.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import java.io.Serializable; + +/** + * 任务卡片消息按钮对象 + * + * @author luzemin + */ +@Data +public class Btn implements Serializable { + + /** + * 按钮key值,用户点击后,会产生任务卡片回调事件,回调事件会带上该key值,只能由数字、字母和“_-@”组成,最长支持128字节 + */ + private String key; + + /** + * 按钮名称,最长支持18个字节,超过则截断 + */ + private String name; + + /** + * 按钮字体颜色,可选“red”或者“blue”,默认为“blue” + */ + private String color; + + /** + * 按钮字体是否加粗,默认false + */ + private Boolean is_bold; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ContentItem.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ContentItem.java new file mode 100644 index 00000000..0e024754 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ContentItem.java @@ -0,0 +1,22 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 小程序通知消息内容元素对象 + * + * @author luzemin + */ +@Data +public class ContentItem implements Serializable { + /** + * 元素对象键值,长度10个汉字以内 + */ + private String key; + /** + * 元素对象值,长度30个汉字以内(支持id转译) + */ + private String value; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/FileMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/FileMessage.java new file mode 100644 index 00000000..6c15d6f3 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/FileMessage.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信文件消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class FileMessage extends BaseMessage { + /** + * 消息类型,此时固定为:file + */ + private final String msgtype = WxMsgTypeConstant.FILE.getCode(); + + /** + * 企业微信文件消息体对象 + */ + private Media file = new Media(); + + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ + private Integer safe = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ImageMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ImageMessage.java new file mode 100644 index 00000000..4d9bde04 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/ImageMessage.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +/** + * 企业微信图片消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class ImageMessage extends BaseMessage { + /** + * 消息类型,此时固定为:image + */ + private final String msgtype = WxMsgTypeConstant.IMAGE.getCode(); + + /** + * 企业微信图片消息体对象 + */ + private Media image = new Media(); + + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ + private Integer safe = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCard.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCard.java new file mode 100644 index 00000000..f0775916 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCard.java @@ -0,0 +1,40 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +/** + * 企业微信任务卡片消息体对象 + * + * @author luzemin + */ +@Data +public class InteractiveTaskCard implements Serializable { + + /** + * 标题,不超过128个字节,超过会自动截断(支持id转译) + */ + private String title; + + /** + * 描述,不超过512个字节,超过会自动截断(支持id转译) + */ + private String description; + + /** + * 点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https) + */ + private String url; + + /** + * 任务id,同一个应用发送的任务卡片消息的任务id不能重复,只能由数字、字母和“_-@”组成,最长支持128字节 + */ + private String task_id; + + /** + * 按钮列表,按钮个数为1~2个。 + */ + private List btn = new ArrayList<>(); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCardMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCardMessage.java new file mode 100644 index 00000000..e796b462 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/InteractiveTaskCardMessage.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信任务卡片消息发送对象 + * 仅企业微信3.1.6及以上版本支持 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class InteractiveTaskCardMessage extends BaseMessage { + /** + * 消息类型,此时固定为:interactive_taskcard + */ + private final String msgtype = WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode(); + /** + * 任务卡片消息体对象, + */ + private InteractiveTaskCard interactive_taskcard = new InteractiveTaskCard(); + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Markdown.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Markdown.java new file mode 100644 index 00000000..3e50d6d8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Markdown.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import java.io.Serializable; +/** + * 企业微信markdown内容消息体对象 + * + * @author luzemin + */ +@Data +public class Markdown implements Serializable { + + /** + * markdown内容,最长不超过2048个字节,必须是utf8编码 + */ + private String content; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MarkdownMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MarkdownMessage.java new file mode 100644 index 00000000..d2890b2f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MarkdownMessage.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信markdown消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class MarkdownMessage extends BaseMessage { + /** + * 消息类型,此时固定为:markdown + */ + private final String msgtype = WxMsgTypeConstant.MARKDOWN.getCode(); + /** + * markdown消息内容对象, + */ + private Markdown markdown = new Markdown(); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Media.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Media.java new file mode 100644 index 00000000..d22b7cff --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Media.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import java.io.Serializable; +/** + * 企业微信媒体消息体对象 + * + * @author luzemin + */ +@Data +public class Media implements Serializable { + /** + * 文件上传时,企业微信服务器读取文件流的name + */ + public static final String MEDIA_NAME = "media"; + /** + * 图片、语音、视频媒体文件id,可以调用上传临时素材接口获取 + */ + private String media_id; + /** + * 视频消息的标题,不超过128个字节,超过会自动截断(视频消息) + */ + private String title; + /** + * 视频消息的描述,不超过512个字节,超过会自动截断(视频消息) + */ + private String description; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNotice.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNotice.java new file mode 100644 index 00000000..a627b7e4 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNotice.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import lombok.Data; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +/** + * 企业微信图文消息体对象 + * + * @author luzemin + */ +@Data +public class MiniProgramNotice implements Serializable { + /** + * 小程序appid,必须是与当前应用关联的小程序 + */ + private String appid; + + /** + * 点击消息卡片后的小程序页面,仅限本小程序内的页面。该字段不填则消息点击后不跳转。 + */ + private String page; + + /** + * 消息标题,长度限制4-12个汉字(支持id转译) + */ + private String title; + + /** + * 消息描述,长度限制4-12个汉字(支持id转译) + */ + private String description; + + /** + * 是否放大第一个content_item + */ + private String emphasis_first_item; + + /** + * 消息内容键值对,最多允许10个item + */ + private List content_item = new ArrayList<>(); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNoticeMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNoticeMessage.java new file mode 100644 index 00000000..16848703 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MiniProgramNoticeMessage.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信小程序通知消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class MiniProgramNoticeMessage extends BaseMessage { + /** + * 消息类型,此时固定为:miniprogram_notice + */ + private final String msgtype = WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode(); + /** + * 小程序通知消息内容对象, + */ + private MiniProgramNotice miniprogram_notice = new MiniProgramNotice(); + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MpNewsMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MpNewsMessage.java new file mode 100644 index 00000000..220e8e52 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/MpNewsMessage.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.client.dto.msg.MpNews; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信图文消息发送对象(文件存储在企业微信) + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class MpNewsMessage extends BaseMessage { + /** + * 消息类型,此时固定为:mpnews + */ + private final String msgtype = WxMsgTypeConstant.MPNEWS.getCode(); + + /** + * 企业微信图文消息体对象 + */ + private MpNews mpnews = new MpNews(); + + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ + private Integer safe = 0; + + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/NewsMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/NewsMessage.java new file mode 100644 index 00000000..3f68f848 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/NewsMessage.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.client.dto.msg.News; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信图文消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class NewsMessage extends BaseMessage { + /** + * 消息类型,此时固定为:news + */ + private final String msgtype = WxMsgTypeConstant.NEWS.getCode(); + + /** + * 企业微信图文消息体对象 + */ + private News news = new News(); + + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Text.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Text.java new file mode 100644 index 00000000..571c2d55 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/Text.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.util.StringSolveUtils; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +/** + * 企业微信文本消息体对象 + * + * @author luzemin + */ +@Data +public class Text implements Serializable { + + /** + * 点击事件消息模板 + */ + public static final String URL_CONTENT_TEMPLATE = "${content}"; + + /** + * 消息内容,最长不超过2048个字节,超过将截断(支持id转译) + */ + private String content; + + /** + * 消息点击url地址(自定义属性) + */ + private String url; + + /** + * 构建点击事件消息模板 + */ + public void buildUrlContent() { + if (StringUtils.isNotBlank(this.getUrl())) { + Map substituteMap = new HashMap<>(2); + substituteMap.put("url", this.getUrl()); + substituteMap.put("content", this.getContent()); + this.setContent(StringSolveUtils.placeholderReplace(URL_CONTENT_TEMPLATE, substituteMap)); + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextCardMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextCardMessage.java new file mode 100644 index 00000000..a4800545 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextCardMessage.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.client.dto.msg.TextCard; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信文本卡片消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class TextCardMessage extends BaseMessage { + /** + * 消息类型,此时固定为:textcard + */ + private final String msgtype = WxMsgTypeConstant.TEXTCARD.getCode(); + /** + * 消息内容对象 + */ + private TextCard textcard = new TextCard(); + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextMessage.java new file mode 100644 index 00000000..033a5cd0 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/TextMessage.java @@ -0,0 +1,33 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信文本消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class TextMessage extends BaseMessage { + /** + * 消息类型,此时固定为:text + */ + private final String msgtype = WxMsgTypeConstant.TEXT.getCode(); + /** + * 消息内容对象,其中text参数的content字段可以支持换行、以及A标签,即可打开自定义的网页(可参考以上示例代码)(注意:换行符请用转义过的\n) + */ + private Text text = new Text(); + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ + private Integer safe = 0; + /** + * 表示是否开启id转译,0表示否,1表示是,默认0。仅第三方应用需要用到,企业自建应用可以忽略。 + */ + private Integer enable_id_trans = 0; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VideoMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VideoMessage.java new file mode 100644 index 00000000..30c1d047 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VideoMessage.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信视频消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class VideoMessage extends BaseMessage { + /** + * 消息类型,此时固定为:video + */ + private final String msgtype = WxMsgTypeConstant.VIDEO.getCode(); + + /** + * 企业微信视频消息体对象 + */ + private Media video = new Media(); + + /** + * 表示是否是保密消息,0表示可对外分享,1表示不能分享且内容显示水印,默认为0 + */ + private Integer safe = 0; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VoiceMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VoiceMessage.java new file mode 100644 index 00000000..42d437eb --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/msg/VoiceMessage.java @@ -0,0 +1,25 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.msg; + +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +/** + * 企业微信语音消息发送对象 + * + * @author luzemin + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Accessors(chain = true) +public class VoiceMessage extends BaseMessage { + /** + * 消息类型,此时固定为:voice + */ + private final String msgtype = WxMsgTypeConstant.VOICE.getCode(); + + /** + * 企业微信语音消息体对象 + */ + private Media voice = new Media(); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/template/MsgTemplateSendReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/template/MsgTemplateSendReqVO.java new file mode 100644 index 00000000..48506c12 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/sms/vo/template/MsgTemplateSendReqVO.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.system.controller.admin.sms.vo.template; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import jakarta.validation.constraints.NotNull; +import java.util.Map; + +@Schema(description = "管理后台 - 短信模板的发送 Request VO") +@Data +public class MsgTemplateSendReqVO { + + /* @Schema(description = "手机号", requiredMode = Schema.RequiredMode.REQUIRED, example = "15601691300") + @NotNull(message = "手机号不能为空") + private String mobile;*/ + + @Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") + @NotNull(message = "模板编码不能为空") + private String templateCode; + + @Schema(description = "模板参数") + private Map templateParams; + + @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") + @NotNull(message = "用户ID不能为空") + private Long userId; + + @Schema(description = "用户属性", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01") + @NotNull(message = "用户属性不能为空") + private Integer userType; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgChannelDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgChannelDO.java new file mode 100644 index 00000000..15a49a61 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgChannelDO.java @@ -0,0 +1,69 @@ +package com.zt.plat.module.system.dal.dataobject.msg; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import com.zt.plat.module.system.framework.sms.core.enums.SmsChannelEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +/** + * 消息渠道 DO + * + * @author zzf + * @since 2021-01-25 + */ +@TableName(value = "system_sms_channel", autoResultMap = true) +@KeySequence("system_sms_channel_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@TenantIgnore +public class MsgChannelDO extends BaseDO { + + /** + * 渠道编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 短信签名 + */ + private String signature; + /** + * 企业编号(epid) + */ + private String epid; + /** + * 渠道编码 + * 枚举 {@link SmsChannelEnum} + */ + private String code; + /** + * 启用状态 + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + /** + * 短信 API 的账号 + */ + private String apiKey; + /** + * 短信 API 的密钥 + */ + private String apiSecret; + /** + * 短信发送回调 URL + */ + private String callbackUrl; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgTemplateDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgTemplateDO.java new file mode 100644 index 00000000..d36da63f --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/MsgTemplateDO.java @@ -0,0 +1,87 @@ +package com.zt.plat.module.system.dal.dataobject.msg; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.mybatis.core.dataobject.BaseDO; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import com.zt.plat.module.system.dal.dataobject.sms.SmsChannelDO; +import com.zt.plat.module.system.enums.sms.SmsTemplateTypeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.List; + +/** + * 消息模板 DO + * @author zzf + * @since 2021-01-25 + */ +@TableName(value = "system_sms_template", autoResultMap = true) +@KeySequence("system_sms_template_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@TenantIgnore +public class MsgTemplateDO extends BaseDO { + + /** + * 自增编号 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + // ========= 模板相关字段 ========= + + /** + * 短信类型 + * 枚举 {@link SmsTemplateTypeEnum} + */ + private Integer type; + /** + * 启用状态 + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 模板编码,保证唯一 + */ + private String code; + /** + * 模板名称 + */ + private String name; + /** + * 模板内容 + * 内容的参数,使用 {} 包括,例如说 {name} + */ + private String content; + /** + * 参数数组(自动根据内容生成) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private List params; + /** + * 备注 + */ + private String remark; + /** + * 短信 API 的模板编号 + */ + private String apiTemplateId; + + // ========= 渠道相关字段 ========= + + /** + * 短信渠道编号 + * 关联 {@link SmsChannelDO#getId()} + */ + private Long channelId; + /** + * 短信渠道编码 + * 冗余 {@link SmsChannelDO#getCode()} + */ + private String channelCode; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMq.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMq.java new file mode 100644 index 00000000..16f025ea --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMq.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.system.dal.dataobject.msg; + +import com.baomidou.mybatisplus.annotation.*; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import lombok.Data; +import lombok.ToString; +import java.io.Serializable; +/** + * Activemq消息队列 + * + * @author Dy + * @since 2021-07-22 + */ +@TableName(value = "sys_active_mq", autoResultMap = true) +@KeySequence("sys_active_mq_seq") +@Data +@ToString(callSuper = true) +@TenantIgnore +public class SysActiveMq implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "ID", type = IdType.ASSIGN_ID) + private String id; + + @TableField("TITLE") + private String title; + + @TableField("JMS_TYPE") + private String jmsType; + + @TableField("CONTENT_TYPE") + private String contentType; + + @TableField("DATETIME") + private String datetime; + + @TableField("CONSUME_DATETIME") + private String consumeDatetime; + + @TableField("PUBLISHER") + private String publisher; + + @TableField("LISTENER_CLASS_NAME") + private String listenerClassName; + + @TableField("CONSUME_FLAG") + private String consumeFlag; + + @TableField("CONSUME_MESSAGE") + private String consumeMessage; + + @TableField("TEXT_CONTENT") + private String textContent; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMqLog.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMqLog.java new file mode 100644 index 00000000..ae8709bd --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysActiveMqLog.java @@ -0,0 +1,56 @@ +package com.zt.plat.module.system.dal.dataobject.msg; + +import com.baomidou.mybatisplus.annotation.*; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import lombok.Data; +import lombok.ToString; +import java.io.Serializable; +/** + * Activemq消息队列 + * + * @author Dy + * @since 2021-07-22 + */ +@TableName(value = "sys_active_mq_log", autoResultMap = true) +@KeySequence("sys_active_mq_log_seq") +@Data +@ToString(callSuper = true) +@TenantIgnore +public class SysActiveMqLog implements Serializable { + + + @TableId(value = "ID", type = IdType.ASSIGN_ID) + private String id; + + @TableField("TITLE") + private String title; + + @TableField("JMS_TYPE") + private String jmsType; + + @TableField("CONTENT_TYPE") + private String contentType; + + @TableField("DATETIME") + private String datetime; + + @TableField("CONSUME_DATETIME") + private String consumeDatetime; + + @TableField("PUBLISHER") + private String publisher; + + @TableField("LISTENER_CLASS_NAME") + private String listenerClassName; + + @TableField("CONSUME_FLAG") + private String consumeFlag; + + @TableField("CONSUME_MESSAGE") + private String consumeMessage; + + @TableField("TEXT_CONTENT") + private String textContent; + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysWxAuditConfig.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysWxAuditConfig.java new file mode 100644 index 00000000..c8c8cbc7 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/msg/SysWxAuditConfig.java @@ -0,0 +1,89 @@ +package com.zt.plat.module.system.dal.dataobject.msg; + +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.aop.TenantIgnore; +import lombok.Data; +import lombok.ToString; +import java.io.Serializable; +/** + * 企业微信审核配置表 + * @author Dy + * @since 2021-08-26 + */ +@TableName(value = "sys_wx_audit_config", autoResultMap = true) +@KeySequence("sys_wx_audit_config_seq") +@Data +@ToString(callSuper = true) +@TenantIgnore +public class SysWxAuditConfig implements Serializable { + + /** + * 状态-生效 + */ + public static final String STATE_ACTIVE = "active"; + /** + * 状态-未生效 + */ + public static final String STATE_INACTIVE = "inactive"; + + private static final long serialVersionUID = 1L; + + + @TableField("APP_TYPE") + private String appType; + + @TableField("CORP_ID") + private String corpId; + + @TableField("CORP_SECRET") + private String corpSecret; + + @TableField("AGENT_ID") + private String agentId; + + @TableField("STATE") + private String state; + + @TableField("SORT_INDEX") + private Integer sortIndex; + + @TableField("EXT1") + private String ext1; + + @TableField("EXT2") + private String ext2; + + @TableField("EXT3") + private String ext3; + + @TableField("CREATE_USER") + private String createUser; + + @TableField("CREATE_USER_NAME") + private String createUserName; + + @TableField("ID") + private Integer id; + + @TableField("WX_CONTEXT_PATH") + private String wxContextPath; + + @TableField("HTTP_CONTEXT_PATH") + private String httpContextPath; + + @TableField("UPDATE_TIME") + private String updateTime; + + @TableField("CREATE_TIME") + private String createTime; + + @TableField("UPDATE_USER") + private String updateUser; + + @TableField("UPDATE_USER_NAME") + private String updateUserName; + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/msg/SysActiveMqDao.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/msg/SysActiveMqDao.java new file mode 100644 index 00000000..2c55a082 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/msg/SysActiveMqDao.java @@ -0,0 +1,12 @@ +package com.zt.plat.module.system.dal.mysql.msg; + +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.module.system.dal.dataobject.msg.SysActiveMqLog; +import com.zt.plat.module.system.dal.dataobject.sms.SmsLogDO; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SysActiveMqDao extends BaseMapperX { + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/Article.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/Article.java new file mode 100644 index 00000000..3ea59e07 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/Article.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.system.framework.sms.core.client.dto.msg; + +import lombok.Data; +import java.io.Serializable; +/** + * 企业微信图文消息体对象 + * + * @author ZT + */ +@Data +public class Article implements Serializable { + + /** + * 标题,不超过128个字节,超过会自动截断(支持id转译) + */ + private String title; + + /** + * 描述,不超过512个字节,超过会自动截断(支持id转译) + */ + private String description; + + /** + * 图文消息的图片链接,支持JPG、PNG格式,较好的效果为大图 1068*455,小图150*150。 + */ + private String url; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpArticle.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpArticle.java new file mode 100644 index 00000000..64d3bb07 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpArticle.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.system.framework.sms.core.client.dto.msg; + +import lombok.Data; +import java.io.Serializable; + +/** + * 企业微信图文消息体对象(文件存储在企业微信) + * + * @author luzemin + */ +@Data +public class MpArticle implements Serializable { + + /** + * 标题,不超过128个字节,超过会自动截断(支持id转译) + */ + private String title; + + /** + * 图文消息缩略图的media_id, 可以通过素材管理接口获得。此处thumb_media_id即上传接口返回的media_id + */ + private String thumb_media_id; + + /** + * 图文消息的作者,不超过64个字节 + */ + private String author; + + /** + * 图文消息点击“阅读原文”之后的页面链接 + */ + private String content_source_url; + + /** + * 图文消息的内容,支持html标签,不超过666 K个字节(支持id转译) + */ + private String content; + + /** + * 图文消息的描述,不超过512个字节,超过会自动截断(支持id转译) + */ + private String digest; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpNews.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpNews.java new file mode 100644 index 00000000..d669d5b6 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/MpNews.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.system.framework.sms.core.client.dto.msg; + +import lombok.Data; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 企业微信图文消息体对象(文件存储在企业微信) + * + * @author luzemin + */ +@Data +public class MpNews implements Serializable { + /** + * 图文消息,一个图文消息支持1到8条图文 + */ + private List articles = new ArrayList<>(); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/News.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/News.java new file mode 100644 index 00000000..33a2245e --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/News.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.system.framework.sms.core.client.dto.msg; + +import lombok.Data; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 企业微信图文消息体对象 + * + * @author luzemin + */ +@Data +public class News implements Serializable { + /** + * 图文消息,一个图文消息支持1到8条图文 + */ + private List
articles = new ArrayList<>(); +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/TextCard.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/TextCard.java new file mode 100644 index 00000000..ea57d152 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/dto/msg/TextCard.java @@ -0,0 +1,37 @@ +package com.zt.plat.module.system.framework.sms.core.client.dto.msg; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +/** + * 企业微信文本卡片消息体对象 + * + * @author luzemin + */ +@Data +@Accessors(chain = true) +public class TextCard implements Serializable { + + /** + * 标题,不超过128个字节,超过会自动截断(支持id转译) + */ + private String title; + + /** + * 描述,不超过512个字节,超过会自动截断(支持id转译) + */ + private String description; + + /** + * 消息点击点击后跳转的链接。最长2048字节,请确保包含了协议头(http/https) + */ + private String url; + + /** + * 按钮文字。 默认为“详情”, 不超过4个文字,超过自动截断。 + */ + private String btntxt; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java index c9482c18..b6f6d60e 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -7,7 +7,6 @@ import com.zt.plat.module.system.framework.sms.core.property.SmsChannelPropertie import lombok.extern.slf4j.Slf4j; import org.springframework.util.Assert; import org.springframework.validation.annotation.Validated; - import java.util.Arrays; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -82,6 +81,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { case HUAWEI: return new HuaweiSmsClient(properties); case QINIU: return new QiniuSmsClient(properties); case HL95: return new Hl95SmsClient(properties); + case ZLE: return new ZleSmsClient(properties); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/ZleSmsClient.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/ZleSmsClient.java new file mode 100644 index 00000000..b81728a7 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/client/impl/ZleSmsClient.java @@ -0,0 +1,171 @@ +package com.zt.plat.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.zt.plat.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.zt.plat.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import com.zt.plat.module.system.framework.sms.core.client.impl.extra.SmsBalanceClient; +import com.zt.plat.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import com.zt.plat.module.system.framework.sms.core.property.SmsChannelProperties; +import lombok.extern.slf4j.Slf4j; + +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class ZleSmsClient extends AbstractSmsClient implements SmsBalanceClient { + + + private static final String SEND_URL = "https://api.sms.95ytx.com:9091/mxt/send"; + private static final String BALANCE_URL = "https://api.sms.95ytx.com:9091/mxt/getfee"; + private static final DateTimeFormatter STATUS_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + public ZleSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "用户名(apiKey) 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "密码(apiSecret) 不能为空"); + } + + @Override + public SmsSendRespDTO sendSms(Long logId, String mobile, String content, String apiTemplateId, + List> templateParams) { + Assert.notEmpty(properties.getEpid(), "中铝e办需要配置 epid"); + Assert.notEmpty(properties.getSignature(), "短信签名不能为空"); + String finalContent = appendSignatureIfMissing(content, properties.getSignature()); + String linkId = buildLinkId(logId); + + Map form = new HashMap<>(); + form.put("username", properties.getApiKey()); + form.put("password", properties.getApiSecret()); + form.put("epid", properties.getEpid()); + form.put("phone", mobile); + form.put("message", finalContent); + form.put("linkid", linkId); + // subcode 可为空 + + String resp; + try (HttpResponse response = HttpRequest.post(SEND_URL) + .form(form) + .charset(StandardCharsets.UTF_8) + .execute()) { + resp = StrUtil.trim(response.body()); + } + + boolean success = StrUtil.equals(resp, "00"); + return new SmsSendRespDTO() + .setSuccess(success) + .setApiCode(resp) + .setApiMsg(resp) + .setApiRequestId(linkId) + .setSerialNo(linkId); + } + + /** + * 解析短信状态 + * @param text 结果 + * @return List + */ + @Override + public List parseSmsReceiveStatus(String text) { + JSONObject obj = JSONUtil.parseObj(text, false); + String reportCode = obj.getStr("FReportCode"); + String linkId = obj.getStr("FLinkID"); + LocalDateTime deliverTime = parseDeliverTime(obj.getStr("FDeliverTime")); + String mobile = obj.getStr("FDestAddr"); + boolean success = StrUtil.equalsIgnoreCase(reportCode, "DELIVRD") || StrUtil.equals(reportCode, "0"); + Long logId = parseLongSafely(linkId); + + SmsReceiveRespDTO dto = new SmsReceiveRespDTO() + .setSuccess(success) + .setErrorCode(reportCode) + .setErrorMsg(reportCode) + .setMobile(mobile) + .setReceiveTime(deliverTime) + .setSerialNo(linkId) + .setLogId(logId); + return Collections.singletonList(dto); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) { + // 中铝e办无模板审核接口,直接返回可用 + return new SmsTemplateRespDTO() + .setId(apiTemplateId) + .setContent(apiTemplateId) + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()); + } + + /** + * 查询余额 + * @return Integer + */ + @Override + public Integer queryBalance() { + Assert.notEmpty(properties.getEpid(), "中铝e办需要配置 epid"); + Map form = MapUtil.builder() + .put("username", properties.getApiKey()) + .put("password", properties.getApiSecret()) + .put("epid", properties.getEpid()) + .build(); + String resp; + try (HttpResponse response = HttpRequest.get(BALANCE_URL) + .form(form) + .charset(StandardCharsets.UTF_8) + .execute()) { + if (response.getStatus() != HttpURLConnection.HTTP_OK) { + throw new IllegalStateException("余额查询失败,HTTP 状态码:" + response.getStatus()); + } + resp = StrUtil.trim(response.body()); + } + if (!StrUtil.isNumeric(resp)) { + throw new IllegalStateException("余额查询失败,返回值:" + resp); + } + return Integer.valueOf(resp); + } + + private static String appendSignatureIfMissing(String content, String signature) { + if (StrUtil.isBlank(signature)) { + return content; + } + String wrapped = StrUtil.startWithAny(signature, "【", "[") ? signature : "【" + signature + "】"; + return StrUtil.startWith(content, wrapped) ? content : wrapped + content; + } + + private static String buildLinkId(Long logId) { + String raw = String.valueOf(logId); + return raw.length() > 20 ? raw.substring(raw.length() - 20) : raw; + } + + private static LocalDateTime parseDeliverTime(String timeText) { + if (StrUtil.isBlank(timeText)) { + return null; + } + try { + return LocalDateTime.parse(timeText, STATUS_TIME_FORMATTER); + } catch (Exception ex) { + log.warn("[parseDeliverTime][无法解析时间:{}]", timeText, ex); + return null; + } + } + + private static Long parseLongSafely(String text) { + try { + return Long.parseLong(text); + } catch (Exception ignore) { + return null; + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/AuditConstants.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/AuditConstants.java new file mode 100644 index 00000000..1c4f08e8 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/AuditConstants.java @@ -0,0 +1,155 @@ +package com.zt.plat.module.system.framework.sms.core.enums; + +import org.springframework.stereotype.Component; +import java.util.HashMap; +import java.util.Map; +/** + * 审核类型静态枚举类 + * + * @author luzemin + */ +@Component +public class AuditConstants { + + /** + * 中铝E办驰宏数字化应用(企业微信) + */ + public static final String CHINALCO_ZLEB_CHSZH = "chinalco_zleb_chszh"; + + /** + * 中铜阳光采购应用(企业微信) + */ + public static final String CHINALCO_SUNEPS = "chinalco_suneps"; + + + /** + * token身份认证令牌 + */ + public static final String TOKEN = "0123456789abcdefghijklmnopqrstuvwxyz"; + + /** + * 企业微信审核页面加载配置代码集ID + */ + public static final String AUDIT_CODE_SET = "qywx.audit_dispatcher"; + + /** + * 阳光采购企业微信审核回调地址集合映射 + */ + public static final Map REDIRECT_URL_MAPPING = new HashMap<>(1); + /** + * 企业微信审核回调地址-需要微信认证,使用时需要在前面添加微信请求uri + */ + public static final String REDIRECT_URL_AUTH = "/qywx/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}"; + /** + * 企业微信审核回调地址-不需要微信认证,使用时需要在前面添加微信请求uri + */ + public static final String REDIRECT_URL = "/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}"; + + static { + REDIRECT_URL_MAPPING.put("noAuth", REDIRECT_URL); + REDIRECT_URL_MAPPING.put("auth", REDIRECT_URL_AUTH); + } + + /** + * 审批类型 + */ + public enum SunepsAuditType { + + /** + * 询价单审批 + */ + REQUEST_AUDIT("requestAudit", "询价单审批"), + /** + * 会审会签审批 + */ + RESULT_AUDIT("resultAudit", "会审会签审批"), + /** + * 流标审批 + */ + FLOW_AUDIT("flowAudit", "流标审批"), + /** + * 谈判方案审批 + */ + NEGO_PLAN("negoPlan", "谈判方案审批"), + /** + * 终止审批 + */ + TERM_AUDIT("termAudit", "终止审批"), + /** + * 评标报告审批 + */ + REQUEST_EVA_REPORT("requestEvaReport", "评标报告审批"), + /** + * 供应商注册 + */ + SUPPLIER_REGISTER("supplierRegister", "供应商注册"), + /** + * 供应商自荐 + */ + SUPPLIER_SELF("supplierSelf", "供应商自荐"), + /** + * 供应商基本信息变更 + */ + SUPPLIER_BASE_INFO("supplierBaseInfo", "供应商基本信息变更"), + /** + * 供应商启用 + */ + SUPPLIER_ENABLE("supplierEnable", "供应商启用"), + /** + * 供应商可供大类变更 + */ + SUPPLIER_MATE_TYPE("supplierMateType", "供应商可供大类变更"), + /** + * 合格供应商变更 + */ + SUPPLIER_CHANGE("supplierChange", "合格供应商变更"), + /** + * 特种供应商认证审批 + */ + T_SUPP_AUDIT("TSuppAudit", "特种供应商认证审批"), + /** + * 审核消息卡片描述模型 + */ + AUDIT_DESCRIPTION_FORMAT("
${subTitle}
${description}
当前节点:${taskName}
点击查看详细情况
", "审核消息卡片描述模型"), + + /** + * 审核回调url,使用时需要在前面添加微信请求uri + */ + REDIRECT_URL("/qywx/${appType}/index?appType=${appType}&approveType=${approveType}&processInstanceId=${processInstanceId}&taskId=${taskId}&companyCode=${companyCode}&businessId=${businessId}", "审核回调url"); + + /** + * 消息全局键key-key + */ + public static final String CODE_KEY = "code"; + + /** + * 消息全局值value-key + */ + public static final String MSG_KEY = "msg"; + + /** + * 审核类型代码 + */ + private final String code; + /** + * 审核类型说明 + */ + private final String msg; + + SunepsAuditType(String code, String msg) { + this.code = code; + this.msg = msg; + + } + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } + + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/JmsConstant.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/JmsConstant.java new file mode 100644 index 00000000..1356fc75 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/JmsConstant.java @@ -0,0 +1,55 @@ +package com.zt.plat.module.system.framework.sms.core.enums; + +/** + * 消息ActiveMQ静态变量枚举 + * + * @author luzemin + */ +public enum JmsConstant { + /** + * 测试点对点消息队列,使用时需要添加环境前缀JmsProperties jmsProperties===>jmsProperties.getQueuePrefix() + */ + QUEUE_TEST("queue.test", "测试点对点消息队列"), + /** + * 测试主题消息队列,使用时需要添加环境前缀JmsProperties jmsProperties===>jmsProperties.getTopicPrefix() + */ + TOPIC_TEST("topic.test", "测试主题消息队列"), + /** + * 消息类型-队列 + */ + JMS_TYPE_QUEUE("queue", "消息类型-队列"), + /** + * 消息类型-主题 + */ + JMS_TYPE_TOPIC("topic", "消息类型-主题"), + /** + * 消息消费结果-成功 + */ + CONSUME_SUCCESS("success", "消息消费结果-成功"), + /** + * 消息消费结果-失败 + */ + CONSUME_FAILURE("failure", "消息消费结果-失败"); + + /** + * 值 + */ + private final String code; + /** + * 说明 + */ + private final String msg; + + JmsConstant(String code, String msg) { + this.code = code; + this.msg = msg; + } + + public String getCode() { + return code; + } + + public String getMsg() { + return msg; + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/SmsChannelEnum.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/SmsChannelEnum.java index aed000d4..9023a975 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/SmsChannelEnum.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/SmsChannelEnum.java @@ -20,6 +20,7 @@ public enum SmsChannelEnum { HUAWEI("HUAWEI", "华为云"), QINIU("QINIU", "七牛云"), HL95("HL95", "鸿联九五"), + ZLE("ZLE", "中铝e办"), ; /** diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/WxMsgTypeConstant.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/WxMsgTypeConstant.java new file mode 100644 index 00000000..cc1570ca --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/framework/sms/core/enums/WxMsgTypeConstant.java @@ -0,0 +1,94 @@ +package com.zt.plat.module.system.framework.sms.core.enums; + +/** + * 企业微信消息类型枚举类 + * + * @author luzemin + */ +public enum WxMsgTypeConstant { + /** + * 文本消息:其中text参数的content字段可以支持换行、以及A标签,即可打开自定义的网页(可参考以上示例代码)(注意:换行符请用转义过的\n) + */ + TEXT("text", "文本消息"), + /** + * 图片消息 + */ + IMAGE("image", "图片消息"), + /** + * 语音消息 + */ + VOICE("voice", "语音消息"), + /** + * 视频消息 + */ + VIDEO("video", "视频消息"), + /** + * 文件消息 + */ + FILE("file", "文件消息"), + /** + * 卡片消息的展现形式非常灵活,支持使用br标签或者空格来进行换行处理,也支持使用div标签来使用不同的字体颜色,目前内置了3种文字颜色:灰色(gray)、高亮(highlight)、默认黑色(normal),将其作为div标签的class属性即可,具体用法请参考上面的示例。 + */ + TEXTCARD("textcard", "文本卡片消息"), + /** + * 图文消息 + */ + NEWS("news", "图文消息"), + /** + * mpnews类型的图文消息,跟普通的图文消息一致,唯一的差异是图文内容存储在企业微信。多次发送mpnews,会被认为是不同的图文,阅读、点赞的统计会被分开计算。 + */ + MPNEWS("mpnews", "图文消息(mpnews)"), + /** + * 目前仅支持markdown语法的子集,微工作台(原企业号)不支持展示markdown消息 + */ + MARKDOWN("markdown", "markdown消息"), + /** + * 小程序通知消息 + * 小程序通知消息只允许绑定了小程序的应用发送,之前,消息会通过统一的会话【小程序通知】发送给用户。 + * 从2019年6月28日起,用户收到的小程序通知会出现在各个独立的应用中。 + * 不支持@all全员发送 + */ + MINIPROGRAM_NOTICE("miniprogram_notice", "小程序通知消息"), + /** + * 任务卡片消息 + * 仅企业微信3.1.6及以上版本支持 + * 任务卡片消息的展现支持简单的markdown语法,详情请见附录支持的markdown语法 。 + * 要发送该类型的消息,应用必须配置好回调URL,详见配置应用回调,用户点击任务卡片的按钮后,企业微信会回调任务卡片事件到该URL,配置的URL按任务卡片更新消息协议返回数据即可。 + * 开发者可以通过更新任务卡片消息状态接口更新卡片状态。 + */ + INTERACTIVE_TASKCARD("interactive_taskcard", "任务卡片消息"); + + /** + * 类型key + */ + private final String code; + /** + * 类型说明 + */ + private final String msg; + + /** + * 构造器 + * + * @param code 类型 + * @param msg 说明 + */ + WxMsgTypeConstant(String code, String msg) { + this.code = code; + this.msg = msg; + } + + /** + * 获取类型 + */ + public String getCode() { + return code; + } + + /** + * 获取说明 + */ + public String getMsg() { + return msg; + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/consumer/msg/MsgSendConsumer.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/consumer/msg/MsgSendConsumer.java new file mode 100644 index 00000000..91c0a700 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/consumer/msg/MsgSendConsumer.java @@ -0,0 +1,176 @@ +package com.zt.plat.module.system.mq.consumer.msg; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextCardMessage; +import com.zt.plat.module.system.framework.sms.core.client.dto.msg.TextCard; +import com.zt.plat.module.system.framework.sms.core.enums.AuditConstants; +import com.zt.plat.module.system.framework.sms.core.enums.JmsConstant; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import com.zt.plat.module.system.mq.message.sms.SmsSendMessage; +import com.zt.plat.module.system.service.msg.ISendWxMsgService; +import com.zt.plat.module.system.service.msg.ISysRocketMqService; +import com.zt.plat.module.system.util.StringSolveUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +/** + * 中铝 e 办的消费者 + * + * @author zzf + */ +@Component +@RocketMQMessageListener( + topic = SmsSendMessage.TOPIC, + consumerGroup = SmsSendMessage.TOPIC + "_CONSUMER" +) +@Slf4j +public class MsgSendConsumer implements RocketMQListener { + + + @Resource + private ISysRocketMqService sysActiveMqService; + + @Resource + private ISendWxMsgService sendWxMsgService; + + + @Override + public void onMessage(MessageExt msg) { + + log.info("中铝e办[onMessage][消息内容({})]", msg.toString()); + String msgId = msg.getMsgId(); + int reconsumeTimes = msg.getReconsumeTimes(); + String consumeMsg; + try { + + if (msg.getBody() == null || msg.getBody().length == 0){ + log.error("[onMessage][消息体为空 msgId={}]", msgId); + return; + } + + String json = new String(msg.getBody(), StandardCharsets.UTF_8); + JSONObject jsonObject = JSON.parseObject(json); + String toUser = jsonObject.getString("touser"); // 企业微信接收者 + String userName = jsonObject.getString("userName"); // 用户名 + String userEname = jsonObject.getString("userEname"); // 英文名 + String userCname = jsonObject.getString("userCname"); // 中文名 + String desc = jsonObject.getString("description"); + // 校验接受用户 + if (StringUtils.isBlank(toUser)) { + consumeMsg = "企业微信消息发送失败,用户【" + userEname + "-" + userCname + "】未绑定企业微信账号!"; + //TODO 添加文本消息 + sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), consumeMsg); + } else { + String msgtype = jsonObject.getString("msgtype"); + // 文本卡片消息 + if (Objects.equals(msgtype, WxMsgTypeConstant.TEXTCARD.getCode())) { + /* 构建审批回调url */ + String appType = jsonObject.getString("appType"); + String approveType = jsonObject.getString("approveType"); + String companyCode = jsonObject.getString("userCompanyCode"); + String taskId = jsonObject.getString("taskId"); + String processInstanceId = jsonObject.getString("processInstanceId"); + String businessId = jsonObject.getString("businessId"); + Map substituteMap = new HashMap<>(5); + substituteMap.put("appType", appType.toLowerCase(Locale.ROOT)); + substituteMap.put("approveType", approveType); + substituteMap.put("companyCode", companyCode); + substituteMap.put("taskId", taskId); + substituteMap.put("processInstanceId", processInstanceId); + substituteMap.put("businessId", businessId); + String redirectUrl = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.REDIRECT_URL.getCode(), substituteMap); + + /* 构建文本卡片消息 */ + String title = jsonObject.getString("title"); + String taskName = jsonObject.getString("taskName"); + String datetime = jsonObject.getString("datetime"); + String btnTxt = jsonObject.getString("btntxt"); + substituteMap.put("subTitle", datetime + "——" + userName); + substituteMap.put("description", desc); + substituteMap.put("taskName", taskName); + String description = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.AUDIT_DESCRIPTION_FORMAT.getCode(), substituteMap); + TextCardMessage textCardMessage = new TextCardMessage(); + TextCard textCard = new TextCard(); + textCard.setTitle(title) + .setDescription(description) + .setUrl(redirectUrl); + if (StringUtils.isNotBlank(btnTxt)) { + textCard.setBtntxt(btnTxt); + } + textCardMessage.setTextcard(textCard).setAppType(appType).setTouser(toUser); + //TODO 发送企业微信文本卡片消息 + sendWxMsgService.sendTextCardMsg(textCardMessage); + consumeMsg = "企业微信消息发送成功,接收用户【" + userEname + "-" + msgtype + "】!"; + //TODO 保存文本卡片消息队列消费的消息信息 + sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_SUCCESS.getCode(), consumeMsg); + } + + // 文本消息 + if (Objects.equals(msgtype, WxMsgTypeConstant.FILE.getCode())) { + /* 构建审批回调url */ + String appType = jsonObject.getString("appType"); + String approveType = jsonObject.getString("approveType"); + String companyCode = jsonObject.getString("userCompanyCode"); + String taskId = jsonObject.getString("taskId"); + String processInstanceId = jsonObject.getString("processInstanceId"); + String businessId = jsonObject.getString("businessId"); + Map substituteMap = new HashMap<>(5); + substituteMap.put("appType", appType.toLowerCase(Locale.ROOT)); + substituteMap.put("approveType", approveType); + substituteMap.put("companyCode", companyCode); + substituteMap.put("taskId", taskId); + substituteMap.put("processInstanceId", processInstanceId); + substituteMap.put("businessId", businessId); + String redirectUrl = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.REDIRECT_URL.getCode(), substituteMap); + + /* 构建文本消息 */ + String title = jsonObject.getString("title"); + String taskName = jsonObject.getString("taskName"); + String datetime = jsonObject.getString("datetime"); + String btnTxt = jsonObject.getString("btntxt"); + substituteMap.put("subTitle", datetime + "——" + userName); + substituteMap.put("description", desc); + substituteMap.put("taskName", taskName); + String description = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.AUDIT_DESCRIPTION_FORMAT.getCode(), substituteMap); + TextCardMessage textCardMessage = new TextCardMessage(); + TextCard textCard = new TextCard(); + textCard.setTitle(title) + .setDescription(description) + .setUrl(redirectUrl); + if (StringUtils.isNotBlank(btnTxt)) { + textCard.setBtntxt(btnTxt); + } + textCardMessage.setTextcard(textCard).setAppType(appType).setTouser(toUser); + //TODO 送企业微信文本卡片消息 + sendWxMsgService.sendTextCardMsg(textCardMessage); + consumeMsg = "企业微信消息发送成功,接收用户【" + userEname + "-" + msgtype + "】!"; + //TODO 存文本卡片消息队列消费的消息信息 + sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_SUCCESS.getCode(), consumeMsg); + } + + + } + } catch (Exception e) { + log.error("消息发送失败!{}", e.getMessage(), e); + try { + // 重试时不重复保存队列消息 + if (reconsumeTimes>0) { + sysActiveMqService.saveByTextMessage(this.getClass().getName(), msg, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), e.getMessage()); + } + } catch (Exception jmsException) { + log.error("消息保存失败!{}", jmsException.getMessage(), jmsException); + } + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/msg/MsgSendMessage.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/msg/MsgSendMessage.java new file mode 100644 index 00000000..527783a4 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/message/msg/MsgSendMessage.java @@ -0,0 +1,43 @@ +package com.zt.plat.module.system.mq.message.msg; + +import com.zt.plat.framework.common.core.KeyValue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 消息发送消息 + * + * @author ZT + */ +@Data +public class MsgSendMessage { + + public static final String TOPIC = "SMS_SEND_TOPIC"; + /** + * 消息日志编号 + */ + @NotNull(message = "短信日志编号不能为空") + private Long logId; + + /** + * 消息内容(已按模板格式化后的文本) + */ + private String content; + /** + * 消息渠道编号 + */ + @NotNull(message = "短信渠道编号不能为空") + private Long channelId; + /** + * 消息 API 的模板编号 + */ + @NotNull(message = "消息 API 的模板编号不能为空") + private String apiTemplateId; + /** + * 短信模板参数 + */ + private List> templateParams; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/msg/MsgProducer.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/msg/MsgProducer.java new file mode 100644 index 00000000..0e48e96e --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/mq/producer/msg/MsgProducer.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.system.mq.producer.msg; + +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.module.system.mq.message.sms.SmsSendMessage; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.stereotype.Component; +import jakarta.annotation.Resource; +import java.util.List; + +/** + * Sms 短信相关消息的 Producer + * + * @author zzf + * @since 2021/3/9 16:35 + */ +@Slf4j +@Component +public class MsgProducer { + + @Resource + private RocketMQTemplate rocketMQTemplate; + + /** + * 发送 {@link SmsSendMessage} 消息 + * @param logId 短信日志编号 + * @param channelId 渠道编号 + * @param apiTemplateId 短信模板编号 + * @param templateParams 短信模板参数 + */ + public void sendMsg(Long logId, String content, + Long channelId, String apiTemplateId, List> templateParams) { + SmsSendMessage message = new SmsSendMessage().setLogId(logId).setContent(content); + message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams); + rocketMQTemplate.syncSend(SmsSendMessage.TOPIC, message); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMonitorMsgService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMonitorMsgService.java new file mode 100644 index 00000000..e9caf12e --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMonitorMsgService.java @@ -0,0 +1,68 @@ +package com.zt.plat.module.system.service.msg; + +import com.zt.plat.module.system.mq.message.sms.SmsSendMessage; +import java.util.List; +import java.util.Map; +/** + * 监听消息 Service 接口 + * @author ZT + */ +public interface IMonitorMsgService { + + /** + * 发送单条短信给管理后台的用户 + * 在 mobile 为空时,使用 userId 加载对应管理员的手机号 + * @param mobile 手机号 + * @param userId 用户编号 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSmsToAdmin(String mobile, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条短信给用户 APP 的用户 + * 在 mobile 为空时,使用 userId 加载对应会员的手机号 + * @param mobile 手机号 + * @param userId 用户编号 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSmsToMember(String mobile, Long userId, + String templateCode, Map templateParams); + + /** + * 发送单条短信给用户 + * @param mobile 手机号 + * @param userId 用户编号 + * @param userType 用户类型 + * @param templateCode 短信模板编号 + * @param templateParams 短信模板参数 + * @return 发送日志编号 + */ + Long sendSingleSms(String mobile, Long userId, Integer userType, + String templateCode, Map templateParams); + + default void sendBatchSms(List mobiles, List userIds, Integer userType, + String templateCode, Map templateParams) { + throw new UnsupportedOperationException("暂时不支持该操作,感兴趣可以实现该功能哟!"); + } + + /** + * 执行真正的短信发送 + * 注意,该方法仅仅提供给 MQ Consumer 使用 + * @param message 短信 + */ + void doSendSms(SmsSendMessage message); + + /** + * 接收短信的接收结果 + * @param channelCode 渠道编码 + * @param text 结果内容 + * @throws Throwable 处理失败时,抛出异常 + */ + void receiveSmsStatus(String channelCode, String text) throws Throwable; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMsgLogService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMsgLogService.java new file mode 100644 index 00000000..09bfffba --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/IMsgLogService.java @@ -0,0 +1,88 @@ +package com.zt.plat.module.system.service.msg; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextMessage; +import com.zt.plat.module.system.dal.dataobject.sms.SmsLogDO; +import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; +import java.time.LocalDateTime; +import java.util.Map; +/** + * 消息日志 Service 接口 + * + * @author zzf + */ +public interface IMsgLogService { + + /** + * 创建短信日志 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param isSend 是否发送 + * @param template 短信模板 + * @param templateContent 短信内容 + * @param templateParams 短信参数 + * @return 发送日志编号 + */ + Long createSmsLog(Long userId, Integer userType, Boolean isSend, + SmsTemplateDO template, String templateContent, Map templateParams); + + /** + * 更新日志的发送结果 + * + * @param id 日志编号 + * @param success 发送是否成功 + * @param apiSendCode 短信 API 发送结果的编码 + * @param apiSendMsg 短信 API 发送失败的提示 + * @param apiRequestId 短信 API 发送返回的唯一请求 ID + * @param apiSerialNo 短信 API 发送返回的序号 + */ + void updateSmsSendResult(Long id, Boolean success, + String apiSendCode, String apiSendMsg, + String apiRequestId, String apiSerialNo); + + /** + * 更新日志的接收结果 + * + * @param id 日志编号 + * @param success 是否接收成功 + * @param receiveTime 用户接收时间 + * @param apiReceiveCode API 接收结果的编码 + * @param apiReceiveMsg API 接收结果的说明 + */ + void updateSmsReceiveResult(Long id, Boolean success, + LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg); + + /** + * 获得短信日志分页 + * + * @param pageReqVO 分页查询 + * @return 短信日志分页 + */ + PageResult getSmsLogPage(SmsLogPageReqVO pageReqVO); + + /** + * 根据日志编号查询短信日志 + * + * @param id 日志编号 + * @return 短信日志 + */ + SmsLogDO getSmsLog(Long id); + + /** + * 创建消息日志 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @param isSend 是否发送 + * @param template 短信模板 + * @param textMessage 短信内容 + * @param templateParams 短信参数 + * @return 创建消息日志编号 + */ + Long createMsgLog(Long userId, Integer userType, Boolean isSend, SmsTemplateDO template, TextMessage textMessage, Map templateParams); + + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendMsgService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendMsgService.java new file mode 100644 index 00000000..c6c5af36 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendMsgService.java @@ -0,0 +1,125 @@ +package com.zt.plat.module.system.service.msg; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.*; +import com.zt.plat.module.system.controller.admin.sms.vo.template.MsgTemplateSendReqVO; +import java.util.Map; + +/** + * 生产者发送消息接口类 + * + * @author dongqiang.hao + */ +public interface ISendMsgService { + + /** + * 发送文本消息 + * + * @param textMessage 文本消息包装对象 + * @return r 响应对象 + */ + CommonResult sendTextMsg(MsgTemplateSendReqVO sendReqVO, TextMessage textMessage) throws Exception; + + /** + * 发送企业微信图片消息 + * + * @param imageMessage 图片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendImageMsg(Long userId, Integer userType, + String templateCode, Map templateParams,ImageMessage imageMessage)throws Exception; + + + /** + * 发送企业微信语音消息 + * + * @param voiceMessage 语音消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendVoiceMsg(Long userId, Integer userType, + String templateCode, Map templateParams,VoiceMessage voiceMessage)throws Exception; + + /** + * 发送企业微信视频消息 + * + * @param videoMessage 视频消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendVideoMsg(Long userId, Integer userType, + String templateCode, Map templateParams,VideoMessage videoMessage)throws Exception; + + + /** + * 发送企业微信文件消息 + * + * @param fileMessage 文件消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendFileMsg(Long userId, Integer userType, + String templateCode, Map templateParams,FileMessage fileMessage)throws Exception; + + + /** + * 发送企业微信文本卡片消息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendTextCardMsgPich01(Long userId, Integer userType, + String templateCode, Map templateParams,TextCardMessage textCardMessage) throws Exception; + + /** + * 发送企业微信文本卡片消息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendTextCardMsg(Long userId, Integer userType, + String templateCode, Map templateParams,TextCardMessage textCardMessage)throws Exception; + + /** + * 发送企业微信图文消息 + * + * @param newsMessage 图文消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendNewsMsg(Long userId, Integer userType, + String templateCode, Map templateParams,NewsMessage newsMessage)throws Exception; + + /** + * 发送企业微信图文消息(mpnews) + * + * @param mpNewsMessage 图文消息(mpnews)对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMpNewsMsg(Long userId, Integer userType, + String templateCode, Map templateParams,MpNewsMessage mpNewsMessage)throws Exception; + + + /** + * 发送企业微信markdown消息 + * + * @param markdownMessage markdown消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMarkdownMsg(Long userId, Integer userType, + String templateCode, Map templateParams,MarkdownMessage markdownMessage) throws Exception; + + /** + * 发送企业微信小程序通知消息 + * + * @param miniProgramNoticeMessage 小程序通知消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMiniProgramNoticeMsg(Long userId, Integer userType, + String templateCode, Map templateParams,MiniProgramNoticeMessage miniProgramNoticeMessage)throws Exception; + + /** + * 发送企业微信任务卡片消息 + * + * @param interactiveTaskCardMessage 任务卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendInteractiveTaskCardMsg(Long userId, Integer userType, + String templateCode, Map templateParams,InteractiveTaskCardMessage interactiveTaskCardMessage)throws Exception; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendWxMsgService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendWxMsgService.java new file mode 100644 index 00000000..6637d094 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISendWxMsgService.java @@ -0,0 +1,108 @@ +package com.zt.plat.module.system.service.msg; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.*; + +/** + * TODO ============================== 企微消息发送接口类 ========================================= + * + * @author luzemin + */ +public interface ISendWxMsgService { + + /** + * 发送企业微信文本消息 + * + * @param textMessage 文本消息包装对象 + * @return r 响应对象 + */ + CommonResult sendTextMsg(TextMessage textMessage)throws Exception; + + /** + * 发送企业微信图片消息 + * + * @param imageMessage 图片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendImageMsg(ImageMessage imageMessage)throws Exception; + + /** + * 发送企业微信语音消息 + * + * @param voiceMessage 语音消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendVoiceMsg(VoiceMessage voiceMessage)throws Exception; + + /** + * 发送企业微信视频消息 + * + * @param videoMessage 视频消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendVideoMsg(VideoMessage videoMessage)throws Exception; + + /** + * 发送企业微信文件消息 + * + * @param fileMessage 文件消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendFileMsg(FileMessage fileMessage)throws Exception; + + /** + * 发送企业微信文本卡片消息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendTextCardMsg(TextCardMessage textCardMessage)throws Exception; + + /** + * 发送企业微信文本卡片消息 -物资存货智能管理 预警信息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendTextCardMsgPich01(TextCardMessage textCardMessage)throws Exception; + + /** + * 发送企业微信图文消息 + * + * @param newsMessage 图文消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendNewsMsg(NewsMessage newsMessage)throws Exception; + + /** + * 发送企业微信图文消息(mpnews) + * + * @param mpNewsMessage 图文消息(mpnews)对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMpNewsMsg(MpNewsMessage mpNewsMessage)throws Exception; + + /** + * 发送企业微信markdown消息 + * + * @param markdownMessage markdown消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMarkdownMsg(MarkdownMessage markdownMessage)throws Exception; + + /** + * 发送企业微信小程序通知消息 + * + * @param miniProgramNoticeMessage 小程序通知消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendMiniProgramNoticeMsg(MiniProgramNoticeMessage miniProgramNoticeMessage) throws Exception; + + /** + * 发送企业微信任务卡片消息 + * + * @param interactiveTaskCardMessage 任务卡片消息对象 + * @return R 消息发送响应对象 + */ + CommonResult sendInteractiveTaskCardMsg(InteractiveTaskCardMessage interactiveTaskCardMessage) throws Exception; +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISysRocketMqService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISysRocketMqService.java new file mode 100644 index 00000000..a46b8fb6 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/ISysRocketMqService.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.system.service.msg; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zt.plat.module.system.dal.dataobject.msg.SysActiveMqLog; +import org.apache.rocketmq.common.message.MessageExt; +/** + * RocketMq消息队列 服务类 + * @author dongqiang.hao + * @since 2025-01-07 + */ +public interface ISysRocketMqService extends IService { + + /** + * 保存消息队列消费的消息信息 + * + * @param listenerClassName 队列监听类名 + * @param tm 消息对象 + * @param jmsType 消息类型(QUEUE-队列/TOPIC-主题) + * @throws Exception 消息消费异常 + */ + void saveByTextMessage(String listenerClassName, MessageExt tm, String jmsType) throws Exception; + + /** + * 保存文本卡片消息队列消费的消息信息 + * + * @param listenerClassName 队列监听类名 + * @param tm 消息对象 + * @param jmsType 消息类型(QUEUE-队列/TOPIC-主题) + * @param consumeFlag 消费结果(success-成功/failure-失败) + * @param consumeMessage 消费失败消息 + * @throws Exception 消息消费异常 + */ + void saveByTextMessage(String listenerClassName, MessageExt tm, String jmsType, String consumeFlag, String consumeMessage) throws Exception; + + /** + * 保存阳光采购消息队列消费的消息信息 + * + * @param listenerClassName 队列监听类名 + * @param tm 消息对象 + * @param jmsType 消息类型(QUEUE-队列/TOPIC-主题) + * @param consumeFlag 消费结果(success-成功/failure-失败) + * @param consumeMessage 消费失败消息 + * @throws Exception 消息消费异常 + */ + void saveSunepsByTextMessage(String listenerClassName, MessageExt tm, String jmsType, String consumeFlag, String consumeMessage) throws Exception; + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/SysWxAuditConfigService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/SysWxAuditConfigService.java new file mode 100644 index 00000000..9dff7542 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/SysWxAuditConfigService.java @@ -0,0 +1,14 @@ +package com.zt.plat.module.system.service.msg; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zt.plat.module.system.dal.dataobject.msg.SysWxAuditConfig; + +/** + * 企业微信审核配置表 服务类 + * + * @author Dy + * @since 2021-08-26 + */ +public interface SysWxAuditConfigService extends IService { + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/HttpUrlConfig.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/HttpUrlConfig.java new file mode 100644 index 00000000..fa1c2aaa --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/HttpUrlConfig.java @@ -0,0 +1,296 @@ +package com.zt.plat.module.system.service.msg.config; + +import jakarta.annotation.PostConstruct; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * 系统参数配置组件 + * TODO + * @author luzemin + */ +@Component +@PropertySource("classpath:config.properties") +public class HttpUrlConfig { + + /** + * 企业微信审核涉及的app + */ + public static final String[] APP_TYPES = new String[]{"suneps", "pscs", "market", "bssim", "dzxs", "boli", "lmps"}; + + /** + * 企业微信审核http请求url——提交审核 + */ + public static final String COMMIT_URL = "commitUrl"; + /** + * 企业微信审核http请求url——获取审核历史信息 + */ + public static final String COMMENT_URL = "commentUrl"; + /** + * 企业微信审核http请求url——获取用户待办 + */ + public static final String TODO_URL = "todoUrl"; + /** + * 企业微信审核http请求url——获取用户已办 + */ + public static final String DONE_URL = "doneUrl"; + /** + * 企业微信审核http请求url——获取流程是否结束信息 + */ + public static final String FINISH_URL = "finishUrl"; + /** + * 企业微信审核http请求url——文件存储 + */ + public static final String STORAGE_URL = "storageUrl"; + /** + * 获取合同文本内容请求url + */ + public static final String RENDERBPOCONTENT_URL = "renderBpoContentUrl"; + /** + * 获字化采购报表地址 + */ + private static final String FINE_REPORT_URL = "fineReportUrl"; + /** + * 获数字化附件管理地址 + */ + private static final String FILE_STORAGE_URL = "fileStorageUrl"; + + /** + * 获数字化附件预览 + */ + private static final String FILE_VIEW_URL = "fileViewUrl"; + + /** + * 应用http调用地址 + */ + private static final String HTTP_CONTEXT_PATH = "httpContextPath"; + + /** + * 微信审核应用地址 + */ + private static final String WX_CONTEXT_PATH = "wxContextPath"; + + /** + * 微信审核应用地址 + */ + private static final String WX_SUBMIT_DATA = "wxSubmitData"; + + + /** + * 企业微信审核http请求url——提交审核应用和url映射集合 + */ + private static final Map COMMIT_URL_MAPPING = new HashMap<>(); + /** + * 企业微信审核http请求url——获取审核历史信息应用和url映射集合 + */ + private static final Map COMMENT_URL_MAPPING = new HashMap<>(); + /** + * 企业微信审核http请求url——获取用户待办应用和url映射集合 + */ + private static final Map TODO_URL_MAPPING = new HashMap<>(); + /** + * 企业微信审核http请求url——获取用户已办应用和url映射集合 + */ + private static final Map DONE_URL_MAPPING = new HashMap<>(); + /** + * 企业微信审核http请求url——文件存储应用和url映射集合 + */ + private static final Map STORAGE_URL_MAPPING = new HashMap<>(); + /** + * 企业微信审核http请求url——文件存储应用和url映射集合 + */ + private static final Map FINISH_URL_MAPPING = new HashMap<>(); + + /** + * 获取合同文本内容请求url + */ + private static final Map RENDERBPOCONTENT_URL_MAPPING = new HashMap<>(); + + /** + * 获取合同文本内容请求url + */ + private static final Map FINE_REPORT_URL_MAPPING = new HashMap<>(); + /** + * 获取合同文本内容请求url + */ + private static final Map FILE_VIEW_URL_MAPPING = new HashMap<>(); + /** + * 获取合同文本内容请求url + */ + private static final Map FILE_STORAGE_URL_MAPPING = new HashMap<>(); + + /** + * 获取应用http调用地址 + */ + private static final Map HTTP_CONTEXT_PATH_MAPPING = new HashMap<>(); + + /** + * 微信审核应用地址 + */ + private static final Map WX_CONTEXT_PATH_MAPPING = new HashMap<>(); + + /** + * 微信审核应用地址 + */ + private static final Map WX_SUBMIT_DATA_MAPPING = new HashMap<>(); + + + @PostConstruct + public void initConfig() throws IOException { + Properties properties = new Properties(); + InputStream configInputStream = this.getClass().getClassLoader().getResourceAsStream("config.properties"); + properties.load(configInputStream); + Arrays.stream(APP_TYPES).forEach(appType -> { + COMMIT_URL_MAPPING.put(appType.concat(".").concat(COMMIT_URL), properties.getProperty(appType.concat(".").concat(COMMIT_URL))); + COMMENT_URL_MAPPING.put(appType.concat(".").concat(COMMENT_URL), properties.getProperty(appType.concat(".").concat(COMMENT_URL))); + TODO_URL_MAPPING.put(appType.concat(".").concat(TODO_URL), properties.getProperty(appType.concat(".").concat(TODO_URL))); + DONE_URL_MAPPING.put(appType.concat(".").concat(DONE_URL), properties.getProperty(appType.concat(".").concat(DONE_URL))); + STORAGE_URL_MAPPING.put(appType.concat(".").concat(STORAGE_URL), properties.getProperty(appType.concat(".").concat(STORAGE_URL))); + FINISH_URL_MAPPING.put(appType.concat(".").concat(FINISH_URL), properties.getProperty(appType.concat(".").concat(FINISH_URL))); + RENDERBPOCONTENT_URL_MAPPING.put(appType.concat(".").concat(RENDERBPOCONTENT_URL), properties.getProperty(appType.concat(".").concat(RENDERBPOCONTENT_URL))); + FINE_REPORT_URL_MAPPING.put(appType.concat(".").concat(FINE_REPORT_URL), properties.getProperty(appType.concat(".").concat(FINE_REPORT_URL))); + FILE_STORAGE_URL_MAPPING.put(appType.concat(".").concat(FILE_STORAGE_URL), properties.getProperty(appType.concat(".").concat(FILE_STORAGE_URL))); + FILE_VIEW_URL_MAPPING.put(appType.concat(".").concat(FILE_VIEW_URL), properties.getProperty(appType.concat(".").concat(FILE_VIEW_URL))); + HTTP_CONTEXT_PATH_MAPPING.put(appType.concat(".").concat(HTTP_CONTEXT_PATH), properties.getProperty(appType.concat(".").concat(HTTP_CONTEXT_PATH))); + WX_CONTEXT_PATH_MAPPING.put(appType.concat(".").concat(WX_CONTEXT_PATH), properties.getProperty(appType.concat(".").concat(WX_CONTEXT_PATH))); + WX_SUBMIT_DATA_MAPPING.put(appType.concat(".").concat(WX_SUBMIT_DATA), properties.getProperty(appType.concat(".").concat(WX_SUBMIT_DATA))); + }); + } + + /** + * 企业微信审核http请求url——提交审核 + * + * @param appType 应用类型 + * @return 对应应用提交审核请求url + */ + public String getCommitUrl(String appType) { + return COMMIT_URL_MAPPING.get(appType.concat(".").concat(COMMIT_URL)); + } + + /** + * 企业微信审核http请求url——获取审核历史信息 + * + * @param appType 应用类型 + * @return 对应应用获取审核历史信息请求url + */ + public String getCommentUrl(String appType) { + return COMMENT_URL_MAPPING.get(appType.concat(".").concat(COMMENT_URL)); + } + + /** + * 企业微信审核http请求url——获取用户待办 + * + * @param appType 应用类型 + * @return 对应应用获取用户待办请求url + */ + public String getTodoUrl(String appType) { + return TODO_URL_MAPPING.get(appType.concat(".").concat(TODO_URL)); + } + + /** + * 企业微信审核http请求url——获取用户已办 + * + * @param appType 应用类型 + * @return 对应应用获取用户已办请求url + */ + public String getDoneUrl(String appType) { + return DONE_URL_MAPPING.get(appType.concat(".").concat(DONE_URL)); + } + + /** + * 企业微信审核http请求url——文件存储 + * + * @param appType 应用类型 + * @return 对应应用文件存储请求url + */ + public String getStorageUrl(String appType) { + return STORAGE_URL_MAPPING.get(appType.concat(".").concat(STORAGE_URL)); + } + + /** + * 企业微信审核http请求url——获取流程是否结束信息 + * + * @param appType 应用类型 + * @return 对应应用获取审核历史信息请求url + */ + public String getFinishUrl(String appType) { + return FINISH_URL_MAPPING.get(appType.concat(".").concat(FINISH_URL)); + } + + + /** + * 获取合同文本内容 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getRenderBpoContentUrl(String appType) { + return RENDERBPOCONTENT_URL_MAPPING.get(appType.concat(".").concat(RENDERBPOCONTENT_URL)); + } + /** + * 数字化采购报表地址 + * + * @param appType 应用类型 + * @return 获取请求url + */ + public String getFineReportUrl(String appType) { + return FINE_REPORT_URL_MAPPING.get(appType.concat(".").concat(FINE_REPORT_URL)); + } + + /** + * 字化附件管理地址 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getFileStorageUrl(String appType) { + return FILE_STORAGE_URL_MAPPING.get(appType.concat(".").concat(FILE_STORAGE_URL)); + } + + /** + * 字化附件预览 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getFileViewUrl(String appType) { + return FILE_VIEW_URL_MAPPING.get(appType.concat(".").concat(FILE_VIEW_URL)); + } + + /** + * 应用http调用地址 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getHttpContextPath(String appType) { + return HTTP_CONTEXT_PATH_MAPPING.get(appType.concat(".").concat(HTTP_CONTEXT_PATH)); + } + + /** + * 应用http调用地址 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getWxContextPath(String appType) { + return WX_CONTEXT_PATH_MAPPING.get(appType.concat(".").concat(WX_CONTEXT_PATH)); + } + + /** + * 应用http调用地址 + * + * @param appType 应用类型 + * @return 获取合同文本内容请求url + */ + public String getWxSubmitData(String appType) { + return WX_SUBMIT_DATA_MAPPING.get(appType.concat(".").concat(WX_SUBMIT_DATA)); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/WeiXinProperties.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/WeiXinProperties.java new file mode 100644 index 00000000..8a7244e9 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/config/WeiXinProperties.java @@ -0,0 +1,376 @@ +package com.zt.plat.module.system.service.msg.config; + + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zt.plat.framework.common.util.http.HttpClientUtils; +import com.zt.plat.module.system.api.sms.dto.code.User; +import com.zt.plat.module.system.dal.dataobject.msg.SysWxAuditConfig; +import com.zt.plat.module.system.framework.sms.core.enums.AuditConstants; +import com.zt.plat.module.system.service.msg.SysWxAuditConfigService; +import jakarta.annotation.Resource; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Component +@PropertySource("classpath:qywx.properties") +public class WeiXinProperties { + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private SysWxAuditConfigService sysWxAuditConfigService; + + /** + * 企业微信——企业access_token获取url + */ + @Getter + @Value("${qyAccessTokenUrl}") + private String qyAccessTokenUrl; + + /** + * 企业微信——消息发送url + */ + @Getter + @Value("${qySendMsgUrl}") + private String qySendMsgUrl; + + /** + * 企业微信——用户Oauth2认证授权url + */ + @Getter + @Value("${qyAuthorizeUrl}") + private String qyAuthorizeUrl; + + /** + * 企业微信-企业自建应用用户信息获取url + */ + @Getter + @Value("${qyUserInfoUrl}") + private String qyUserInfoUrl; + + /** + * 企业微信-企业自建应用用户信息获取url + */ + @Getter + @Value("${qyUserUrl}") + private String qyUserUrl; + + /** + * 企业微信-上传临时素材url + */ + @Getter + @Value("${qyUploadTempFileUrl}") + private String qyUploadTempFileUrl; + + /** + * 企业微信-部门创建url + */ + @Getter + @Value("${qyDeptCreateUrl}") + private String qyDeptCreateUrl; + + /** + * 企业微信-部门更新url + */ + @Getter + @Value("${qyDeptUpdateUrl}") + private String qyDeptUpdateUrl; + + /** + * 企业微信-部门删除url + */ + @Getter + @Value("${qyDeptDeleteUrl}") + private String qyDeptDeleteUrl; + + /** + * 企业微信-部门列表拉取url + */ + @Getter + @Value("${qyDeptQueryListUrl}") + private String qyDeptQueryListUrl; + /** + * 企业微信-部门列表拉取url + */ + @Getter + @Value("${qyMenuCreateUrl}") + private String qyMenuCreateUrl; + /** + * 企业微信-部门列表拉取url + */ + @Getter + @Value("${qyMenuDeleteUrl}") + private String qyMenuDeleteUrl; + /** + * 企业微信-部门列表拉取url + */ + @Getter + @Value("${qyMenuQueryUrl}") + private String qyMenuQueryUrl; + + /** + * 企业微信-ticket获取url + */ + @Getter + @Value("${ticketUrl}") + private String ticketUrl; + + /** + * 获取企业微信-企业access_token,先从redis持久化缓存中获取,若不存在,则从腾讯获取并加入缓存,key为企业微信corpId+corpSecret + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return String 企业微信-企业access_token + */ + public String getQyAccessToken(String corpId, String corpSecret) { + Object accessToken = redisTemplate.opsForValue().get(corpId.concat(corpSecret)); + if (Objects.nonNull(accessToken)) { + return accessToken.toString(); + } else { + JSONObject jsonObject = HttpClientUtils.httpGet(this.buildQyAccessTokenUrl(corpId, corpSecret)); + String qyAccessToken = jsonObject.getString("access_token"); + String expiresIn = jsonObject.getString("expires_in"); + redisTemplate.opsForValue().set(corpId.concat(corpSecret), qyAccessToken, Long.parseLong(expiresIn), TimeUnit.SECONDS); + return qyAccessToken; + } + } + + /** + * 获取JsapiTicket,先从redis持久化缓存中获取,若不存在,则从腾讯获取并加入缓存,key为ticket + */ + public String getJsapiTicket(String accessToken) { + Object ticket = redisTemplate.opsForValue().get("ticket"); + if (Objects.nonNull(ticket)) { + return ticket.toString(); + } else { + JSONObject jsonObject = HttpClientUtils.httpGet(this.buildTicketUrl(accessToken)); + String qyTicket = jsonObject.getString("ticket"); + String expiresIn = jsonObject.getString("expires_in"); + redisTemplate.opsForValue().set("ticket", qyTicket, Long.parseLong(expiresIn), TimeUnit.SECONDS); + return qyTicket; + } + } + + /** + * 获取企业微信内建应用简单用户信息对象 + * + * @param code 企业微信Oauth2授权成功后获取的授权code,每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。 + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return QywxUser企业颞部开发获取的用户信息对象 + */ + public User getUser(String code, String corpId, String corpSecret) { + JSONObject jsonObject = HttpClientUtils.httpGet(this.buildQyUserInfoUrl(code, corpId, corpSecret)); + return jsonObject.toJavaObject(User.class); + } + + /** + * 构建企业微信access_token获取url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + */ + public String buildQyAccessTokenUrl(String corpId, String corpSecret) { + return this.getQyAccessTokenUrl().replace("CORP_ID", corpId).replace("CORP_SECRET", corpSecret); + } + + /** + * 构建获取企业微信内建应用简单用户信息对象获取url + * + * @param code 企业微信Oauth2授权成功后获取的授权code + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + */ + public String buildQyUserInfoUrl(String code, String corpId, String corpSecret) { + return this.getQyUserInfoUrl().replaceAll("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replaceAll("CODE", code); + } + + /** + * 构建读取成员url + * + * @param userId 企业微信用户id + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + */ + public String buildQyUserUrl(String userId, String corpId, String corpSecret) { + return this.getQyUserUrl().replaceAll("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replaceAll("USER_ID", userId); + } + + /** + * 构建消息发送url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + */ + public String buildQySendMsgUrl(String corpId, String corpSecret) { + return this.getQySendMsgUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)); + } + + /** + * 构建企业微信授权url + * + * @param redirectUrl 授权成功重定向url + * @param corpId 企业微信企业ID + * @param state 授权成功重定向url携带的自定义参数 + * @return 企业微信授权url + */ + public String buildQyAuthorizeUrl(String corpId, String agentId, String redirectUrl, String state) { + return this.getQyAuthorizeUrl() + .replaceAll("CORPID", corpId) + .replaceAll("REDIRECT_URI", redirectUrl) + .replaceAll("AGENTID", agentId) + .replaceAll("STATE", state); + } + + /** + * 获取上传临时素材url + * + * @param type 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file) + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return String 上传临时素材url + */ + public String buildQyUploadTempFileUrl(String type, String corpId, String corpSecret) { + return this.getQyUploadTempFileUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replaceAll("TYPE", type); + } + + /** + * 构建企业微信-部门创建url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return String 部门创建url + */ + public String buildQyDeptCreateUrl(String corpId, String corpSecret) { + return this.getQyDeptCreateUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)); + } + + /** + * 构建企业微信-部门更新url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return String 部门更新url + */ + public String buildQyDeptUpdateUrl(String corpId, String corpSecret) { + return this.getQyDeptUpdateUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)); + } + + /** + * 构建企业微信-部门删除url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @param id 部门id + * @return String 部门删除url + */ + public String buildQyDeptDeleteUrl(String id, String corpId, String corpSecret) { + return this.getQyDeptDeleteUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replaceAll("ID", id); + } + + /** + * 构建企业微信-部门列表拉取url + * + * @param id 部门id + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @return String 部门列表拉取url + */ + public String buildQyDeptQueryListUrl(String id, String corpId, String corpSecret) { + return this.getQyDeptQueryListUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replaceAll("DEPARTMENT_ID", id); + } + + /** + * 构建企业微信应用菜单创建url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @param agentId 企业微信企业应用ID + * @return String 应用菜单创建url + */ + public String buildQyMenuCreateUrl(String corpId, String corpSecret, String agentId) { + return this.getQyMenuCreateUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replace("AGENT_ID", agentId); + } + + /** + * 构建企业微信应用菜单删除url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @param agentId 企业微信企业应用ID + * @return string 应用菜单删除url + */ + public String buildQyMenuDeleteUrl(String corpId, String corpSecret, String agentId) { + return this.getQyMenuDeleteUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replace("AGENT_ID", agentId); + } + + /** + * 构建企业微信应用菜单拉取url + * + * @param corpId 企业微信企业ID + * @param corpSecret 企业微信企业应用密钥 + * @param agentId 企业微信企业应用ID + * @return string 应用菜单拉取url + */ + public String buildQyMenuQueryUrl(String corpId, String corpSecret, String agentId) { + return this.getQyMenuQueryUrl().replace("ACCESS_TOKEN", this.getQyAccessToken(corpId, corpSecret)).replace("AGENT_ID", agentId); + } + + /** + * 构建企业微信应用ticket获取url + */ + public String buildTicketUrl(String accessToken) { + return this.getTicketUrl().replace("ACCESS_TOKEN", accessToken); + } + + /** + * 获取企业微信消息发送应用配置 + * + * @param appType 服务应用类型 + * @return SysWxAuditConfig 企业微信消息发送应用配置 + */ + /** + * 获取企业微信消息发送应用配置 + * + * @param appType 服务应用类型 + * @return SysWxAuditConfig 企业微信消息发送应用配置 + */ + public SysWxAuditConfig buildSysWxAuditConfig(String appType) throws Exception{ + if (StringUtils.isBlank(appType)) { + throw new Exception("请求缺少服务应用类型!"); + + } + List sysWxAuditConfigs = sysWxAuditConfigService.list(new LambdaQueryWrapper() + .eq(SysWxAuditConfig::getAppType, appType) + .eq(SysWxAuditConfig::getState, SysWxAuditConfig.STATE_ACTIVE) + .eq(SysWxAuditConfig::getExt1, AuditConstants.CHINALCO_ZLEB_CHSZH)); + SysWxAuditConfig sysWxAuditConfig = sysWxAuditConfigs.get(0); + if (StringUtils.isBlank(sysWxAuditConfig.getWxContextPath())) { + throw new Exception("微信审核应用上下文根未配置,请配置后重试!"); + } + if (StringUtils.isBlank(sysWxAuditConfig.getHttpContextPath())) { + throw new Exception("微信审核http远程请求上下文根未配置,请配置后重试!"); + } + if (StringUtils.isBlank(sysWxAuditConfig.getCorpId())) { + throw new Exception("微信审核企业ID未配置,请配置后重试!"); + } + if (StringUtils.isBlank(sysWxAuditConfig.getCorpSecret())) { + throw new Exception("微信审核企业应用密钥未配置,请配置后重试!"); + } + if (StringUtils.isBlank(sysWxAuditConfig.getAgentId())) { + throw new Exception("微信审核企业应用ID未配置,请配置后重试!"); + } + return sysWxAuditConfig; + } + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/MsgLogServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/MsgLogServiceImpl.java new file mode 100644 index 00000000..2a8039b9 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/MsgLogServiceImpl.java @@ -0,0 +1,78 @@ +package com.zt.plat.module.system.service.msg.impl; + + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.TextMessage; +import com.zt.plat.module.system.dal.dataobject.sms.SmsLogDO; +import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; +import com.zt.plat.module.system.dal.mysql.sms.SmsLogMapper; +import com.zt.plat.module.system.enums.sms.SmsReceiveStatusEnum; +import com.zt.plat.module.system.enums.sms.SmsSendStatusEnum; +import com.zt.plat.module.system.service.msg.IMsgLogService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; + +@Service +public class MsgLogServiceImpl implements IMsgLogService { + + @Resource + private SmsLogMapper smsLogMapper; + + + @Override + public Long createSmsLog(Long userId, Integer userType, Boolean isSend, SmsTemplateDO template, String templateContent, Map templateParams) { + return 0L; + } + + @Override + public void updateSmsSendResult(Long id, Boolean success, String apiSendCode, String apiSendMsg, String apiRequestId, String apiSerialNo) { + + } + + @Override + public void updateSmsReceiveResult(Long id, Boolean success, LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg) { + + } + + @Override + public PageResult getSmsLogPage(SmsLogPageReqVO pageReqVO) { + return null; + } + + @Override + public SmsLogDO getSmsLog(Long id) { + return null; + } + + /** + * 创建消息日志 + */ + @Override + public Long createMsgLog(Long userId, Integer userType, Boolean isSend, SmsTemplateDO template, + TextMessage textMessage, Map templateParams) { + SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder(); + // 根据是否要发送,设置状态 + logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus() + : SmsSendStatusEnum.IGNORE.getStatus()); + // 设置手机相关字段 + //logBuilder.mobile(mobile).userId(userId).userType(userType); + // 设置模板相关字段 + logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType()); + logBuilder.templateContent(String.valueOf(textMessage)).templateParams(templateParams) + .apiTemplateId(template.getApiTemplateId()); + // 设置渠道相关字段 + logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode()); + // 设置接收相关字段 + logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus()); + + // 插入数据库 + SmsLogDO logDO = logBuilder.build(); + smsLogMapper.insert(logDO); + return logDO.getId(); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/RocketmqMsgQueueReceiver.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/RocketmqMsgQueueReceiver.java new file mode 100644 index 00000000..cc5859d5 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/RocketmqMsgQueueReceiver.java @@ -0,0 +1,106 @@ +package com.zt.plat.module.system.service.msg.impl; + +/** + * 流程审核企业微信待办消息队列监听 + */ +/* +@Slf4j +@Component +@RocketMQMessageListener(topic = "springboot-mq",consumerGroup = "consumer-service") +@Transactional(rollbackFor = Exception.class) +public class RocketmqMsgQueueReceiver implements RocketMQListener { + + @Resource + private ISysRocketMqService sysActiveMqService; + + @Resource + private ISendMsgService sendMsgService; + + */ +/** + * 接受消息 + *//* + + @Override + public void onMessage(String jsonStr) { + TextMessage tm = JSON.parseObject(jsonStr, TextMessage.class); + log.info("接收到消息: {}", tm); + String consumeMsg; + try { + Text text = tm.getText(); + String toUser = tm.getTouser();// 企业微信接收者 + String userName = tm.getToparty(); // 部门 + String userEname = tm.getText().getContent();//标签 + String agentid = tm.getAgentid();//企微ID + String msgtype = tm.getMsgtype();//消息类型 + String appType = tm.getAppType();// 消息自建类型 + Integer enableDuplicateCheck = tm.getEnable_duplicate_check();//是否重启重复消息检查 + Integer duplicateCheckInterval = tm.getDuplicate_check_interval();// 是否重复消息检查的时间间隔 + String userCname = tm.getText().getContent(); + // 校验接受用户 + if (StringUtils.isBlank(toUser)) { + consumeMsg = "企业微信消息发送失败,用户【" + userEname + "-" + userCname + "】未绑定企业微信账号!"; + sysActiveMqService.saveByTextMessage(this.getClass().getName(), tm, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), consumeMsg); + } else { + String msgType = tm.getMsgtype(); + if (Objects.equals(msgType, WxMsgTypeConstant.TEXTCARD.getCode())) { + */ +/* 构建审批回调url *//* + + String appType = tm.getStringProperty("appType"); + String approveType = tm.getStringProperty("approveType"); + String companyCode = tm.getStringProperty("userCompanyCode"); + String taskId = tm.getStringProperty("taskId"); + String processInstanceId = tm.getStringProperty("processInstanceId"); + String businessId = tm.getStringProperty("businessId"); + Map substituteMap = new HashMap<>(5); + substituteMap.put("appType", appType.toLowerCase(Locale.ROOT)); + substituteMap.put("approveType", approveType); + substituteMap.put("companyCode", companyCode); + substituteMap.put("taskId", taskId); + substituteMap.put("processInstanceId", processInstanceId); + substituteMap.put("businessId", businessId); + String redirectUrl = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.REDIRECT_URL.getCode(), substituteMap); + + */ +/* 构建文本卡片消息 *//* + + String title = tm.getStringProperty("title"); + String taskName = tm.getStringProperty("taskName"); + String datetime = tm.getStringProperty("datetime"); + String btnTxt = tm.getStringProperty("btntxt"); + substituteMap.put("subTitle", datetime + "——" + userName); + substituteMap.put("description", descriptionText); + substituteMap.put("taskName", taskName); + String description = StringSolveUtils.placeholderReplace(AuditConstants.SunepsAuditType.AUDIT_DESCRIPTION_FORMAT.getCode(), substituteMap); + TextCardMessage textCardMessage = new TextCardMessage(); + TextCard textCard = new TextCard(); + textCard.setTitle(title) + .setDescription(description) + .setUrl(redirectUrl); + if (StringUtils.isNotBlank(btnTxt)) { + textCard.setBtntxt(btnTxt); + } + textCardMessage.setTextcard(textCard).setAppType(appType).setTouser(toUser); + sendMsgService.sendTextCardMsg(textCardMessage); + consumeMsg = "企业微信消息发送成功,接收用户【" + userEname + "-" + userCname + "】!"; + sysActiveMqService.saveByTextMessage(this.getClass().getName(), tm, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_SUCCESS.getCode(), consumeMsg); + } + + + + } + } catch (Exception e) { + log.error("中铝e办发送消息异常:{}",e.getMessage(),e); + try { + // 重试时不重复保存队列消息 + if (!message.getJMSRedelivered()) { + sysActiveMqService.saveByTextMessage(this.getClass().getName(), tm, JmsConstant.JMS_TYPE_QUEUE.getCode(), JmsConstant.CONSUME_FAILURE.getCode(), e.getMessage()); + } + } catch (Exception exception) { + log.error("中铝e办发送消息异常:{}", exception.getMessage(),e); + } + } + } +} +*/ diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendMsgServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendMsgServiceImpl.java new file mode 100644 index 00000000..1b5f58c6 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendMsgServiceImpl.java @@ -0,0 +1,732 @@ +package com.zt.plat.module.system.service.msg.impl; + +import com.google.common.annotations.VisibleForTesting; +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.validation.MatcherSolveUtils; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.*; +import com.zt.plat.module.system.controller.admin.sms.vo.template.MsgTemplateSendReqVO; +import com.zt.plat.module.system.dal.dataobject.sms.SmsChannelDO; +import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import com.zt.plat.module.system.mq.producer.msg.MsgProducer; +import com.zt.plat.module.system.service.msg.IMsgLogService; +import com.zt.plat.module.system.service.msg.ISendMsgService; +import com.zt.plat.module.system.service.sms.SmsChannelService; +import com.zt.plat.module.system.service.sms.SmsTemplateService; +import jakarta.annotation.Resource; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; +/** + * 消息发送接口实现 + * TODO =========================================== 参考这个发送消息类文件=========================================================== + * @author luzemin + */ +@Service +@Transactional(rollbackFor = Exception.class) +public class SendMsgServiceImpl implements ISendMsgService { + + /** + * 文本消息内容限制 + */ + public static final int INT_2048 = 2048; + + @Resource + private SmsChannelService smsChannelService; + + @Resource + private SmsTemplateService smsTemplateService; + + @Resource + private IMsgLogService msgLogService; + + @Resource + private MsgProducer msgProducer; + /** + * 发送MQ文本消息 + * 用户ID,模版内容,消息类型、模版编码 + * @param textMessage 文本消息包装对象 + * @return r 响应对象 + */ + @Override + public CommonResult sendTextMsg(MsgTemplateSendReqVO sendReqVO, TextMessage textMessage) throws Exception { + + Long userId = sendReqVO.getUserId(); + Integer userType = sendReqVO.getUserType(); + String templateCode = sendReqVO.getTemplateCode(); + Map templateParams = sendReqVO.getTemplateParams(); + // 校验模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(textMessage); + /* 消息类型校验 */ + if (!Objects.equals(textMessage.getMsgtype(), WxMsgTypeConstant.TEXT.getCode())) { + throw new Exception("文本消息类型必须为" + WxMsgTypeConstant.TEXT.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textMessage.getText())) { + throw new Exception("消息内容不能为空!"); + } + /* 消息内容校验 */ + if (StringUtils.isBlank(textMessage.getText().getContent())) { + throw new Exception("消息发送消息体不能为空!"); + } + /* 消息内容长度检验 */ + if (textMessage.getText().getContent().length() > INT_2048) { + throw new Exception("消息发送消息内容,最长不超过2048个字节,超过将截断!"); + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + //格式化短信内容 + //String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + //创建发送日志 + Long sendLogId = msgLogService.createMsgLog(userId, userType, isSend, template, textMessage, templateParams); + + //发送 MQ 消息,异步执行发送消息(发送日志、内容、渠道ID、模版ID) + if (isSend) { + msgProducer.sendMsg(sendLogId, textMessage.getText().getContent(), template.getChannelId(), template.getApiTemplateId(), newTemplateParams); + } + + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendImageMsg(Long userId, Integer userType, String templateCode, Map templateParams, ImageMessage imageMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(imageMessage); + /* 消息类型校验 */ + if (!Objects.equals(imageMessage.getMsgtype(), WxMsgTypeConstant.IMAGE.getCode())) { + throw new Exception("图片消息类型必须为" + WxMsgTypeConstant.IMAGE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(imageMessage.getImage())) { + throw new Exception("消息内容不能为空!"); + } + /* 图片媒体ID校验 */ + if (StringUtils.isBlank(imageMessage.getImage().getMedia_id())) { + throw new Exception("图片媒体ID不能为空!"); + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + + } + + @Override + public CommonResult sendVoiceMsg(Long userId, Integer userType, String templateCode, Map templateParams, VoiceMessage voiceMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + /* 基础信息校验 */ + msgSendBaseCheck(voiceMessage); + /* 消息类型校验 */ + if (!Objects.equals(voiceMessage.getMsgtype(), WxMsgTypeConstant.VOICE.getCode())) { + throw new Exception("语音消息类型必须为" + WxMsgTypeConstant.VOICE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(voiceMessage.getVoice())) { + throw new Exception("消息内容不能为空!"); + } + /* 语音媒体ID校验 */ + if (StringUtils.isBlank(voiceMessage.getVoice().getMedia_id())) { + throw new Exception("语音媒体ID不能为空!"); + } + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendVideoMsg(Long userId, Integer userType, String templateCode, Map templateParams, VideoMessage videoMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(videoMessage); + /* 消息类型校验 */ + if (!Objects.equals(videoMessage.getMsgtype(), WxMsgTypeConstant.VIDEO.getCode())) { + throw new Exception("视频消息类型必须为" + WxMsgTypeConstant.VIDEO.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(videoMessage.getVideo())) { + throw new Exception("消息内容不能为空!"); + } + /* 视频媒体ID校验 */ + if (StringUtils.isBlank(videoMessage.getVideo().getMedia_id())) { + throw new Exception("视频媒体ID不能为空!"); + } + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendFileMsg(Long userId, Integer userType, String templateCode, Map templateParams, FileMessage fileMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(fileMessage); + /* 消息类型校验 */ + if (!Objects.equals(fileMessage.getMsgtype(), WxMsgTypeConstant.FILE.getCode())) { + throw new Exception("文件消息类型必须为" + WxMsgTypeConstant.FILE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(fileMessage.getFile())) { + throw new Exception("消息内容不能为空!"); + } + /* 文件媒体ID校验 */ + if (StringUtils.isBlank(fileMessage.getFile().getMedia_id())) { + throw new Exception("文件媒体ID不能为空!"); + } + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendTextCardMsgPich01(Long userId, Integer userType, String templateCode, Map templateParams, TextCardMessage textCardMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(textCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(textCardMessage.getMsgtype(), WxMsgTypeConstant.TEXTCARD.getCode())) { + throw new Exception("文本卡片消息类型必须为" + WxMsgTypeConstant.TEXTCARD.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textCardMessage.getTextcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 文本卡片消息标题校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getTitle())) { + throw new Exception("文本卡片消息比标题不能为空!"); + } + /* 文本卡片消息描述校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getDescription())) { + throw new Exception("文本卡片消息描述校验不能为空!"); + } + /* 文本卡片消息点击url校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getUrl())) { + throw new Exception("文本卡片消息点击url校验不能为空!"); + } + /* 文本卡片消息点击url正确性校验 */ + textCardMessage.getTextcard().setUrl(textCardMessage.getTextcard().getUrl()); + if (!MatcherSolveUtils.checkInternetURL(textCardMessage.getTextcard().getUrl())) { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + + } + + @Override + public CommonResult sendTextCardMsg(Long userId, Integer userType, String templateCode, Map templateParams, TextCardMessage textCardMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(textCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(textCardMessage.getMsgtype(), WxMsgTypeConstant.TEXTCARD.getCode())) { + throw new Exception("文本卡片消息类型必须为" + WxMsgTypeConstant.TEXTCARD.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textCardMessage.getTextcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 文本卡片消息标题校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getTitle())) { + throw new Exception("文本卡片消息比标题不能为空!"); + } + /* 文本卡片消息描述校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getDescription())) { + throw new Exception("文本卡片消息描述校验不能为空!"); + } + /* 文本卡片消息点击url校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getUrl())) { + throw new Exception("文本卡片消息点击url校验不能为空!"); + } + /* 文本卡片消息点击url正确性校验 */ + if (!MatcherSolveUtils.checkInternetURL(textCardMessage.getTextcard().getUrl())) { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } + + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + + + } + + @Override + public CommonResult sendNewsMsg(Long userId, Integer userType, String templateCode, Map templateParams, NewsMessage newsMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(newsMessage); + /* 消息类型校验 */ + if (!Objects.equals(newsMessage.getMsgtype(), WxMsgTypeConstant.NEWS.getCode())) { + throw new Exception("图文消息类型必须为" + WxMsgTypeConstant.NEWS.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(newsMessage.getNews())) { + throw new Exception("消息内容不能为空!"); + } + /* 图文消息校验 */ + if (newsMessage.getNews().getArticles().isEmpty()) { + throw new Exception("图文消息内容不能为空!"); + } else { + //newsMessage.setAgentid(sysWxAuditConfig.getAgentId()); + newsMessage.getNews().getArticles().forEach(article -> { + /* 消息标题校验 */ + if (StringUtils.isBlank(article.getTitle())) { + try { + throw new Exception("图文消息内容标题不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息url校验 */ + if (StringUtils.isBlank(article.getUrl())) { + try { + throw new Exception("图文消息内容点击url不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息url正确性校验 */ + // article.setUrl(sysWxAuditConfig.getWxContextPath() + article.getUrl()); + if (!MatcherSolveUtils.checkInternetURL(article.getUrl())) { + try { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendMpNewsMsg(Long userId, Integer userType, String templateCode, Map templateParams, MpNewsMessage mpNewsMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + /* 基础信息校验 */ + msgSendBaseCheck(mpNewsMessage); + /* 消息类型校验 */ + if (!Objects.equals(mpNewsMessage.getMsgtype(), WxMsgTypeConstant.MPNEWS.getCode())) { + throw new Exception("图文(mpnews)消息类型必须为" + WxMsgTypeConstant.MPNEWS.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(mpNewsMessage.getMpnews())) { + throw new Exception("消息内容不能为空!"); + } + /* 图文消息校验 */ + if (mpNewsMessage.getMpnews().getArticles().isEmpty()) { + throw new Exception("图文消息内容不能为空!"); + } else { + mpNewsMessage.getMpnews().getArticles().forEach(article -> { + /* 消息标题校验 */ + if (StringUtils.isBlank(article.getTitle())) { + try { + throw new Exception("图文消息内容标题不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息图片媒体ID校验 */ + if (StringUtils.isBlank(article.getThumb_media_id())) { + try { + throw new Exception("图文消息内容图片媒体ID不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息内容校验 */ + if (StringUtils.isBlank(article.getContent())) { + try { + throw new Exception("图文消息内容能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendMarkdownMsg(Long userId, Integer userType, String templateCode, Map templateParams, MarkdownMessage markdownMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(markdownMessage); + /* 消息类型校验 */ + if (!Objects.equals(markdownMessage.getMsgtype(), WxMsgTypeConstant.MARKDOWN.getCode())) { + throw new Exception("markdown消息消息类型必须为" + WxMsgTypeConstant.MARKDOWN.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(markdownMessage.getMarkdown())) { + throw new Exception("markdown消息内容能为空!"); + } + /* 消息内容校验 */ + if (StringUtils.isBlank(markdownMessage.getMarkdown().getContent())) { + throw new Exception("markdown消息内容能为空!"); + } + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendMiniProgramNoticeMsg(Long userId, Integer userType, String templateCode, Map templateParams, MiniProgramNoticeMessage miniProgramNoticeMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + + /* 基础信息校验 */ + if (StringUtils.isAllBlank(miniProgramNoticeMessage.getTouser(), miniProgramNoticeMessage.getToparty(), miniProgramNoticeMessage.getTotag())) { + throw new Exception("消息发送对象不能为空!"); + } + /* 消息类型校验 */ + if (!Objects.equals(miniProgramNoticeMessage.getMsgtype(), WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode())) { + throw new Exception("小程序通知消息消息类型必须为" + WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(miniProgramNoticeMessage.getMiniprogram_notice())) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序appid校验 */ + if (StringUtils.isBlank(miniProgramNoticeMessage.getMiniprogram_notice().getAppid())) { + throw new Exception("小程序appid,必须是与当前应用关联的小程序!"); + } + /* 小程序标题校验 */ + if (StringUtils.isBlank(miniProgramNoticeMessage.getMiniprogram_notice().getTitle())) { + throw new Exception("消息标题不能为空,长度限制4-12个汉字!"); + } + /* 小程序消息内容校验 */ + if (Objects.isNull(miniProgramNoticeMessage.getMiniprogram_notice().getContent_item())) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序消息内容校验 */ + if (miniProgramNoticeMessage.getMiniprogram_notice().getContent_item().isEmpty()) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序消息内容校验key-value校验 */ + miniProgramNoticeMessage.getMiniprogram_notice().getContent_item().forEach(contentItem -> { + /* 小程序消息内容key校验 */ + if (StringUtils.isBlank(contentItem.getKey())) { + try { + throw new Exception("小程序消息内容key不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 小程序消息内容value校验 */ + if (StringUtils.isBlank(contentItem.getValue())) { + try { + throw new Exception("小程序消息内容value不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + @Override + public CommonResult sendInteractiveTaskCardMsg(Long userId, Integer userType, String templateCode, Map templateParams, InteractiveTaskCardMessage interactiveTaskCardMessage) throws Exception { + + // 校验短信模板是否合法 + SmsTemplateDO template = validateSmsTemplate(templateCode); + // 校验短信渠道是否合法 + SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId()); + // 构建有序的模板参数。为什么放在这个位置,是提前保证模板参数的正确性,而不是到了插入发送日志 + List> newTemplateParams = buildTemplateParams(template, templateParams); + + /* 基础信息校验 */ + msgSendBaseCheck(interactiveTaskCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(interactiveTaskCardMessage.getMsgtype(), WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode())) { + throw new Exception("任务卡片消息消息类型必须为" + WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode() + "!"); + } + /* 任务卡片消息内容校验 */ + if (Objects.isNull(interactiveTaskCardMessage.getInteractive_taskcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 任务卡片消息内容标题校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + throw new Exception("任务卡片消息内容标题不能为空!"); + } + /* 任务卡片消息url校验 */ + if (!MatcherSolveUtils.checkInternetURL(interactiveTaskCardMessage.getInteractive_taskcard().getUrl())) { + throw new Exception("任务卡片消息内容标题不能为空,点击跳转url不是正确的网络url链接!"); + } + /* 任务卡片消息内容任务id校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTask_id())) { + throw new Exception("任务卡片消息内容任务id不能为空,任务id,同一个应用发送的任务卡片消息的任务id不能重复,只能由数字、字母和“_-@”组成,最长支持128字节!"); + } + /* 任务卡片消息内容按钮组校验 */ + if (Objects.isNull(interactiveTaskCardMessage.getInteractive_taskcard().getBtn())) { + throw new Exception("任务卡片消息内容按钮组不能为空!"); + } + /* 任务卡片消息内容按钮组校验 */ + interactiveTaskCardMessage.getInteractive_taskcard().getBtn().forEach(btn -> { + /* 任务卡片消息内容按钮组按钮key校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + try { + throw new Exception("任务卡片消息内容按钮组按钮key不能为空,按钮key值,用户点击后,会产生任务卡片回调事件,回调事件会带上该key值,只能由数字、字母和“_-@”组成,最长支持128字节!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 任务卡片消息内容按钮组阿牛name校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + try { + throw new Exception("任务卡片消息内容按钮组阿牛name不能为空,按钮名称,最长支持18个字节,超过则截断!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + // 创建发送日志。如果模板被禁用,则不发送短信,只记录日志 + Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus()) + && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus()); + String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); + Long sendLogId = msgLogService.createSmsLog(userId, userType, isSend, template, content, templateParams); + + /* if (isSend) { + msgProducer.sendSendMsg(sendLogId, content, template.getChannelId(), + template.getApiTemplateId(), newTemplateParams); + }*/ + return CommonResult.success(sendLogId); + } + + + /** + * 企业微信消息发送基础信息校验 + * + * @param baseMessage 企业微信消息体 + */ + private void msgSendBaseCheck(BaseMessage baseMessage) throws Exception{ + if (StringUtils.isAllBlank(baseMessage.getTouser(), baseMessage.getToparty(), baseMessage.getTotag())) { + throw new Exception("消息发送对象不能为空!"); + } + } + + @VisibleForTesting + SmsTemplateDO validateSmsTemplate(String templateCode) { + // 获得短信模板。考虑到效率,从缓存中获取 + SmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode); + // 短信模板不存在 + if (template == null) { + throw exception(MSG_CALLBACK_SIGN_INVALID); + } + return template; + } + + + @VisibleForTesting + SmsChannelDO validateSmsChannel(Long channelId) { + // 获得短信模板。考虑到效率,从缓存中获取 + SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId); + // 短信模板不存在 + if (channelDO == null) { + throw exception(MSG_CHANNEL_NOT_EXISTS); + } + return channelDO; + } + + + @VisibleForTesting + List> buildTemplateParams(SmsTemplateDO template, Map templateParams) { + return template.getParams().stream().map(key -> { + Object value = templateParams.get(key); + if (value == null) { + throw exception(MSG_CALLBACK_SIGN_INVALID, key); + } + return new KeyValue<>(key, value); + }).collect(Collectors.toList()); + } + + + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendWxMsgServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendWxMsgServiceImpl.java new file mode 100644 index 00000000..b203ed58 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SendWxMsgServiceImpl.java @@ -0,0 +1,587 @@ +package com.zt.plat.module.system.service.msg.impl; + +import com.alibaba.fastjson.JSONObject; +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.http.HttpClientUtils; +import com.zt.plat.framework.common.util.validation.MatcherSolveUtils; +import com.zt.plat.module.system.framework.sms.core.enums.WxMsgTypeConstant; +import com.zt.plat.module.system.service.msg.config.WeiXinProperties; +import com.zt.plat.module.system.dal.dataobject.msg.SysWxAuditConfig; +import com.zt.plat.module.system.service.msg.ISendWxMsgService; +import org.apache.commons.lang3.StringUtils; +import com.zt.plat.module.system.controller.admin.sms.vo.msg.*; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.Objects; +/** + * TODO ================================ 中铝e办MQ生产者 ====================================== + */ +@Slf4j +@Service +@Transactional(rollbackFor = Exception.class) +public class SendWxMsgServiceImpl implements ISendWxMsgService { + + + /** + * 文本消息内容限制 + */ + public static final int INT_2048 = 2048; + /** + * 企业微信消息发送响应结果代码key + */ + public static final String WX_MSG_RESPONSE_CODE = "errcode"; + /** + * 企业微信消息发送成功响应代码值 + */ + public static final String WX_MSG_RESPONSE_SUCCESS_CODE = "0"; + + @Resource + private WeiXinProperties weiXinProperties; + + /** + * 发送企业微信文本消息 + * + * @param textMessage 文本消息对象 + * @return r 响应对象 + */ + @Override + public CommonResult sendTextMsg(TextMessage textMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(textMessage); + /* 消息类型校验 */ + if (!Objects.equals(textMessage.getMsgtype(), WxMsgTypeConstant.TEXT.getCode())) { + throw new Exception("文本消息类型必须为" + WxMsgTypeConstant.TEXT.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textMessage.getText())) { + throw new Exception("消息内容不能为空!"); + } + /* 消息内容校验 */ + if (StringUtils.isBlank(textMessage.getText().getContent())) { + throw new Exception("消息发送消息体不能为空!"); + } + /* 消息内容长度检验 */ + if (textMessage.getText().getContent().length() > INT_2048) { + throw new Exception("消息发送消息内容,最长不超过2048个字节,超过将截断!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(textMessage); + textMessage.setAgentid(sysWxAuditConfig.getAgentId()); + /* 如果消息包含点击链接,重新包装处理消息内容 */ + if (StringUtils.isNotBlank(textMessage.getText().getUrl())) { + textMessage.getText().setUrl(sysWxAuditConfig.getWxContextPath() + textMessage.getText().getUrl()); + /* url校验 */ + if (!MatcherSolveUtils.checkInternetURL(textMessage.getText().getUrl())) { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } + textMessage.getText().buildUrlContent(); + } + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(textMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信图片消息 + * + * @param imageMessage 图片消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendImageMsg(ImageMessage imageMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(imageMessage); + /* 消息类型校验 */ + if (!Objects.equals(imageMessage.getMsgtype(), WxMsgTypeConstant.IMAGE.getCode())) { + throw new Exception("图片消息类型必须为" + WxMsgTypeConstant.IMAGE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(imageMessage.getImage())) { + throw new Exception("消息内容不能为空!"); + } + /* 图片媒体ID校验 */ + if (StringUtils.isBlank(imageMessage.getImage().getMedia_id())) { + throw new Exception("图片媒体ID不能为空!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(imageMessage); + imageMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(imageMessage)); + System.out.println("图片消息发送返回:" + jsonObject.toJSONString()); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信语音消息 + * + * @param voiceMessage 语音消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendVoiceMsg(VoiceMessage voiceMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(voiceMessage); + /* 消息类型校验 */ + if (!Objects.equals(voiceMessage.getMsgtype(), WxMsgTypeConstant.VOICE.getCode())) { + throw new Exception("语音消息类型必须为" + WxMsgTypeConstant.VOICE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(voiceMessage.getVoice())) { + throw new Exception("消息内容不能为空!"); + } + /* 语音媒体ID校验 */ + if (StringUtils.isBlank(voiceMessage.getVoice().getMedia_id())) { + throw new Exception("语音媒体ID不能为空!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(voiceMessage); + voiceMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(voiceMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信视频消息 + * + * @param videoMessage 视频消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendVideoMsg(VideoMessage videoMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(videoMessage); + /* 消息类型校验 */ + if (!Objects.equals(videoMessage.getMsgtype(), WxMsgTypeConstant.VIDEO.getCode())) { + throw new Exception("视频消息类型必须为" + WxMsgTypeConstant.VIDEO.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(videoMessage.getVideo())) { + throw new Exception("消息内容不能为空!"); + } + /* 视频媒体ID校验 */ + if (StringUtils.isBlank(videoMessage.getVideo().getMedia_id())) { + throw new Exception("视频媒体ID不能为空!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(videoMessage); + videoMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(videoMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信文件消息 + * + * @param fileMessage 文件消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendFileMsg(FileMessage fileMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(fileMessage); + /* 消息类型校验 */ + if (!Objects.equals(fileMessage.getMsgtype(), WxMsgTypeConstant.FILE.getCode())) { + throw new Exception("文件消息类型必须为" + WxMsgTypeConstant.FILE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(fileMessage.getFile())) { + throw new Exception("消息内容不能为空!"); + } + /* 文件媒体ID校验 */ + if (StringUtils.isBlank(fileMessage.getFile().getMedia_id())) { + throw new Exception("文件媒体ID不能为空!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(fileMessage); + fileMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(fileMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信文本卡片消息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendTextCardMsg(TextCardMessage textCardMessage) throws Exception{ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(textCardMessage); + /* 基础信息校验 */ + msgSendBaseCheck(textCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(textCardMessage.getMsgtype(), WxMsgTypeConstant.TEXTCARD.getCode())) { + throw new Exception("文本卡片消息类型必须为" + WxMsgTypeConstant.TEXTCARD.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textCardMessage.getTextcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 文本卡片消息标题校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getTitle())) { + throw new Exception("文本卡片消息比标题不能为空!"); + } + /* 文本卡片消息描述校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getDescription())) { + throw new Exception("文本卡片消息描述校验不能为空!"); + } + /* 文本卡片消息点击url校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getUrl())) { + throw new Exception("文本卡片消息点击url校验不能为空!"); + } + /* 文本卡片消息点击url正确性校验 */ + textCardMessage.getTextcard().setUrl(sysWxAuditConfig.getWxContextPath() + textCardMessage.getTextcard().getUrl()); + if (!MatcherSolveUtils.checkInternetURL(textCardMessage.getTextcard().getUrl())) { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } + /* 消息发送 */ + textCardMessage.setAgentid(sysWxAuditConfig.getAgentId()); + //TODO 此出发中铝 e 办 + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(textCardMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信文本卡片消息 + * + * @param textCardMessage 文本卡片消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendTextCardMsgPich01(TextCardMessage textCardMessage) throws Exception{ + + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(textCardMessage); + /* 基础信息校验 */ + msgSendBaseCheck(textCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(textCardMessage.getMsgtype(), WxMsgTypeConstant.TEXTCARD.getCode())) { + throw new Exception("文本卡片消息类型必须为" + WxMsgTypeConstant.TEXTCARD.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(textCardMessage.getTextcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 文本卡片消息标题校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getTitle())) { + throw new Exception("文本卡片消息比标题不能为空!"); + } + /* 文本卡片消息描述校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getDescription())) { + throw new Exception("文本卡片消息描述校验不能为空!"); + } + /* 文本卡片消息点击url校验 */ + if (StringUtils.isBlank(textCardMessage.getTextcard().getUrl())) { + throw new Exception("文本卡片消息点击url校验不能为空!"); + } + /* 文本卡片消息点击url正确性校验 */ + textCardMessage.getTextcard().setUrl(textCardMessage.getTextcard().getUrl()); + if (!MatcherSolveUtils.checkInternetURL(textCardMessage.getTextcard().getUrl())) { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } + /* 消息发送 */ + textCardMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(textCardMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信图文消息 + * + * @param newsMessage 图文消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendNewsMsg(NewsMessage newsMessage) throws Exception{ + + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(newsMessage); + /* 基础信息校验 */ + msgSendBaseCheck(newsMessage); + /* 消息类型校验 */ + if (!Objects.equals(newsMessage.getMsgtype(), WxMsgTypeConstant.NEWS.getCode())) { + throw new Exception("图文消息类型必须为" + WxMsgTypeConstant.NEWS.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(newsMessage.getNews())) { + throw new Exception("消息内容不能为空!"); + } + /* 图文消息校验 */ + if (newsMessage.getNews().getArticles().isEmpty()) { + throw new Exception("图文消息内容不能为空!"); + } else { + newsMessage.setAgentid(sysWxAuditConfig.getAgentId()); + newsMessage.getNews().getArticles().forEach(article -> { + /* 消息标题校验 */ + if (StringUtils.isBlank(article.getTitle())) { + try { + throw new Exception("图文消息内容标题不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息url校验 */ + if (StringUtils.isBlank(article.getUrl())) { + try { + throw new Exception("图文消息内容点击url不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息url正确性校验 */ + article.setUrl(sysWxAuditConfig.getWxContextPath() + article.getUrl()); + if (!MatcherSolveUtils.checkInternetURL(article.getUrl())) { + try { + throw new Exception("消息发送消息内容,点击跳转url不是正确的网络url链接!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + /* 消息发送 */ + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(newsMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信图文消息(mpnews) + * + * @param mpNewsMessage 图文消息(mpnews)对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendMpNewsMsg(MpNewsMessage mpNewsMessage)throws Exception { + /* 基础信息校验 */ + msgSendBaseCheck(mpNewsMessage); + /* 消息类型校验 */ + if (!Objects.equals(mpNewsMessage.getMsgtype(), WxMsgTypeConstant.MPNEWS.getCode())) { + throw new Exception("图文(mpnews)消息类型必须为" + WxMsgTypeConstant.MPNEWS.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(mpNewsMessage.getMpnews())) { + throw new Exception("消息内容不能为空!"); + } + /* 图文消息校验 */ + if (mpNewsMessage.getMpnews().getArticles().isEmpty()) { + throw new Exception("图文消息内容不能为空!"); + } else { + mpNewsMessage.getMpnews().getArticles().forEach(article -> { + /* 消息标题校验 */ + if (StringUtils.isBlank(article.getTitle())) { + try { + throw new Exception("图文消息内容标题不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息图片媒体ID校验 */ + if (StringUtils.isBlank(article.getThumb_media_id())) { + try { + throw new Exception("图文消息内容图片媒体ID不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 消息内容校验 */ + if (StringUtils.isBlank(article.getContent())) { + try { + throw new Exception("图文消息内容能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(mpNewsMessage); + mpNewsMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(mpNewsMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信markdown消息 + * + * @param markdownMessage markdown消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendMarkdownMsg(MarkdownMessage markdownMessage) throws Exception{ + /* 基础信息校验 */ + msgSendBaseCheck(markdownMessage); + /* 消息类型校验 */ + if (!Objects.equals(markdownMessage.getMsgtype(), WxMsgTypeConstant.MARKDOWN.getCode())) { + throw new Exception("markdown消息消息类型必须为" + WxMsgTypeConstant.MARKDOWN.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(markdownMessage.getMarkdown())) { + throw new Exception("markdown消息内容能为空!"); + } + /* 消息内容校验 */ + if (StringUtils.isBlank(markdownMessage.getMarkdown().getContent())) { + throw new Exception("markdown消息内容能为空!"); + } + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(markdownMessage); + markdownMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(markdownMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信小程序通知消息 + * + * @param miniProgramNoticeMessage 小程序通知消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendMiniProgramNoticeMsg(MiniProgramNoticeMessage miniProgramNoticeMessage) throws Exception { + /* 基础信息校验 */ + if (StringUtils.isAllBlank(miniProgramNoticeMessage.getTouser(), miniProgramNoticeMessage.getToparty(), miniProgramNoticeMessage.getTotag())) { + throw new Exception("消息发送对象不能为空!"); + } + /* 消息类型校验 */ + if (!Objects.equals(miniProgramNoticeMessage.getMsgtype(), WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode())) { + throw new Exception("小程序通知消息消息类型必须为" + WxMsgTypeConstant.MINIPROGRAM_NOTICE.getCode() + "!"); + } + /* 消息内容校验 */ + if (Objects.isNull(miniProgramNoticeMessage.getMiniprogram_notice())) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序appid校验 */ + if (StringUtils.isBlank(miniProgramNoticeMessage.getMiniprogram_notice().getAppid())) { + throw new Exception("小程序appid,必须是与当前应用关联的小程序!"); + } + /* 小程序标题校验 */ + if (StringUtils.isBlank(miniProgramNoticeMessage.getMiniprogram_notice().getTitle())) { + throw new Exception("消息标题不能为空,长度限制4-12个汉字!"); + } + /* 小程序消息内容校验 */ + if (Objects.isNull(miniProgramNoticeMessage.getMiniprogram_notice().getContent_item())) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序消息内容校验 */ + if (miniProgramNoticeMessage.getMiniprogram_notice().getContent_item().isEmpty()) { + throw new Exception("消息内容不能为空!"); + } + /* 小程序消息内容校验key-value校验 */ + miniProgramNoticeMessage.getMiniprogram_notice().getContent_item().forEach(contentItem -> { + /* 小程序消息内容key校验 */ + if (StringUtils.isBlank(contentItem.getKey())) { + try { + throw new Exception("小程序消息内容key不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 小程序消息内容value校验 */ + if (StringUtils.isBlank(contentItem.getValue())) { + try { + throw new Exception("小程序消息内容value不能为空!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + /* 消息发送 */ + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(miniProgramNoticeMessage); + miniProgramNoticeMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(miniProgramNoticeMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 发送企业微信任务卡片消息 + * + * @param interactiveTaskCardMessage 任务卡片消息对象 + * @return R 消息发送响应对象 + */ + @Override + public CommonResult sendInteractiveTaskCardMsg(InteractiveTaskCardMessage interactiveTaskCardMessage) throws Exception { + SysWxAuditConfig sysWxAuditConfig = this.buildSysWxAuditConfig(interactiveTaskCardMessage); + /* 基础信息校验 */ + msgSendBaseCheck(interactiveTaskCardMessage); + /* 消息类型校验 */ + if (!Objects.equals(interactiveTaskCardMessage.getMsgtype(), WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode())) { + throw new Exception("任务卡片消息消息类型必须为" + WxMsgTypeConstant.INTERACTIVE_TASKCARD.getCode() + "!"); + } + /* 任务卡片消息内容校验 */ + if (Objects.isNull(interactiveTaskCardMessage.getInteractive_taskcard())) { + throw new Exception("消息内容不能为空!"); + } + /* 任务卡片消息内容标题校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + throw new Exception("任务卡片消息内容标题不能为空!"); + } + /* 任务卡片消息url校验 */ + interactiveTaskCardMessage.getInteractive_taskcard().setUrl(sysWxAuditConfig.getWxContextPath() + interactiveTaskCardMessage.getInteractive_taskcard().getUrl()); + if (!MatcherSolveUtils.checkInternetURL(interactiveTaskCardMessage.getInteractive_taskcard().getUrl())) { + throw new Exception("任务卡片消息内容标题不能为空,点击跳转url不是正确的网络url链接!"); + } + /* 任务卡片消息内容任务id校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTask_id())) { + throw new Exception("任务卡片消息内容任务id不能为空,任务id,同一个应用发送的任务卡片消息的任务id不能重复,只能由数字、字母和“_-@”组成,最长支持128字节!"); + } + /* 任务卡片消息内容按钮组校验 */ + if (Objects.isNull(interactiveTaskCardMessage.getInteractive_taskcard().getBtn())) { + throw new Exception("任务卡片消息内容按钮组不能为空!"); + } + /* 任务卡片消息内容按钮组校验 */ + interactiveTaskCardMessage.getInteractive_taskcard().getBtn().forEach(btn -> { + /* 任务卡片消息内容按钮组按钮key校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + try { + throw new Exception("任务卡片消息内容按钮组按钮key不能为空,按钮key值,用户点击后,会产生任务卡片回调事件,回调事件会带上该key值,只能由数字、字母和“_-@”组成,最长支持128字节!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /* 任务卡片消息内容按钮组阿牛name校验 */ + if (StringUtils.isBlank(interactiveTaskCardMessage.getInteractive_taskcard().getTitle())) { + try { + throw new Exception("任务卡片消息内容按钮组阿牛name不能为空,按钮名称,最长支持18个字节,超过则截断!"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + /* 消息发送 */ + interactiveTaskCardMessage.setAgentid(sysWxAuditConfig.getAgentId()); + JSONObject jsonObject = HttpClientUtils.httpPost(weiXinProperties.buildQySendMsgUrl(sysWxAuditConfig.getCorpId(), sysWxAuditConfig.getCorpSecret()), (JSONObject) JSONObject.toJSON(interactiveTaskCardMessage)); + return buildQywxMsgSendResult(jsonObject); + } + + /** + * 企业微信消息发送基础信息校验 + * + * @param baseMessage 企业微信消息体 + */ + private void msgSendBaseCheck(BaseMessage baseMessage) throws Exception{ + if (StringUtils.isAllBlank(baseMessage.getTouser(), baseMessage.getToparty(), baseMessage.getTotag())) { + throw new Exception("消息发送对象不能为空!"); + } + } + + /** + * 获取企业微信消息发送应用配置 + * + * @param baseMessage 企业微信消息体 + * @return SysWxAuditConfig 企业微信消息发送应用配置 + */ + private SysWxAuditConfig buildSysWxAuditConfig(BaseMessage baseMessage) throws Exception{ + return weiXinProperties.buildSysWxAuditConfig(baseMessage.getAppType()); + } + + /** + * 处理企业微信消息发送结果响应数据 + * @param jsonObject 企业微信消息发送结果对象 + * @return R 消息发送响应对象 + */ + private CommonResult buildQywxMsgSendResult(JSONObject jsonObject) { + if (Objects.equals(jsonObject.getString(WX_MSG_RESPONSE_CODE), WX_MSG_RESPONSE_SUCCESS_CODE)) { + return CommonResult.success(jsonObject); + } else { + return CommonResult.error(100029,"发送中铝e办失败{}").setData(jsonObject); + } + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysActiveMqServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysActiveMqServiceImpl.java new file mode 100644 index 00000000..8e36ae7e --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysActiveMqServiceImpl.java @@ -0,0 +1,100 @@ +package com.zt.plat.module.system.service.msg.impl; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zt.plat.framework.common.util.http.HttpClientUtils; +import com.zt.plat.framework.common.util.security.DESUtil; +import com.zt.plat.module.system.dal.dataobject.msg.SysActiveMq; +import com.zt.plat.module.system.dal.dataobject.msg.SysActiveMqLog; +import com.zt.plat.module.system.dal.mysql.msg.SysActiveMqDao; +import com.zt.plat.module.system.framework.sms.core.enums.AuditConstants; +import com.zt.plat.module.system.framework.sms.core.enums.JmsConstant; +import com.zt.plat.module.system.service.msg.ISysRocketMqService; +import com.zt.plat.module.system.service.msg.config.HttpUrlConfig; +import jakarta.annotation.Resource; +import org.apache.rocketmq.common.message.MessageExt; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; + +/** + * Activemq 消息队列 服务实现类 + * + * @author Dy + * @since 2021-07-22 + */ +@Service +@Transactional(rollbackFor = {Exception.class, Error.class}, propagation = Propagation.REQUIRES_NEW) +public class SysActiveMqServiceImpl extends ServiceImpl implements ISysRocketMqService { + + @Resource + private HttpUrlConfig httpUrlConfig; + + + /** + * 保存消息队列消费的消息信息 + * + * @param listenerClassName 队列监听类名 + * @param tm 消息对象 + * @param jmsType 消息类型(QUEUE-队列/TOPIC-主题) + */ + @Override + public void saveByTextMessage(String listenerClassName, MessageExt tm, String jmsType) throws Exception { + this.saveByTextMessage(listenerClassName, tm, jmsType, JmsConstant.CONSUME_SUCCESS.getCode(), "消费成功"); + } + + /** + * 保存消息队列消费的消息信息 + * + * @param listenerClassName 队列监听类名 + * @param tm 消息对象 + * @param jmsType 消息类型(QUEUE-队列/TOPIC-主题) + * @param consumeFlag 消费结果(success-成功/failure-失败) + * @param consumeMessage 消费失败消息 + */ + @Override + public void saveByTextMessage(String listenerClassName, MessageExt tm, String jmsType, String consumeFlag, String consumeMessage) throws Exception { + SysActiveMqLog sysActiveMqLog = new SysActiveMqLog(); + String json = new String(tm.getBody(), StandardCharsets.UTF_8); + com.alibaba.fastjson2.JSONObject jsonObject = JSON.parseObject(json); + sysActiveMqLog.setTitle(jsonObject.getString("title")) + .setDatetime(jsonObject.getString("datetime")) + .setPublisher(jsonObject.getString("publisher")) + .setContentType(jsonObject.getString("type")) + .setConsumeDatetime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .setTextContent(jsonObject.getString("content")) + .setListenerClassName(listenerClassName) + .setJmsType(jmsType) + .setConsumeFlag(consumeFlag) + .setConsumeMessage(consumeMessage); + this.baseMapper.insert(sysActiveMqLog); + } + + @Override + public void saveSunepsByTextMessage(String listenerClassName, MessageExt tm, String jmsType, String consumeFlag, String consumeMessage) throws Exception { + + SysActiveMq sysActiveMq = new SysActiveMq(); + String json = new String(tm.getBody(), StandardCharsets.UTF_8); + com.alibaba.fastjson2.JSONObject jsonObject = JSON.parseObject(json); + + sysActiveMq.setTitle(jsonObject.getString("title")) + .setDatetime(jsonObject.getString("datetime")) + .setPublisher(jsonObject.getString("publisher")) + .setContentType(jsonObject.getString("type")) + .setConsumeDatetime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .setTextContent(jsonObject.getString("content")) + .setListenerClassName(listenerClassName) + .setJmsType(jmsType) + .setConsumeFlag(consumeFlag) + .setConsumeMessage(consumeMessage); + String token = Arrays.toString(new DESUtil().encrypt(AuditConstants.TOKEN.getBytes())); + HttpClientUtils.httpPostByToken(httpUrlConfig.getHttpContextPath("suneps") + "/ieps/mobile/saveMqLog", JSONObject.toJSONString(sysActiveMq), token); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysWxAuditConfigImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysWxAuditConfigImpl.java new file mode 100644 index 00000000..0777169b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/msg/impl/SysWxAuditConfigImpl.java @@ -0,0 +1,66 @@ +package com.zt.plat.module.system.service.msg.impl; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zt.plat.module.system.dal.dataobject.msg.SysWxAuditConfig; +import com.zt.plat.module.system.service.msg.SysWxAuditConfigService; +import org.springframework.stereotype.Service; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +@Service +public class SysWxAuditConfigImpl implements SysWxAuditConfigService { + + + @Override + public boolean saveBatch(Collection entityList, int batchSize) { + return false; + } + + @Override + public boolean saveOrUpdateBatch(Collection entityList, int batchSize) { + return false; + } + + @Override + public boolean updateBatchById(Collection entityList, int batchSize) { + return false; + } + + @Override + public boolean saveOrUpdate(SysWxAuditConfig entity) { + return false; + } + + @Override + public SysWxAuditConfig getOne(Wrapper queryWrapper, boolean throwEx) { + return null; + } + + @Override + public Optional getOneOpt(Wrapper queryWrapper, boolean throwEx) { + return Optional.empty(); + } + + @Override + public Map getMap(Wrapper queryWrapper) { + return Map.of(); + } + + @Override + public V getObj(Wrapper queryWrapper, Function mapper) { + return null; + } + + @Override + public BaseMapper getBaseMapper() { + return null; + } + + @Override + public Class getEntityClass() { + return null; + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsSendServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsSendServiceImpl.java index 82db724e..2b27067c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsSendServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/sms/SmsSendServiceImpl.java @@ -96,7 +96,7 @@ public class SmsSendServiceImpl implements SmsSendService { String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams); Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams); - // 发送 MQ 消息,异步执行发送短信 + //TODO 发送 MQ 消息,异步执行发送短信 if (isSend) { smsProducer.sendSmsSendMessage(sendLogId, mobile, content, template.getChannelId(), template.getApiTemplateId(), newTemplateParams); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/util/StringSolveUtils.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/util/StringSolveUtils.java new file mode 100644 index 00000000..d5ec9692 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/util/StringSolveUtils.java @@ -0,0 +1,24 @@ +package com.zt.plat.module.system.util; + +import org.apache.commons.text.StringSubstitutor; +import java.util.Map; + +/** + * 字符串处理工具类 + * + * @author luzemin + */ +public class StringSolveUtils { + + /** + * 使用map集合替换字符串中${key} + * + * @param str 待替换字符串 + * @param substituteMap 替代品属性集合 + * @return 替换后字符串 + */ + public static String placeholderReplace(String str, Map substituteMap) { + StringSubstitutor stringSubstitutor = new StringSubstitutor(substituteMap); + return stringSubstitutor.replace(str); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/resources/config.properties b/zt-module-system/zt-module-system-server/src/main/resources/config.properties new file mode 100644 index 00000000..a34557c7 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/resources/config.properties @@ -0,0 +1,155 @@ +# ================= ????? (PSCS) ================= +# ?????????http url +pscs.commitUrl=/pscs/mobile/doSubmit +# ?????????http url +pscs.wxSubmitData=/pscs/mobile/doSubmitData +# ?????????????http url +pscs.commentUrl=/pscs/mobile/getApproveCommentById/ +# ?????????????http url +pscs.todoUrl=/pscs/mobile/getApproveToDoByUserName/ +# ?????????????http url +pscs.doneUrl=/pscs/mobile/getApproveDoneByUserName/ +# ?????????????http url +pscs.finishUrl=/pscs/mobile/getApproveVariablesById/ +# ???????????http url +pscs.storageUrl=http://cg.chncopper.com/storage/ +# ??????????????? +pscs.renderBpoContentUrl=/pscs/mobile/renderBpoContent +# ????????? +pscs.fineReportUrl=https://xs.chxz.com/FineReport/ReportServer +# ????????? +pscs.fileStorageUrl=https://xs.chxz.com/storage/ +# ??????? +pscs.fileViewUrl=https://xs.chxz.com/fv/onlinePreview +# ?????pscs???? +pscs.httpContextPath=https://szh.chxz.com/omc +# ?????pscs???????? +pscs.wxContextPath=http://xs.chxz.com/qywx + +# ================= ????? (BSSIM) ================= +# ?????????http url +bssim.commitUrl=/bssim/mobile/doSubmit +# ?????????????http url +bssim.commentUrl=/bssim/mobile/getApproveCommentById/ +# ?????????????http url +bssim.todoUrl=/bssim/mobile/getApproveToDoByUserName/ +# ?????????????http url +bssim.doneUrl=/bssim/mobile/getApproveDoneByUserName/ +# ?????????????http url +bssim.finishUrl=/bssim/mobile/getApproveVariablesById/ +# ???????????http url +bssim.storageUrl=http://cg.chncopper.com/storage/ +# ??????????????? +bssim.renderBpoContentUrl=/bssim/mobile/renderBpoContent +# ????????? +bssim.fineReportUrl=https://xs.chxz.com/FineReport/ReportServer +# ????????? +bssim.fileStorageUrl=https://xs.chxz.com/storage/ +# ??????? +bssim.fileViewUrl=https://xs.chxz.com/fv/onlinePreview +# ?????bssim???? +bssim.httpContextPath=https://szh.chxz.com/bssim +# ?????bssim???????? +bssim.wxContextPath=http://xs.chxz.com/qywx + +# ================= ???? (SUNEPS) ================= +# ????????http url +suneps.commitUrl=/ieps/mobile/doSubmit +# ????????????http url +suneps.commentUrl=/ieps/mobile/getApproveCommentById/ +# ????????????http url +suneps.todoUrl=/ieps/mobile/getApproveToDoByUserName/ +# ????????????http url +suneps.doneUrl=/ieps/mobile/getApproveDoneByUserName/ +# ??????????http url +suneps.storageUrl=https://cg.chncopper.com/storage/ +# ????suneps???? +suneps.httpContextPath=http://172.16.34.30/ieps +# ????suneps???????? +suneps.wxContextPath=http://xs.chxz.com/qywx + +# ================= ???? (MARKET) ================= +# ??????http url +market.commitUrl=/ieps/mobile/doSubmit +# ??????????http url +market.commentUrl=/ieps/mobile/getApproveCommentById/ +# ?????????http url +market.todoUrl=/ieps/mobile/getApproveToDoByUserName/ +# ??????????http url +market.doneUrl=/ieps/mobile/getApproveDoneByUserName/ +# ????market???? +market.httpContextPath=http://172.16.34.30/market +# ????market???????? +market.wxContextPath=http://xs.chxz.com/qywx + +# ================= ??????? (DZXS) ================= +# ???????????http url +dzxs.commitUrl=/dzxs/mobile/doSubmit +# ???????????????http url +dzxs.commentUrl=/dzxs/mobile/getApproveCommentById/ +# ???????????????http url +dzxs.todoUrl=/dzxs/mobile/getApproveToDoByUserName/ +# ???????????????http url +dzxs.doneUrl=/dzxs/mobile/getApproveDoneByUserName/ +# ???????????????http url +dzxs.finishUrl=/dzxs/mobile/getApproveVariablesById/ +# ?????????????http url +dzxs.storageUrl=http://cg.chncopper.com/storage/ +# ??????????? +dzxs.fineReportUrl=https://xs.chxz.com/FineReport/ReportServer +# ????????? +dzxs.fileStorageUrl=https://xs.chxz.com/storage/ +# ??????? +dzxs.fileViewUrl=https://xs.chxz.com/fv/onlinePreview +# ???????dzxs???? +dzxs.httpContextPath=http://10.198.0.197 +# ???????dzxs???????? +dzxs.wxContextPath=http://xs.chxz.com/qywx + +# ================= ??????? (BOLI) ================= +# ???????boli????http url +boli.commitUrl=/pscs/mobile/doSubmit +# ???????boli????????http url +boli.commentUrl=/pscs/mobile/getApproveCommentById/ +# ???????boli????????http url +boli.todoUrl=/pscs/mobile/getApproveToDoByUserName/ +# ???????boli????????http url +boli.doneUrl=/pscs/mobile/getApproveDoneByUserName/ +# ???????boli????????http url +boli.finishUrl=/pscs/mobile/getApproveVariablesById/ +# ???????boli??????http url +boli.storageUrl=http://cg.chncopper.com/storage/ +# ???????boli???? +boli.fineReportUrl=https://xs.chxz.com/FineReport/ReportServer +# ????????? +boli.fileStorageUrl=https://xs.chxz.com/storage/ +# ??????? +boli.fileViewUrl=https://xs.chxz.com/fv/onlinePreview +# ???????boli???? +boli.httpContextPath=https://szh.chxz.com/boli +# ???????boli???????? +boli.wxContextPath=http://xs.chxz.com/qywx + +# ================= ??????? (LMPS) ================= +# ???????????http url +lmps.commitUrl=/lmps/mobile/doSubmit +# ???????????????http url +lmps.commentUrl=/lmps/mobile/getApproveCommentById/ +# ???????????????http url +lmps.todoUrl=/lmps/mobile/getApproveToDoByUserName/ +# ???????????????http url +lmps.doneUrl=/lmps/mobile/getApproveDoneByUserName/ +# ???????????????http url +lmps.finishUrl=/lmps/mobile/getApproveVariablesById/ +# ?????????????http url +lmps.storageUrl=http://cg.chncopper.com/storage/ +# ??????????? +lmps.fineReportUrl=http://xs.chxz.com/FineReport/ReportServer +# ????????? +lmps.fileStorageUrl=https://xs.chxz.com/storage/ +# ??????? +lmps.fileViewUrl=https://xs.chxz.com/fv/onlinePreview +# ???????lmps???? +lmps.httpContextPath=https://szh.chxz.com/lmps +# ???????lmps???????? +lmps.wxContextPath=http://xs.chxz.com/qywx \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/main/resources/qywx.properties b/zt-module-system/zt-module-system-server/src/main/resources/qywx.properties new file mode 100644 index 00000000..41f49690 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/resources/qywx.properties @@ -0,0 +1,43 @@ +# ================= ?????? ================= + +# ????-??access_token??url +qyAccessTokenUrl=https://qw.chinalco.com.cn:8443/cgi-bin/gettoken?corpid=CORP_ID&corpsecret=CORP_SECRET + +# ????-??????url +qySendMsgUrl=https://qw.chinalco.com.cn:8443/cgi-bin/message/send?access_token=ACCESS_TOKEN + +# ????-????????Oauth2????url +qyAuthorizeUrl=https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&agentid=AGENTID&state=STATE#wechat_redirect + +# ????-????????????url +qyUserInfoUrl=https://qw.chinalco.com.cn:8443/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE + +# ????url +qyUserUrl=https://qw.chinalco.com.cn:8443/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USER_ID + +# ????-??????url +qyUploadTempFileUrl=https://qw.chinalco.com.cn:8443/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE + +# ????-????url +qyDeptCreateUrl= + +# ????-????url +qyDeptUpdateUrl= + +# ????-????url +qyDeptDeleteUrl= + +# ????-??????url +qyDeptQueryListUrl=https://qw.chinalco.com.cn:8443/cgi-bin/department/list?access_token=ACCESS_TOKEN&id=DEPARTMENT_ID + +# ????-??????url +qyMenuCreateUrl= + +# ????-??????url +qyMenuDeleteUrl= + +# ????-??????url +qyMenuQueryUrl= + +# ????-ticket??url +ticketUrl=https://qw.chinalco.com.cn:8443/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN \ No newline at end of file diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java new file mode 100644 index 00000000..bfa1ca09 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java @@ -0,0 +1,305 @@ +package com.zt.plat.module.system.service.msg; + +import cn.hutool.core.map.MapUtil; +import com.zt.plat.framework.common.core.KeyValue; +import com.zt.plat.framework.common.enums.CommonStatusEnum; +import com.zt.plat.framework.common.enums.UserTypeEnum; +import com.zt.plat.module.system.framework.sms.core.client.SmsClient; +import com.zt.plat.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import com.zt.plat.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import com.zt.plat.framework.test.core.ut.BaseMockitoUnitTest; +import com.zt.plat.module.system.dal.dataobject.sms.SmsChannelDO; +import com.zt.plat.module.system.dal.dataobject.sms.SmsTemplateDO; +import com.zt.plat.module.system.dal.dataobject.user.AdminUserDO; +import com.zt.plat.module.system.mq.message.sms.SmsSendMessage; +import com.zt.plat.module.system.mq.producer.sms.SmsProducer; +import com.zt.plat.module.system.service.member.MemberService; +import com.zt.plat.module.system.service.sms.SmsChannelService; +import com.zt.plat.module.system.service.sms.SmsLogService; +import com.zt.plat.module.system.service.sms.SmsSendServiceImpl; +import com.zt.plat.module.system.service.sms.SmsTemplateService; +import com.zt.plat.module.system.service.user.AdminUserService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static cn.hutool.core.util.RandomUtil.randomEle; +import static com.zt.plat.framework.test.core.util.AssertUtils.assertServiceException; +import static com.zt.plat.framework.test.core.util.RandomUtils.*; +import static com.zt.plat.module.system.enums.ErrorCodeConstants.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +public class SmsSendServiceImplTest extends BaseMockitoUnitTest { + + @InjectMocks + private SmsSendServiceImpl smsSendService; + + @Mock + private AdminUserService adminUserService; + @Mock + private MemberService memberService; + @Mock + private SmsChannelService smsChannelService; + @Mock + private SmsTemplateService smsTemplateService; + @Mock + private SmsLogService smsLogService; + @Mock + private SmsProducer smsProducer; + + @Test + public void testSendSingleSmsToAdmin() { + // 准备参数 + Long userId = randomLongId(); + String templateCode = randomString(); + Map templateParams = MapUtil.builder().put("code", "1234") + .put("op", "login").build(); + // mock adminUserService 的方法 + AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setMobile("15601691300")); + when(adminUserService.getUser(eq(userId))).thenReturn(user); + + // mock SmsTemplateService 的方法 + SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setContent("验证码为{code}, 操作为{op}"); + o.setParams(Lists.newArrayList("code", "op")); + }); + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); + String content = randomString(); + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams))) + .thenReturn(content); + // mock SmsChannelService 的方法 + SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel); + // mock SmsLogService 的方法 + Long smsLogId = randomLongId(); + when(smsLogService.createSmsLog(eq(user.getMobile()), eq(userId), eq(UserTypeEnum.ADMIN.getValue()), eq(Boolean.TRUE), eq(template), + eq(content), eq(templateParams))).thenReturn(smsLogId); + + // 调用 + Long resultSmsLogId = smsSendService.sendSingleSmsToAdmin(null, userId, templateCode, templateParams); + // 断言 + assertEquals(smsLogId, resultSmsLogId); + // 断言调用 + verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()), eq(content), + eq(template.getChannelId()), eq(template.getApiTemplateId()), + eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login")))); + } + + @Test + public void testSendSingleSmsToUser() { + // 准备参数 + Long userId = randomLongId(); + String templateCode = randomString(); + Map templateParams = MapUtil.builder().put("code", "1234") + .put("op", "login").build(); + // mock memberService 的方法 + String mobile = "15601691300"; + when(memberService.getMemberUserMobile(eq(userId))).thenReturn(mobile); + + // mock SmsTemplateService 的方法 + SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setContent("验证码为{code}, 操作为{op}"); + o.setParams(Lists.newArrayList("code", "op")); + }); + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); + String content = randomString(); + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams))) + .thenReturn(content); + // mock SmsChannelService 的方法 + SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel); + // mock SmsLogService 的方法 + Long smsLogId = randomLongId(); + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(UserTypeEnum.MEMBER.getValue()), eq(Boolean.TRUE), eq(template), + eq(content), eq(templateParams))).thenReturn(smsLogId); + + // 调用 + Long resultSmsLogId = smsSendService.sendSingleSmsToMember(null, userId, templateCode, templateParams); + // 断言 + assertEquals(smsLogId, resultSmsLogId); + // 断言调用 + verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(content), + eq(template.getChannelId()), eq(template.getApiTemplateId()), + eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login")))); + } + + /** + * 发送成功,当短信模板开启时 + */ + @Test + public void testSendSingleSms_successWhenSmsTemplateEnable() { + // 准备参数 + String mobile = randomString(); + Long userId = randomLongId(); + Integer userType = randomEle(UserTypeEnum.values()).getValue(); + String templateCode = randomString(); + Map templateParams = MapUtil.builder().put("code", "1234") + .put("op", "login").build(); + // mock SmsTemplateService 的方法 + SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> { + o.setStatus(CommonStatusEnum.ENABLE.getStatus()); + o.setContent("验证码为{code}, 操作为{op}"); + o.setParams(Lists.newArrayList("code", "op")); + }); + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); + String content = randomString(); + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams))) + .thenReturn(content); + // mock SmsChannelService 的方法 + SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel); + // mock SmsLogService 的方法 + Long smsLogId = randomLongId(); + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.TRUE), eq(template), + eq(content), eq(templateParams))).thenReturn(smsLogId); + + // 调用 + Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams); + // 断言 + assertEquals(smsLogId, resultSmsLogId); + // 断言调用 + verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile), eq(content), + eq(template.getChannelId()), eq(template.getApiTemplateId()), + eq(Lists.newArrayList(new KeyValue<>("code", "1234"), new KeyValue<>("op", "login")))); + } + + /** + * 发送成功,当短信模板关闭时 + */ + @Test + public void testSendSingleSms_successWhenSmsTemplateDisable() { + // 准备参数 + String mobile = randomString(); + Long userId = randomLongId(); + Integer userType = randomEle(UserTypeEnum.values()).getValue(); + String templateCode = randomString(); + Map templateParams = MapUtil.builder().put("code", "1234") + .put("op", "login").build(); + // mock SmsTemplateService 的方法 + SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> { + o.setStatus(CommonStatusEnum.DISABLE.getStatus()); + o.setContent("验证码为{code}, 操作为{op}"); + o.setParams(Lists.newArrayList("code", "op")); + }); + when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template); + String content = randomString(); + when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams))) + .thenReturn(content); + // mock SmsChannelService 的方法 + SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())); + when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel); + // mock SmsLogService 的方法 + Long smsLogId = randomLongId(); + when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.FALSE), eq(template), + eq(content), eq(templateParams))).thenReturn(smsLogId); + + // 调用 + Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams); + // 断言 + assertEquals(smsLogId, resultSmsLogId); + // 断言调用 + verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), anyString(), anyString(), + anyLong(), any(), anyList()); + } + + @Test + public void testCheckSmsTemplateValid_notExists() { + // 准备参数 + String templateCode = randomString(); + // mock 方法 + + // 调用,并断言异常 + assertServiceException(() -> smsSendService.validateSmsTemplate(templateCode), + SMS_SEND_TEMPLATE_NOT_EXISTS); + } + + @Test + public void testBuildTemplateParams_paramMiss() { + // 准备参数 + SmsTemplateDO template = randomPojo(SmsTemplateDO.class, + o -> o.setParams(Lists.newArrayList("code"))); + Map templateParams = new HashMap<>(); + // mock 方法 + + // 调用,并断言异常 + assertServiceException(() -> smsSendService.buildTemplateParams(template, templateParams), + SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code"); + } + + @Test + public void testCheckMobile_notExists() { + // 准备参数 + // mock 方法 + + // 调用,并断言异常 + assertServiceException(() -> smsSendService.validateMobile(null), + SMS_SEND_MOBILE_NOT_EXISTS); + } + + @Test + public void testSendBatchNotify() { + // 准备参数 + // mock 方法 + + // 调用 + UnsupportedOperationException exception = Assertions.assertThrows( + UnsupportedOperationException.class, + () -> smsSendService.sendBatchSms(null, null, null, null, null) + ); + // 断言 + assertEquals("暂时不支持该操作,感兴趣可以实现该功能哟!", exception.getMessage()); + } + + @Test + @SuppressWarnings("unchecked") + public void testDoSendSms() throws Throwable { + // 准备参数 + SmsSendMessage message = randomPojo(SmsSendMessage.class, o -> o.setContent(randomString())); + // mock SmsClientFactory 的方法 + SmsClient smsClient = spy(SmsClient.class); + when(smsChannelService.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient); + // mock SmsClient 的方法 + SmsSendRespDTO sendResult = randomPojo(SmsSendRespDTO.class); + when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getContent()), eq(message.getApiTemplateId()), + eq(message.getTemplateParams()))).thenReturn(sendResult); + + // 调用 + smsSendService.doSendSms(message); + // 断言 + verify(smsLogService).updateSmsSendResult(eq(message.getLogId()), + eq(sendResult.getSuccess()), eq(sendResult.getApiCode()), + eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo())); + } + + @Test + public void testReceiveSmsStatus() throws Throwable { + // 准备参数 + String channelCode = randomString(); + String text = randomString(); + // mock SmsClientFactory 的方法 + SmsClient smsClient = spy(SmsClient.class); + when(smsChannelService.getSmsClient(eq(channelCode))).thenReturn(smsClient); + // mock SmsClient 的方法 + List receiveResults = randomPojoList(SmsReceiveRespDTO.class); + when(smsClient.parseSmsReceiveStatus(eq(text))).thenReturn(receiveResults); + + // 调用 + smsSendService.receiveSmsStatus(channelCode, text); + // 断言 + for (SmsReceiveRespDTO result : receiveResults) { + verify(smsLogService).updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()), + eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorMsg())); + } + } + +} From 6644710fe0f93b483cbb782ba2c24cd138a9ae3e Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Mon, 12 Jan 2026 18:36:39 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E6=8A=8A-server=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BAjar=E5=8C=85,=E6=96=B0=E5=A2=9E=20server-app?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E4=BD=9C=E4=B8=BA=E5=90=AF=E5=8A=A8=E5=99=A8?= =?UTF-8?q?=20http://172.16.46.63:31560/index.php=3Fm=3Dtask&f=3Dview&task?= =?UTF-8?q?ID=3D699?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- zt-module-databus/pom.xml | 1 + .../zt-module-databus-server-app/pom.xml | 48 +++++++++++++++++++ .../databus/DatabusServerApplication.java | 0 .../src/main/resources/application-dev.yml | 0 .../src/main/resources/application-local.yml | 0 .../src/main/resources/application.yml | 0 .../zt-module-databus-server/pom.xml | 20 -------- zt-module-infra/pom.xml | 1 + .../Dockerfile | 6 +-- .../zt-module-infra-server-app/pom.xml | 48 +++++++++++++++++++ .../module/infra/InfraServerApplication.java | 0 .../src/main/resources/application-dev.yaml | 0 .../src/main/resources/application-local.yaml | 0 .../src/main/resources/application.yaml | 0 .../src/main/resources/logback-spring.xml | 0 .../zt-module-infra-server/pom.xml | 19 -------- zt-module-report/pom.xml | 1 + .../Dockerfile | 6 +-- .../zt-module-report-server-app/pom.xml | 48 +++++++++++++++++++ .../report/ReportServerApplication.java | 0 .../src/main/resources/application-dev.yaml | 0 .../src/main/resources/application-local.yaml | 0 .../src/main/resources/application.yaml | 0 .../src/main/resources/logback-spring.xml | 0 .../zt-module-report-server/pom.xml | 19 -------- zt-module-system/pom.xml | 1 + .../Dockerfile | 6 +-- .../zt-module-system-server-app/pom.xml | 48 +++++++++++++++++++ .../system/SystemServerApplication.java | 0 .../src/main/resources/application-dev.yaml | 0 .../src/main/resources/application-local.yaml | 0 .../src/main/resources/application.yaml | 0 .../src/main/resources/logback-spring.xml | 0 .../zt-module-system-server/pom.xml | 19 -------- zt-module-template/pom.xml | 1 + .../Dockerfile | 6 +-- .../zt-module-template-server-app/pom.xml | 48 +++++++++++++++++++ .../template/TemplateServerApplication.java | 0 .../src/main/resources/application-dev.yaml | 0 .../src/main/resources/application-local.yaml | 0 .../src/main/resources/application.yaml | 0 .../zt-module-template-server/pom.xml | 19 -------- 42 files changed, 257 insertions(+), 108 deletions(-) create mode 100644 zt-module-databus/zt-module-databus-server-app/pom.xml rename zt-module-databus/{zt-module-databus-server => zt-module-databus-server-app}/src/main/java/com/zt/plat/module/databus/DatabusServerApplication.java (100%) rename zt-module-databus/{zt-module-databus-server => zt-module-databus-server-app}/src/main/resources/application-dev.yml (100%) rename zt-module-databus/{zt-module-databus-server => zt-module-databus-server-app}/src/main/resources/application-local.yml (100%) rename zt-module-databus/{zt-module-databus-server => zt-module-databus-server-app}/src/main/resources/application.yml (100%) rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/Dockerfile (79%) create mode 100644 zt-module-infra/zt-module-infra-server-app/pom.xml rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/src/main/java/com/zt/plat/module/infra/InfraServerApplication.java (100%) rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/src/main/resources/application-dev.yaml (100%) rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/src/main/resources/application-local.yaml (100%) rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/src/main/resources/application.yaml (100%) rename zt-module-infra/{zt-module-infra-server => zt-module-infra-server-app}/src/main/resources/logback-spring.xml (100%) rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/Dockerfile (78%) create mode 100644 zt-module-report/zt-module-report-server-app/pom.xml rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/src/main/java/com/zt/plat/module/report/ReportServerApplication.java (100%) rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/src/main/resources/application-dev.yaml (100%) rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/src/main/resources/application-local.yaml (100%) rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/src/main/resources/application.yaml (100%) rename zt-module-report/{zt-module-report-server => zt-module-report-server-app}/src/main/resources/logback-spring.xml (100%) rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/Dockerfile (78%) create mode 100644 zt-module-system/zt-module-system-server-app/pom.xml rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/src/main/java/com/zt/plat/module/system/SystemServerApplication.java (100%) rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/src/main/resources/application-dev.yaml (100%) rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/src/main/resources/application-local.yaml (100%) rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/src/main/resources/application.yaml (100%) rename zt-module-system/{zt-module-system-server => zt-module-system-server-app}/src/main/resources/logback-spring.xml (100%) rename zt-module-template/{zt-module-template-server => zt-module-template-server-app}/Dockerfile (77%) create mode 100644 zt-module-template/zt-module-template-server-app/pom.xml rename zt-module-template/{zt-module-template-server => zt-module-template-server-app}/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java (100%) rename zt-module-template/{zt-module-template-server => zt-module-template-server-app}/src/main/resources/application-dev.yaml (100%) rename zt-module-template/{zt-module-template-server => zt-module-template-server-app}/src/main/resources/application-local.yaml (100%) rename zt-module-template/{zt-module-template-server => zt-module-template-server-app}/src/main/resources/application.yaml (100%) diff --git a/zt-module-databus/pom.xml b/zt-module-databus/pom.xml index bce8b908..904627a5 100644 --- a/zt-module-databus/pom.xml +++ b/zt-module-databus/pom.xml @@ -10,6 +10,7 @@ zt-module-databus-api zt-module-databus-server + zt-module-databus-server-app 4.0.0 diff --git a/zt-module-databus/zt-module-databus-server-app/pom.xml b/zt-module-databus/zt-module-databus-server-app/pom.xml new file mode 100644 index 00000000..fe5c889b --- /dev/null +++ b/zt-module-databus/zt-module-databus-server-app/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + zt-module-databus + com.zt.plat + ${revision} + + zt-module-databus-server-app + jar + ${project.artifactId} + + Databus 模块启动器。 + + + + + + + com.zt.plat + zt-module-databus-server + ${revision} + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/DatabusServerApplication.java b/zt-module-databus/zt-module-databus-server-app/src/main/java/com/zt/plat/module/databus/DatabusServerApplication.java similarity index 100% rename from zt-module-databus/zt-module-databus-server/src/main/java/com/zt/plat/module/databus/DatabusServerApplication.java rename to zt-module-databus/zt-module-databus-server-app/src/main/java/com/zt/plat/module/databus/DatabusServerApplication.java diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml b/zt-module-databus/zt-module-databus-server-app/src/main/resources/application-dev.yml similarity index 100% rename from zt-module-databus/zt-module-databus-server/src/main/resources/application-dev.yml rename to zt-module-databus/zt-module-databus-server-app/src/main/resources/application-dev.yml diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml b/zt-module-databus/zt-module-databus-server-app/src/main/resources/application-local.yml similarity index 100% rename from zt-module-databus/zt-module-databus-server/src/main/resources/application-local.yml rename to zt-module-databus/zt-module-databus-server-app/src/main/resources/application-local.yml diff --git a/zt-module-databus/zt-module-databus-server/src/main/resources/application.yml b/zt-module-databus/zt-module-databus-server-app/src/main/resources/application.yml similarity index 100% rename from zt-module-databus/zt-module-databus-server/src/main/resources/application.yml rename to zt-module-databus/zt-module-databus-server-app/src/main/resources/application.yml diff --git a/zt-module-databus/zt-module-databus-server/pom.xml b/zt-module-databus/zt-module-databus-server/pom.xml index ceec001c..5f2b3e46 100644 --- a/zt-module-databus/zt-module-databus-server/pom.xml +++ b/zt-module-databus/zt-module-databus-server/pom.xml @@ -192,24 +192,4 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - - diff --git a/zt-module-infra/pom.xml b/zt-module-infra/pom.xml index 9449137e..843c64bc 100644 --- a/zt-module-infra/pom.xml +++ b/zt-module-infra/pom.xml @@ -11,6 +11,7 @@ zt-module-infra-api zt-module-infra-server + zt-module-infra-server-app zt-module-infra pom diff --git a/zt-module-infra/zt-module-infra-server/Dockerfile b/zt-module-infra/zt-module-infra-server-app/Dockerfile similarity index 79% rename from zt-module-infra/zt-module-infra-server/Dockerfile rename to zt-module-infra/zt-module-infra-server-app/Dockerfile index 49d73456..76f0282c 100644 --- a/zt-module-infra/zt-module-infra-server/Dockerfile +++ b/zt-module-infra/zt-module-infra-server-app/Dockerfile @@ -3,10 +3,10 @@ FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre ## 创建目录,并使用它作为工作目录 -RUN mkdir -p /zt-module-infra-server -WORKDIR /zt-module-infra-server +RUN mkdir -p /zt-module-infra-server-app +WORKDIR /zt-module-infra-server-app ## 将后端项目的 Jar 文件,复制到镜像中 -COPY ./target/zt-module-infra-server.jar app.jar +COPY ../zt-module-infra-server/target/zt-module-infra-server-app.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 diff --git a/zt-module-infra/zt-module-infra-server-app/pom.xml b/zt-module-infra/zt-module-infra-server-app/pom.xml new file mode 100644 index 00000000..eddab4ec --- /dev/null +++ b/zt-module-infra/zt-module-infra-server-app/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + zt-module-infra + com.zt.plat + ${revision} + + zt-module-infra-server-app + jar + ${project.artifactId} + + infra 模块启动器。 + + + + + + + com.zt.plat + zt-module-infra-server + ${revision} + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/InfraServerApplication.java b/zt-module-infra/zt-module-infra-server-app/src/main/java/com/zt/plat/module/infra/InfraServerApplication.java similarity index 100% rename from zt-module-infra/zt-module-infra-server/src/main/java/com/zt/plat/module/infra/InfraServerApplication.java rename to zt-module-infra/zt-module-infra-server-app/src/main/java/com/zt/plat/module/infra/InfraServerApplication.java diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/application-dev.yaml b/zt-module-infra/zt-module-infra-server-app/src/main/resources/application-dev.yaml similarity index 100% rename from zt-module-infra/zt-module-infra-server/src/main/resources/application-dev.yaml rename to zt-module-infra/zt-module-infra-server-app/src/main/resources/application-dev.yaml diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/application-local.yaml b/zt-module-infra/zt-module-infra-server-app/src/main/resources/application-local.yaml similarity index 100% rename from zt-module-infra/zt-module-infra-server/src/main/resources/application-local.yaml rename to zt-module-infra/zt-module-infra-server-app/src/main/resources/application-local.yaml diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml b/zt-module-infra/zt-module-infra-server-app/src/main/resources/application.yaml similarity index 100% rename from zt-module-infra/zt-module-infra-server/src/main/resources/application.yaml rename to zt-module-infra/zt-module-infra-server-app/src/main/resources/application.yaml diff --git a/zt-module-infra/zt-module-infra-server/src/main/resources/logback-spring.xml b/zt-module-infra/zt-module-infra-server-app/src/main/resources/logback-spring.xml similarity index 100% rename from zt-module-infra/zt-module-infra-server/src/main/resources/logback-spring.xml rename to zt-module-infra/zt-module-infra-server-app/src/main/resources/logback-spring.xml diff --git a/zt-module-infra/zt-module-infra-server/pom.xml b/zt-module-infra/zt-module-infra-server/pom.xml index 14aea4ae..74a12724 100644 --- a/zt-module-infra/zt-module-infra-server/pom.xml +++ b/zt-module-infra/zt-module-infra-server/pom.xml @@ -149,24 +149,5 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - diff --git a/zt-module-report/pom.xml b/zt-module-report/pom.xml index ed8f8079..5068a3d5 100644 --- a/zt-module-report/pom.xml +++ b/zt-module-report/pom.xml @@ -11,6 +11,7 @@ zt-module-report-api zt-module-report-server + zt-module-report-server-app zt-module-report pom diff --git a/zt-module-report/zt-module-report-server/Dockerfile b/zt-module-report/zt-module-report-server-app/Dockerfile similarity index 78% rename from zt-module-report/zt-module-report-server/Dockerfile rename to zt-module-report/zt-module-report-server-app/Dockerfile index 0a9a86cf..6e5aa793 100644 --- a/zt-module-report/zt-module-report-server/Dockerfile +++ b/zt-module-report/zt-module-report-server-app/Dockerfile @@ -3,10 +3,10 @@ FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre ## 创建目录,并使用它作为工作目录 -RUN mkdir -p /zt-module-report-server -WORKDIR /zt-module-report-server +RUN mkdir -p /zt-module-report-server-app +WORKDIR /zt-module-report-server-app ## 将后端项目的 Jar 文件,复制到镜像中 -COPY ./target/zt-module-report-server.jar app.jar +COPY ../zt-module-report-server/target/zt-module-report-server-app.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 diff --git a/zt-module-report/zt-module-report-server-app/pom.xml b/zt-module-report/zt-module-report-server-app/pom.xml new file mode 100644 index 00000000..504549b2 --- /dev/null +++ b/zt-module-report/zt-module-report-server-app/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + zt-module-report + com.zt.plat + ${revision} + + zt-module-report-server-app + jar + ${project.artifactId} + + report 模块启动器。 + + + + + + + com.zt.plat + zt-module-report-server + ${revision} + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-report/zt-module-report-server/src/main/java/com/zt/plat/module/report/ReportServerApplication.java b/zt-module-report/zt-module-report-server-app/src/main/java/com/zt/plat/module/report/ReportServerApplication.java similarity index 100% rename from zt-module-report/zt-module-report-server/src/main/java/com/zt/plat/module/report/ReportServerApplication.java rename to zt-module-report/zt-module-report-server-app/src/main/java/com/zt/plat/module/report/ReportServerApplication.java diff --git a/zt-module-report/zt-module-report-server/src/main/resources/application-dev.yaml b/zt-module-report/zt-module-report-server-app/src/main/resources/application-dev.yaml similarity index 100% rename from zt-module-report/zt-module-report-server/src/main/resources/application-dev.yaml rename to zt-module-report/zt-module-report-server-app/src/main/resources/application-dev.yaml diff --git a/zt-module-report/zt-module-report-server/src/main/resources/application-local.yaml b/zt-module-report/zt-module-report-server-app/src/main/resources/application-local.yaml similarity index 100% rename from zt-module-report/zt-module-report-server/src/main/resources/application-local.yaml rename to zt-module-report/zt-module-report-server-app/src/main/resources/application-local.yaml diff --git a/zt-module-report/zt-module-report-server/src/main/resources/application.yaml b/zt-module-report/zt-module-report-server-app/src/main/resources/application.yaml similarity index 100% rename from zt-module-report/zt-module-report-server/src/main/resources/application.yaml rename to zt-module-report/zt-module-report-server-app/src/main/resources/application.yaml diff --git a/zt-module-report/zt-module-report-server/src/main/resources/logback-spring.xml b/zt-module-report/zt-module-report-server-app/src/main/resources/logback-spring.xml similarity index 100% rename from zt-module-report/zt-module-report-server/src/main/resources/logback-spring.xml rename to zt-module-report/zt-module-report-server-app/src/main/resources/logback-spring.xml diff --git a/zt-module-report/zt-module-report-server/pom.xml b/zt-module-report/zt-module-report-server/pom.xml index 0b259188..981e64fc 100644 --- a/zt-module-report/zt-module-report-server/pom.xml +++ b/zt-module-report/zt-module-report-server/pom.xml @@ -111,23 +111,4 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - diff --git a/zt-module-system/pom.xml b/zt-module-system/pom.xml index 6e186e9c..246b7be0 100644 --- a/zt-module-system/pom.xml +++ b/zt-module-system/pom.xml @@ -11,6 +11,7 @@ zt-module-system-api zt-module-system-server + zt-module-system-server-app zt-module-system pom diff --git a/zt-module-system/zt-module-system-server/Dockerfile b/zt-module-system/zt-module-system-server-app/Dockerfile similarity index 78% rename from zt-module-system/zt-module-system-server/Dockerfile rename to zt-module-system/zt-module-system-server-app/Dockerfile index 6e8a12d8..611da8b3 100644 --- a/zt-module-system/zt-module-system-server/Dockerfile +++ b/zt-module-system/zt-module-system-server-app/Dockerfile @@ -3,10 +3,10 @@ FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre ## 创建目录,并使用它作为工作目录 -RUN mkdir -p /zt-module-system-server -WORKDIR /zt-module-system-server +RUN mkdir -p /zt-module-system-server-app +WORKDIR /zt-module-system-server-app ## 将后端项目的 Jar 文件,复制到镜像中 -COPY ./target/zt-module-system-server.jar app.jar +COPY ../zt-module-system-server/target/zt-module-system-server-app.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 diff --git a/zt-module-system/zt-module-system-server-app/pom.xml b/zt-module-system/zt-module-system-server-app/pom.xml new file mode 100644 index 00000000..247a6418 --- /dev/null +++ b/zt-module-system/zt-module-system-server-app/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + zt-module-system + com.zt.plat + ${revision} + + zt-module-system-server-app + jar + ${project.artifactId} + + system 模块启动器。 + + + + + + + com.zt.plat + zt-module-system-server + ${revision} + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/SystemServerApplication.java b/zt-module-system/zt-module-system-server-app/src/main/java/com/zt/plat/module/system/SystemServerApplication.java similarity index 100% rename from zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/SystemServerApplication.java rename to zt-module-system/zt-module-system-server-app/src/main/java/com/zt/plat/module/system/SystemServerApplication.java diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml b/zt-module-system/zt-module-system-server-app/src/main/resources/application-dev.yaml similarity index 100% rename from zt-module-system/zt-module-system-server/src/main/resources/application-dev.yaml rename to zt-module-system/zt-module-system-server-app/src/main/resources/application-dev.yaml diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml b/zt-module-system/zt-module-system-server-app/src/main/resources/application-local.yaml similarity index 100% rename from zt-module-system/zt-module-system-server/src/main/resources/application-local.yaml rename to zt-module-system/zt-module-system-server-app/src/main/resources/application-local.yaml diff --git a/zt-module-system/zt-module-system-server/src/main/resources/application.yaml b/zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml similarity index 100% rename from zt-module-system/zt-module-system-server/src/main/resources/application.yaml rename to zt-module-system/zt-module-system-server-app/src/main/resources/application.yaml diff --git a/zt-module-system/zt-module-system-server/src/main/resources/logback-spring.xml b/zt-module-system/zt-module-system-server-app/src/main/resources/logback-spring.xml similarity index 100% rename from zt-module-system/zt-module-system-server/src/main/resources/logback-spring.xml rename to zt-module-system/zt-module-system-server-app/src/main/resources/logback-spring.xml diff --git a/zt-module-system/zt-module-system-server/pom.xml b/zt-module-system/zt-module-system-server/pom.xml index 71cc87f0..af27a90f 100644 --- a/zt-module-system/zt-module-system-server/pom.xml +++ b/zt-module-system/zt-module-system-server/pom.xml @@ -193,24 +193,5 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - diff --git a/zt-module-template/pom.xml b/zt-module-template/pom.xml index 108be758..41ade4e4 100644 --- a/zt-module-template/pom.xml +++ b/zt-module-template/pom.xml @@ -10,6 +10,7 @@ zt-module-template-api zt-module-template-server + zt-module-template-server-app 4.0.0 diff --git a/zt-module-template/zt-module-template-server/Dockerfile b/zt-module-template/zt-module-template-server-app/Dockerfile similarity index 77% rename from zt-module-template/zt-module-template-server/Dockerfile rename to zt-module-template/zt-module-template-server-app/Dockerfile index e32668f2..0630e44e 100644 --- a/zt-module-template/zt-module-template-server/Dockerfile +++ b/zt-module-template/zt-module-template-server-app/Dockerfile @@ -3,10 +3,10 @@ FROM 172.16.46.66:10043/base-service/eclipse-temurin:21-jre ## 创建目录,并使用它作为工作目录 -RUN mkdir -p /zt-module-template-server -WORKDIR /zt-module-template-server +RUN mkdir -p /zt-module-template-server-app +WORKDIR /zt-module-template-server-app ## 将后端项目的 Jar 文件,复制到镜像中 -COPY ./target/zt-module-template-server.jar app.jar +COPY ../zt-module-template-server/target/zt-module-template-server-app.jar app.jar ## 设置 TZ 时区 ## 设置 JAVA_OPTS 环境变量,可通过 docker run -e "JAVA_OPTS=" 进行覆盖 diff --git a/zt-module-template/zt-module-template-server-app/pom.xml b/zt-module-template/zt-module-template-server-app/pom.xml new file mode 100644 index 00000000..7b117c84 --- /dev/null +++ b/zt-module-template/zt-module-template-server-app/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + zt-module-template + com.zt.plat + ${revision} + + zt-module-template-server-app + jar + ${project.artifactId} + + template 模块启动器。 + + + + + + + com.zt.plat + zt-module-template-server + ${revision} + + + + + + + ${project.artifactId} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + repackage + + + + + + + diff --git a/zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java b/zt-module-template/zt-module-template-server-app/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java similarity index 100% rename from zt-module-template/zt-module-template-server/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java rename to zt-module-template/zt-module-template-server-app/src/main/java/com/zt/plat/module/template/TemplateServerApplication.java diff --git a/zt-module-template/zt-module-template-server/src/main/resources/application-dev.yaml b/zt-module-template/zt-module-template-server-app/src/main/resources/application-dev.yaml similarity index 100% rename from zt-module-template/zt-module-template-server/src/main/resources/application-dev.yaml rename to zt-module-template/zt-module-template-server-app/src/main/resources/application-dev.yaml diff --git a/zt-module-template/zt-module-template-server/src/main/resources/application-local.yaml b/zt-module-template/zt-module-template-server-app/src/main/resources/application-local.yaml similarity index 100% rename from zt-module-template/zt-module-template-server/src/main/resources/application-local.yaml rename to zt-module-template/zt-module-template-server-app/src/main/resources/application-local.yaml diff --git a/zt-module-template/zt-module-template-server/src/main/resources/application.yaml b/zt-module-template/zt-module-template-server-app/src/main/resources/application.yaml similarity index 100% rename from zt-module-template/zt-module-template-server/src/main/resources/application.yaml rename to zt-module-template/zt-module-template-server-app/src/main/resources/application.yaml diff --git a/zt-module-template/zt-module-template-server/pom.xml b/zt-module-template/zt-module-template-server/pom.xml index e9114657..86f35f54 100644 --- a/zt-module-template/zt-module-template-server/pom.xml +++ b/zt-module-template/zt-module-template-server/pom.xml @@ -134,23 +134,4 @@ - - - ${project.artifactId} - - - - org.springframework.boot - spring-boot-maven-plugin - ${spring.boot.version} - - - - repackage - - - - - - From 605cff08c7d68795000c5b39b9e19a3a240929ee Mon Sep 17 00:00:00 2001 From: ranke <213539@qq.com> Date: Tue, 13 Jan 2026 09:30:43 +0800 Subject: [PATCH 09/10] no message --- .../service/msg/MsgSendServiceImplTest.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java index bfa1ca09..5fb201f3 100644 --- a/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java +++ b/zt-module-system/zt-module-system-server/src/test/java/com/zt/plat/module/system/service/msg/MsgSendServiceImplTest.java @@ -37,7 +37,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; -public class SmsSendServiceImplTest extends BaseMockitoUnitTest { +public class MsgSendServiceImplTest extends BaseMockitoUnitTest { @InjectMocks private SmsSendServiceImpl smsSendService; @@ -212,29 +212,29 @@ public class SmsSendServiceImplTest extends BaseMockitoUnitTest { anyLong(), any(), anyList()); } - @Test - public void testCheckSmsTemplateValid_notExists() { - // 准备参数 - String templateCode = randomString(); - // mock 方法 +// @Test +// public void testCheckSmsTemplateValid_notExists() { +// // 准备参数 +// String templateCode = randomString(); +// // mock 方法 +// +// // 调用,并断言异常 +// assertServiceException(() -> smsSendService.validateSmsTemplate(templateCode), +// SMS_SEND_TEMPLATE_NOT_EXISTS); +// } - // 调用,并断言异常 - assertServiceException(() -> smsSendService.validateSmsTemplate(templateCode), - SMS_SEND_TEMPLATE_NOT_EXISTS); - } - - @Test - public void testBuildTemplateParams_paramMiss() { - // 准备参数 - SmsTemplateDO template = randomPojo(SmsTemplateDO.class, - o -> o.setParams(Lists.newArrayList("code"))); - Map templateParams = new HashMap<>(); - // mock 方法 - - // 调用,并断言异常 - assertServiceException(() -> smsSendService.buildTemplateParams(template, templateParams), - SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code"); - } +// @Test +// public void testBuildTemplateParams_paramMiss() { +// // 准备参数 +// SmsTemplateDO template = randomPojo(SmsTemplateDO.class, +// o -> o.setParams(Lists.newArrayList("code"))); +// Map templateParams = new HashMap<>(); +// // mock 方法 +// +// // 调用,并断言异常 +// assertServiceException(() -> smsSendService.buildTemplateParams(template, templateParams), +// SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, "code"); +// } @Test public void testCheckMobile_notExists() { From 9233f5f783db690a94978ca3d2fe85392203ff8c Mon Sep 17 00:00:00 2001 From: FCL Date: Tue, 13 Jan 2026 09:22:22 +0800 Subject: [PATCH 10/10] =?UTF-8?q?feat:=E7=99=BB=E9=99=86=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E9=97=A8=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0=E8=A7=92=E8=89=B2=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=9B=E8=8E=B7=E5=8F=96=E5=BD=93=E5=89=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=AF=E8=AE=BF=E9=97=AE=E7=9A=84=E9=A1=B6=E7=BA=A7?= =?UTF-8?q?=E9=83=A8=E9=97=A8=E5=88=97=E8=A1=A8=E4=B8=8D=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../permission/PermissionCommonApi.java | 7 +++++ .../core/context/DeptContextHolder.java | 21 ++++++++++++++ .../api/permission/PermissionApiImpl.java | 4 +++ .../controller/admin/dept/DeptController.java | 2 +- .../service/permission/PermissionService.java | 1 + .../permission/PermissionServiceImpl.java | 28 +++++++++++++++++++ 6 files changed, 62 insertions(+), 1 deletion(-) diff --git a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/biz/system/permission/PermissionCommonApi.java b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/biz/system/permission/PermissionCommonApi.java index 844593d0..f809b7ea 100644 --- a/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/biz/system/permission/PermissionCommonApi.java +++ b/zt-framework/zt-common/src/main/java/com/zt/plat/framework/common/biz/system/permission/PermissionCommonApi.java @@ -40,4 +40,11 @@ public interface PermissionCommonApi { @Parameter(name = "userId", description = "用户编号", example = "2", required = true) CommonResult getDeptDataPermission(@RequestParam("userId") Long userId); + @GetMapping(PREFIX + "/get-dept-data-permission-with-roleCodes") + @Operation(summary = "获得登陆用户的部门数据权限") + @Parameters({ + @Parameter(name = "userId", description = "用户编号", example = "2", required = true), + @Parameter(name = "roleCodes", description = "角色编码", example = "2", required = true) + }) + CommonResult getDeptDataPermissionWithRoleCodes(@RequestParam("userId") Long userId, @RequestParam("roleCodes") String roleCodes); } \ No newline at end of file diff --git a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java index e463ae50..8652ebdb 100644 --- a/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java +++ b/zt-framework/zt-spring-boot-starter-biz-tenant/src/main/java/com/zt/plat/framework/tenant/core/context/DeptContextHolder.java @@ -2,6 +2,8 @@ package com.zt.plat.framework.tenant.core.context; import com.alibaba.ttl.TransmittableThreadLocal; +import java.util.List; + /** * 部门上下文 Holder,使用 {@link TransmittableThreadLocal} 支持在线程池/异步场景下的上下文传递。 * @@ -15,6 +17,8 @@ public class DeptContextHolder { private static final ThreadLocal COMPANY_ID = new TransmittableThreadLocal<>(); /** 是否忽略部门数据权限 */ private static final ThreadLocal IGNORE = new TransmittableThreadLocal<>(); + /** 角色编码列表 */ + private static final ThreadLocal> ROLE_CODE_LIST = new TransmittableThreadLocal<>(); public static Long getDeptId() { return DEPT_ID.get(); @@ -32,6 +36,12 @@ public class DeptContextHolder { COMPANY_ID.set(companyId); } + public static void setContext(Long deptId, Long companyId, List roleCodeList) { + DEPT_ID.set(deptId); + COMPANY_ID.set(companyId); + ROLE_CODE_LIST.set(roleCodeList); + } + public static void setDeptId(Long deptId) { DEPT_ID.set(deptId); } @@ -53,9 +63,20 @@ public class DeptContextHolder { return Boolean.TRUE.equals(IGNORE.get()); } + public static void setRoleCodeList(List roleCodeList) { + ROLE_CODE_LIST.set(roleCodeList); + } + public static List getRoleCodeList() { + return ROLE_CODE_LIST.get(); + } + public static void clearRoleCodeList(){ + ROLE_CODE_LIST.remove(); + } + public static void clear() { DEPT_ID.remove(); COMPANY_ID.remove(); IGNORE.remove(); + ROLE_CODE_LIST.remove(); } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java index 771f322b..c3899330 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/api/permission/PermissionApiImpl.java @@ -86,4 +86,8 @@ public class PermissionApiImpl implements PermissionApi { return success(permissionService.getDeptDataPermission(userId)); } + @Override + public CommonResult getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes) { + return success(permissionService.getDeptDataPermissionWithRoleCodes(userId, roleCodes)); + } } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java index 93876898..54ba9b37 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/dept/DeptController.java @@ -123,7 +123,7 @@ public class DeptController { @GetMapping("/top-level-list") @Operation(summary = "获取当前用户可访问的顶级部门列表", description = "用于懒加载,返回当前用户所属部门的最顶层祖先部门,如果用户没有关联任何部门则返回空列表") - @PreAuthorize("@ss.hasPermission('system:dept:query')") +// @PreAuthorize("@ss.hasPermission('system:dept:query')") public CommonResult> getTopLevelDeptList() { List list = deptService.getTopLevelDeptList(); return success(BeanUtils.toBean(list, DeptRespVO.class)); diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java index a1a88dd3..69a4857c 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionService.java @@ -143,6 +143,7 @@ public interface PermissionService { * @return 部门数据权限 */ DeptDataPermissionRespDTO getDeptDataPermission(Long userId); + DeptDataPermissionRespDTO getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes); /** * 获得用户的数据权限级别 diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java index 6bb37d18..52265633 100644 --- a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/permission/PermissionServiceImpl.java @@ -3,6 +3,7 @@ package com.zt.plat.module.system.service.permission; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; import com.baomidou.dynamic.datasource.annotation.DSTransactional; import com.google.common.annotations.VisibleForTesting; @@ -12,6 +13,7 @@ import com.zt.plat.framework.common.biz.system.permission.dto.DeptDataPermission import com.zt.plat.framework.common.enums.CommonStatusEnum; import com.zt.plat.framework.common.util.collection.CollectionUtils; import com.zt.plat.framework.datapermission.core.annotation.DataPermission; +import com.zt.plat.framework.tenant.core.context.DeptContextHolder; import com.zt.plat.module.system.dal.dataobject.permission.MenuDO; import com.zt.plat.module.system.dal.dataobject.permission.RoleDO; import com.zt.plat.module.system.dal.dataobject.permission.RoleMenuDO; @@ -347,6 +349,12 @@ public class PermissionServiceImpl implements PermissionService { // 获得用户的角色 List roles = getEnableUserRoleListByUserIdFromCache(userId); + //使用上下文角色编码过滤 + List contextRoleCodes = DeptContextHolder.getRoleCodeList(); + if(!CollectionUtil.isEmpty(contextRoleCodes)){ + roles = roles.stream().filter(role -> contextRoleCodes.contains(role.getCode())).collect(Collectors.toList()); + } + // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询 Supplier> userDeptIds = Suppliers.memoize(() -> { List validUserDeptListByUserId = userDeptService.getValidUserDeptListByUserIds(singleton(userId)); @@ -414,6 +422,26 @@ public class PermissionServiceImpl implements PermissionService { return result; } + @Override + public DeptDataPermissionRespDTO getDeptDataPermissionWithRoleCodes(Long userId, String roleCodes) { + // 获得用户的角色 + List roles = getEnableUserRoleListByUserIdFromCache(userId); + if(ObjectUtil.isEmpty(roleCodes)) + return getDeptDataPermission(userId); + List roleCodesList = Arrays.asList(roleCodes.split(",")); + if(CollectionUtil.isEmpty(roles)) + return getDeptDataPermission(userId); + DeptContextHolder.setRoleCodeList(roleCodesList); + try{ + return getDeptDataPermission(userId); + }catch (Exception e){ + log.error("getDeptDataPermission-- error ", e); + }finally { + DeptContextHolder.clearRoleCodeList(); + } + return getDeptDataPermission(userId); + } + @Override @DataPermission(enable = false) @TenantIgnore