diff --git a/urbanops-framework/urbanops-spring-boot-starter-web/src/main/java/com/zteits/urbanops/framework/web/core/util/WebFrameworkUtils.java b/urbanops-framework/urbanops-spring-boot-starter-web/src/main/java/com/zteits/urbanops/framework/web/core/util/WebFrameworkUtils.java index a89d45e..22472b4 100644 --- a/urbanops-framework/urbanops-spring-boot-starter-web/src/main/java/com/zteits/urbanops/framework/web/core/util/WebFrameworkUtils.java +++ b/urbanops-framework/urbanops-spring-boot-starter-web/src/main/java/com/zteits/urbanops/framework/web/core/util/WebFrameworkUtils.java @@ -132,7 +132,8 @@ public class WebFrameworkUtils { return UserTypeEnum.ADMIN.getValue(); } if (request.getServletPath().startsWith(properties.getAppApi().getPrefix())) { - return UserTypeEnum.MEMBER.getValue(); + // 20251206 我改的,目前前后端用户同一套,不做区分 + return UserTypeEnum.ADMIN.getValue(); } return null; } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/OldtreesController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/OldtreesController.java index 716f2c4..4918702 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/OldtreesController.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/OldtreesController.java @@ -98,7 +98,8 @@ public class OldtreesController { @TransMethodResult public CommonResult getOldtrees(@RequestParam("id") String id) { OldtreesDO oldtrees = oldtreesService.getOldtrees(id); - return success(BeanUtils.toBean(oldtrees, OldtreesRespVO.class)); + OldtreesRespVO bean = BeanUtils.toBean(oldtrees, OldtreesRespVO.class); + return success(bean); } @GetMapping({"/page","/list"}) diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/vo/OldtreesRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/vo/OldtreesRespVO.java index 029dd15..9939a2c 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/vo/OldtreesRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtrees/vo/OldtreesRespVO.java @@ -1,10 +1,10 @@ package com.zteits.urbanops.module.garden.controller.admin.oldtrees.vo; -import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fhs.core.trans.anno.Trans; import com.fhs.core.trans.constant.TransType; +import com.fhs.core.trans.vo.VO; import com.zteits.urbanops.framework.common.util.json.databind.LocalDateTimeStringSerializer; import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; import com.zteits.urbanops.module.system.dal.dataobject.user.AdminUserDO; @@ -21,7 +21,7 @@ import static com.zteits.urbanops.framework.common.util.date.DateUtils.TIME_ZONE @Schema(description = "管理后台 - 古树名木 Response VO") @Data @ExcelIgnoreUnannotated -public class OldtreesRespVO { +public class OldtreesRespVO implements VO { @Schema(description = "id", example = "4258") // @ExcelProperty("id") @@ -43,9 +43,13 @@ public class OldtreesRespVO { @ExcelProperty("所在地") private String location; + @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name",ref = "maintainunitName") + private String maintainunit; + @Schema(description = "养护单位") @ExcelProperty("养护单位") - private String maintainunit; + private String maintainunitName; + @Schema(description = "权属分类--数据字典:专业绿地和单位(居住区)") @ExcelProperty("权属分类") diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtreeschangelog/vo/OldtreesChangeLogRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtreeschangelog/vo/OldtreesChangeLogRespVO.java index 3b1b8d7..06d95c7 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtreeschangelog/vo/OldtreesChangeLogRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/oldtreeschangelog/vo/OldtreesChangeLogRespVO.java @@ -47,10 +47,12 @@ public class OldtreesChangeLogRespVO implements VO { @ExcelProperty("所在地") private String location; + @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name",ref = "maintainunitName") + private String maintainunit; + @Schema(description = "养护单位") @ExcelProperty("养护单位") - @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name",ref = "maintainunit") - private String maintainunit; + private String maintainunitName; @Schema(description = "权属分类--数据字典:专业绿地和单位(居住区)") @ExcelProperty("权属分类--数据字典:专业绿地和单位(居住区)") diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeRespVO.java index 73a8764..a55520d 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/tree/vo/TreeRespVO.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fhs.core.trans.anno.Trans; import com.fhs.core.trans.constant.TransType; +import com.fhs.core.trans.vo.VO; import com.zteits.urbanops.framework.common.util.json.databind.LocalDateTimeStringSerializer; import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; import com.zteits.urbanops.module.system.dal.dataobject.user.AdminUserDO; @@ -21,7 +22,7 @@ import static com.zteits.urbanops.framework.common.util.date.DateUtils.TIME_ZONE @Schema(description = "管理后台 - 一树一档案 Response VO") @Data @ExcelIgnoreUnannotated -public class TreeRespVO { +public class TreeRespVO implements VO { @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "28236") // @ExcelProperty("id") @@ -71,9 +72,13 @@ public class TreeRespVO { // @ExcelProperty("所在地") private String location; + @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name", ref = "maintainunitName") + private String maintainunit; + @Schema(description = "养护单位") @ExcelProperty("养护单位") - private String maintainunit; + private String maintainunitName; + @Schema(description = "管护单位") @ExcelProperty("管护单位") diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treechangelog/vo/TreeChangeLogRespVO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treechangelog/vo/TreeChangeLogRespVO.java index b756cee..c99ef3a 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treechangelog/vo/TreeChangeLogRespVO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/treechangelog/vo/TreeChangeLogRespVO.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fhs.core.trans.anno.Trans; import com.fhs.core.trans.constant.TransType; +import com.fhs.core.trans.vo.VO; import com.zteits.urbanops.framework.common.util.json.databind.LocalDateTimeStringSerializer; import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; import com.zteits.urbanops.module.system.dal.dataobject.user.AdminUserDO; @@ -20,7 +21,7 @@ import static com.zteits.urbanops.framework.common.util.date.DateUtils.TIME_ZONE @Schema(description = "管理后台 - 一树一档案修改记录 Response VO") @Data @ExcelIgnoreUnannotated -public class TreeChangeLogRespVO { +public class TreeChangeLogRespVO implements VO { @Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "3528") @ExcelProperty("id") @@ -74,10 +75,12 @@ public class TreeChangeLogRespVO { @ExcelProperty("所在地") private String location; + @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name",ref = "maintainunitName") + private String maintainunit; + @Schema(description = "养护单位") @ExcelProperty("养护单位") - @Trans(type = TransType.SIMPLE, target = DeptDO.class, fields = "name",ref = "maintainunit") - private String maintainunit; + private String maintainunitName; @Schema(description = "管护单位") @ExcelProperty("管护单位") diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtrees/OldtreesDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtrees/OldtreesDO.java index 8ef5e42..e574a07 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtrees/OldtreesDO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtrees/OldtreesDO.java @@ -47,6 +47,9 @@ public class OldtreesDO { * 养护单位 */ private String maintainunit; + + @TableField(exist = false) + private String maintainunitName; /** * 权属分类--数据字典:专业绿地和单位(居住区) */ diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtreeschangelog/OldtreesChangeLogDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtreeschangelog/OldtreesChangeLogDO.java index f65a944..891b556 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtreeschangelog/OldtreesChangeLogDO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/oldtreeschangelog/OldtreesChangeLogDO.java @@ -52,6 +52,9 @@ public class OldtreesChangeLogDO { * 养护单位 */ private String maintainunit; + + @TableField(exist = false) + private String maintainunitName; /** * 权属分类--数据字典:专业绿地和单位(居住区) */ diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/tree/TreeDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/tree/TreeDO.java index 436d5cb..4d2771e 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/tree/TreeDO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/tree/TreeDO.java @@ -77,6 +77,9 @@ public class TreeDO { * 养护单位 */ private String maintainunit; + + @TableField(exist = false) + private String maintainunitName; /** * 管护单位 */ diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treechangelog/TreeChangeLogDO.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treechangelog/TreeChangeLogDO.java index b261734..4869d78 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treechangelog/TreeChangeLogDO.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/dataobject/treechangelog/TreeChangeLogDO.java @@ -80,6 +80,9 @@ public class TreeChangeLogDO { * 养护单位 */ private String maintainunit; + + @TableField(exist = false) + private String maintainunitName; /** * 管护单位 */ diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtrees/OldtreesMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtrees/OldtreesMapper.java index 053bec1..db57547 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtrees/OldtreesMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtrees/OldtreesMapper.java @@ -22,7 +22,7 @@ public interface OldtreesMapper extends BaseMapperX { default PageResult selectPage(OldtreesPageReqVO reqVO) { MPJLambdaWrapperX wrapper = new MPJLambdaWrapperX() - .selectAs(DeptDO::getName, OldtreesDO::getMaintainunit) + .selectAs(DeptDO::getName, OldtreesDO::getMaintainunitName) .selectAll(OldtreesDO.class) .leftJoin(DeptDO.class, DeptDO::getId, OldtreesDO::getMaintainunit) .eqIfPresent(OldtreesDO::getFamoustreenumber, reqVO.getFamoustreenumber()) @@ -66,6 +66,7 @@ public interface OldtreesMapper extends BaseMapperX { .eqIfPresent(OldtreesDO::getUpdateby, reqVO.getUpdateby()) .betweenIfPresent(OldtreesDO::getUpdatetime, reqVO.getUpdatetime()) .eqIfPresent(OldtreesDO::getDeptId, reqVO.getDeptId()) + .orderByDesc(OldtreesDO::getUpdatetime) .orderByDesc(OldtreesDO::getCreatetime); return selectJoinPage(reqVO, OldtreesDO.class, wrapper); diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtreeschangelog/OldtreesChangeLogMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtreeschangelog/OldtreesChangeLogMapper.java index 56636fd..6276071 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtreeschangelog/OldtreesChangeLogMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/oldtreeschangelog/OldtreesChangeLogMapper.java @@ -22,7 +22,7 @@ public interface OldtreesChangeLogMapper extends BaseMapperX selectPage(OldtreesChangeLogPageReqVO reqVO) { MPJLambdaWrapperX wrapper = new MPJLambdaWrapperX() - .selectAs(DeptDO::getName, OldtreesChangeLogDO::getMaintainunit) + .selectAs(DeptDO::getName, OldtreesChangeLogDO::getMaintainunitName) .selectAll(OldtreesChangeLogDO.class) .leftJoin(DeptDO.class, DeptDO::getId, OldtreesChangeLogDO::getMaintainunit) .eqIfPresent(OldtreesChangeLogDO::getOldtreeid, reqVO.getOldtreeid()) @@ -66,6 +66,7 @@ public interface OldtreesChangeLogMapper extends BaseMapperX { default PageResult selectPage(TreePageReqVO reqVO) { MPJLambdaWrapperX wrapper = new MPJLambdaWrapperX() - .selectAs(DeptDO::getName, TreeDO::getMaintainunit) + .selectAs(DeptDO::getName, TreeDO::getMaintainunitName) .selectAll(TreeDO.class) .leftJoin(DeptDO.class, DeptDO::getId, TreeDO::getMaintainunit) .eqIfPresent(TreeDO::getTreenumber, reqVO.getTreenumber()) @@ -58,6 +58,7 @@ public interface TreeMapper extends BaseMapperX { .betweenIfPresent(TreeDO::getUpdatetime, reqVO.getUpdatetime()) .eqIfPresent(TreeDO::getRemark, reqVO.getRemark()) .eqIfPresent(TreeDO::getDeptId, reqVO.getDeptId()) + .orderByDesc(TreeDO::getUpdatetime) .orderByDesc(TreeDO::getId); return selectJoinPage(reqVO, TreeDO.class, wrapper); diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treechangelog/TreeChangeLogMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treechangelog/TreeChangeLogMapper.java index 17752bd..97f59ac 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treechangelog/TreeChangeLogMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/treechangelog/TreeChangeLogMapper.java @@ -22,7 +22,7 @@ public interface TreeChangeLogMapper extends BaseMapperX { default PageResult selectPage(TreeChangeLogPageReqVO reqVO) { MPJLambdaWrapperX wrapper = new MPJLambdaWrapperX() - .selectAs(DeptDO::getName, TreeChangeLogDO::getMaintainunit) + .selectAs(DeptDO::getName, TreeChangeLogDO::getMaintainunitName) .selectAll(TreeChangeLogDO.class) .leftJoin(DeptDO.class, DeptDO::getId, TreeChangeLogDO::getMaintainunit) .eqIfPresent(TreeChangeLogDO::getTreeid, reqVO.getTreeid()) @@ -59,6 +59,7 @@ public interface TreeChangeLogMapper extends BaseMapperX { .eqIfPresent(TreeChangeLogDO::getUpdateby, reqVO.getUpdateby()) .betweenIfPresent(TreeChangeLogDO::getUpdatetime, reqVO.getUpdatetime()) .eqIfPresent(TreeChangeLogDO::getRemark, reqVO.getRemark()) + .orderByDesc(TreeChangeLogDO::getUpdatetime) .orderByDesc(TreeChangeLogDO::getId); return selectJoinPage(reqVO, TreeChangeLogDO.class, wrapper); diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/AppModuleController.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/AppModuleController.java new file mode 100644 index 0000000..39da5bc --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/AppModuleController.java @@ -0,0 +1,105 @@ +package com.zteits.urbanops.module.system.controller.admin.appmodule; + +import com.zteits.urbanops.framework.common.enums.CommonStatusEnum; +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleListReqVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleRespVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSaveVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSimpleRespVO; +import com.zteits.urbanops.module.system.convert.appmodule.AppModuleConvert; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.service.appmodule.AppModuleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Comparator; +import java.util.List; + +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + + +/** + * 管理后台 - App 功能模块 + * + * @author system + */ +@Tag(name = "管理后台 - App 功能模块") +@RestController +@RequestMapping("/system/app-module") +@Validated +public class AppModuleController { + + @Resource + private AppModuleService appModuleService; + + @PostMapping("/create") + @Operation(summary = "创建 App 功能模块") + @PreAuthorize("@ss.hasPermission('system:app-module:create')") + public CommonResult createAppModule(@Valid @RequestBody AppModuleSaveVO createReqVO) { + Long moduleId = appModuleService.createAppModule(createReqVO); + return success(moduleId); + } + + @PutMapping("/update") + @Operation(summary = "修改 App 功能模块") + @PreAuthorize("@ss.hasPermission('system:app-module:update')") + public CommonResult updateAppModule(@Valid @RequestBody AppModuleSaveVO updateReqVO) { + appModuleService.updateAppModule(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除 App 功能模块") + @Parameter(name = "id", description = "模块编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:app-module:delete')") + public CommonResult deleteAppModule(@RequestParam("id") Long id) { + appModuleService.deleteAppModule(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除 App 功能模块") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('system:app-module:delete')") + public CommonResult deleteAppModuleList(@RequestParam("ids") List ids) { + appModuleService.deleteAppModuleList(ids); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获取 App 功能模块列表", description = "用于【App 功能模块管理】界面") + @PreAuthorize("@ss.hasPermission('system:app-module:query')") + public CommonResult> getAppModuleList(AppModuleListReqVO reqVO) { + List list = appModuleService.getAppModuleList(reqVO); + list.sort(Comparator.comparing(AppModuleDO::getSort)); + return success(AppModuleConvert.INSTANCE.convertList(list)); + } + + @GetMapping("/list-all-simple") + @Operation(summary = "获取 App 功能模块精简信息列表", + description = "只包含被开启的模块,用于配置、选择等功能") + public CommonResult> getSimpleAppModuleList() { + AppModuleListReqVO reqVO = new AppModuleListReqVO(); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + List list = appModuleService.getAppModuleList(reqVO); + list = appModuleService.filterDisableModules(list); + list.sort(Comparator.comparing(AppModuleDO::getSort)); + return success(AppModuleConvert.INSTANCE.convertSimpleList(list)); + } + + @GetMapping("/get") + @Operation(summary = "获取 App 功能模块信息") + @Parameter(name = "id", description = "模块编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:app-module:query')") + public CommonResult getAppModule(@RequestParam("id") Long id) { + AppModuleDO module = appModuleService.getAppModule(id); + return success(AppModuleConvert.INSTANCE.convert(module)); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleListReqVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleListReqVO.java new file mode 100644 index 0000000..8844ac1 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleListReqVO.java @@ -0,0 +1,19 @@ +package com.zteits.urbanops.module.system.controller.admin.appmodule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - App 功能模块列表 Request VO") +@Data +public class AppModuleListReqVO { + + @Schema(description = "模块名称,模糊匹配", example = "个人") + private String name; + + @Schema(description = "状态,参见 CommonStatusEnum 枚举类", example = "0") + private Integer status; + + @Schema(description = "模块类型,参见 AppModuleTypeEnum 枚举类", example = "2") + private Integer type; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleRespVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleRespVO.java new file mode 100644 index 0000000..46b9f33 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleRespVO.java @@ -0,0 +1,74 @@ +package com.zteits.urbanops.module.system.controller.admin.appmodule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - App 功能模块信息 Response VO") +@Data +public class AppModuleRespVO { + + @Schema(description = "模块编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "模块名称(主标题)", requiredMode = Schema.RequiredMode.REQUIRED, example = "个人中心") + private String name; + + @Schema(description = "模块副标题", example = "查看个人信息") + private String subtitle; + + @Schema(description = "类型,参见 AppModuleTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + @Schema(description = "父模块 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Long parentId; + + @Schema(description = "模块图标 URL", example = "https://example.com/icon.png") + private String icon; + + @Schema(description = "跳转路径", example = "/user/profile") + private String jumpUrl; + + @Schema(description = "小程序 AppId", example = "wx1234567890") + private String miniAppId; + + @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + + @Schema(description = "是否可见", example = "true") + private Boolean visible; + + @Schema(description = "可见范围,见 AppModuleVisibleScopeEnum 枚举", example = "1") + private Integer visibleScope; + + @Schema(description = "可见范围配置 JSON", example = "{}") + private List visibleConfig; + + @Schema(description = "是否支持灰度发布", example = "false") + private Boolean grayRelease; + + @Schema(description = "灰度发布配置 JSON", example = "{}") + private Map grayConfig; + + @Schema(description = "角标文本", example = "NEW") + private String badgeText; + + @Schema(description = "角标颜色", example = "#FF0000") + private String badgeColor; + + @Schema(description = "扩展字段 JSON", example = "{}") + private Map extra; + + @Schema(description = "备注", example = "这是一个测试模块") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSaveVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSaveVO.java new file mode 100644 index 0000000..710aaac --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSaveVO.java @@ -0,0 +1,87 @@ +package com.zteits.urbanops.module.system.controller.admin.appmodule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - App 功能模块创建/修改 Request VO") +@Data +public class AppModuleSaveVO { + + @Schema(description = "模块编号", example = "1024") + private Long id; + + @Schema(description = "模块名称(主标题)", requiredMode = Schema.RequiredMode.REQUIRED, example = "个人中心") + @NotBlank(message = "模块名称不能为空") + @Size(max = 50, message = "模块名称长度不能超过50个字符") + private String name; + + @Schema(description = "模块副标题", example = "查看个人信息") + @Size(max = 100, message = "副标题长度不能超过100个字符") + private String subtitle; + + @Schema(description = "类型,参见 AppModuleTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "模块类型不能为空") + private Integer type; + + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "显示顺序不能为空") + private Integer sort; + + @Schema(description = "父模块 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "父模块 ID 不能为空") + private Long parentId; + + @Schema(description = "模块图标 URL", example = "https://example.com/icon.png") + @Size(max = 500, message = "图标 URL 不能超过500个字符") + private String icon; + + @Schema(description = "跳转路径", example = "/user/profile") + @Size(max = 500, message = "跳转路径不能超过500个字符") + private String jumpUrl; + + @Schema(description = "小程序 AppId", example = "wx1234567890") + @Size(max = 100, message = "小程序 AppId 不能超过100个字符") + private String miniAppId; + + @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; + + @Schema(description = "是否可见", example = "true") + private Boolean visible; + + @Schema(description = "可见范围,见 AppModuleVisibleScopeEnum 枚举", example = "1") + private Integer visibleScope; + + @Schema(description = "可见范围配置 JSON", example = "{}") + @Size(max = 2000, message = "可见范围配置不能超过2000个字符") + private List visibleConfig; + + @Schema(description = "是否支持灰度发布", example = "false") + private Boolean grayRelease; + + @Schema(description = "灰度发布配置 JSON", example = "{}") + private Map grayConfig; + + @Schema(description = "角标文本", example = "NEW") + @Size(max = 20, message = "角标文本不能超过20个字符") + private String badgeText; + + @Schema(description = "角标颜色", example = "#FF0000") + @Size(max = 20, message = "角标颜色不能超过20个字符") + private String badgeColor; + + @Schema(description = "扩展字段 JSON", example = "{}") + private Map extra; + + @Schema(description = "备注", example = "这是一个测试模块") + @Size(max = 500, message = "备注不能超过500个字符") + private String remark; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSimpleRespVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSimpleRespVO.java new file mode 100644 index 0000000..88e1db2 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/appmodule/vo/AppModuleSimpleRespVO.java @@ -0,0 +1,28 @@ +package com.zteits.urbanops.module.system.controller.admin.appmodule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - App 功能模块精简 Response VO") +@Data +public class AppModuleSimpleRespVO { + + @Schema(description = "模块编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "模块名称(主标题)", requiredMode = Schema.RequiredMode.REQUIRED, example = "个人中心") + private String name; + + @Schema(description = "父模块 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Long parentId; + + @Schema(description = "类型,参见 AppModuleTypeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/AppAppModuleController.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/AppAppModuleController.java new file mode 100644 index 0000000..fb0332b --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/AppAppModuleController.java @@ -0,0 +1,59 @@ +package com.zteits.urbanops.module.system.controller.app.appmodule; + +import com.zteits.urbanops.framework.common.pojo.CommonResult; +import com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleListReqVO; +import com.zteits.urbanops.module.system.controller.app.appmodule.vo.AppModuleVO; +import com.zteits.urbanops.module.system.convert.appmodule.AppModuleConvert; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.service.appmodule.AppModuleService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Comparator; +import java.util.List; + +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + + +/** + * 用户 APP - App 功能模块 + * + * @author system + */ +@Tag(name = "用户 APP - App 功能模块") +@RestController +@RequestMapping("/member/app-module") +@Validated +public class AppAppModuleController { + + @Resource + private AppModuleService appModuleService; + + @GetMapping("/list") + @Operation(summary = "获取 App 功能模块列表", description = "获取用户可见的功能模块列表") + public CommonResult> getAppModuleList(AppModuleListReqVO reqVO) { + // 获取当前登录用户ID + Long userId = SecurityFrameworkUtils.getLoginUserId(); + + // 查询用户可见的模块列表 + List list = appModuleService.getVisibleAppModuleList(userId, reqVO); + + // 填充统计数据(根据extra中的statisticKey配置) +// appModuleService.fillModuleStatistics(list, userId); + + // 排序并转换 + list.sort(Comparator.comparing(AppModuleDO::getSort)); + List result = AppModuleConvert.INSTANCE.convertAppList(list); + + // MapStruct 转换已通过 @AfterMapping 自动填充 statisticCount + + return success(result); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/vo/AppModuleVO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/vo/AppModuleVO.java new file mode 100644 index 0000000..1db8357 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/app/appmodule/vo/AppModuleVO.java @@ -0,0 +1,54 @@ +package com.zteits.urbanops.module.system.controller.app.appmodule.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Map; + +@Schema(description = "用户 APP - App 功能模块 Response VO") +@Data +public class AppModuleVO { + + @Schema(description = "模块编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "模块名称(主标题)", requiredMode = Schema.RequiredMode.REQUIRED, example = "个人中心") + private String name; + + @Schema(description = "模块副标题", example = "查看个人信息") + private String subtitle; + + @Schema(description = "类型,1-目录 2-原生页面 3-H5页面 4-小程序 5-外部链接", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer type; + + @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + @Schema(description = "父模块 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Long parentId; + + @Schema(description = "模块图标 URL", example = "https://example.com/icon.png") + private String icon; + + @Schema(description = "跳转路径", example = "/user/profile") + private String jumpUrl; + + @Schema(description = "小程序 AppId", example = "wx1234567890") + private String miniAppId; + + @Schema(description = "角标文本", example = "NEW") + private String badgeText; + + @Schema(description = "角标颜色", example = "#FF0000") + private String badgeColor; + + @Schema(description = "统计数量(动态查询)", example = "18") + private Integer statisticCount; + + @Schema(description = "是否配置统计标识", example = "true") + private Boolean hasStatistic; + + @Schema(description = "扩展字段 JSON", example = "{}") + private Map extra; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/convert/appmodule/AppModuleConvert.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/convert/appmodule/AppModuleConvert.java new file mode 100644 index 0000000..250989a --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/convert/appmodule/AppModuleConvert.java @@ -0,0 +1,86 @@ +package com.zteits.urbanops.module.system.convert.appmodule; + +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleRespVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSaveVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSimpleRespVO; +import com.zteits.urbanops.module.system.controller.app.appmodule.vo.AppModuleVO; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * App 功能模块 Convert + * + * @author system + */ +@Mapper +public interface AppModuleConvert { + + AppModuleConvert INSTANCE = Mappers.getMapper(AppModuleConvert.class); + + /** + * DO 转 RespVO + */ + AppModuleRespVO convert(AppModuleDO moduleDO); + + /** + * DO 列表转 RespVO 列表 + */ + List convertList(List list); + + /** + * DO 列表转 SimpleRespVO 列表 + */ + List convertSimpleList(List list); + + /** + * SaveVO 转 DO + */ + AppModuleDO convert(AppModuleSaveVO saveVO); + + /** + * DO 列表转 App VO 列表 + */ + List convertAppList(List list); + + /** + * DO 转 App VO(供列表转换时复用) + */ + AppModuleVO convertApp(AppModuleDO moduleDO); + + /** + * 映射后填充 statisticCount 与 hasStatistic + */ + @AfterMapping + default void fillStatisticCount(AppModuleDO source, @MappingTarget AppModuleVO target) { + if (source == null) { + return; + } + + // 统计数量(来源于 DO.extra.statisticCount) + if (source.getExtra() != null) { + Object statisticCount = source.getExtra().get("statisticCount"); + if (statisticCount instanceof Integer) { + target.setStatisticCount((Integer) statisticCount); + } else if (statisticCount instanceof Number) { + target.setStatisticCount(((Number) statisticCount).intValue()); + } + } + + // 是否配置统计(来源于 DO.extra.statisticKey 是否存在且非空) + boolean hasStatistic = false; + if (source.getExtra() != null) { + Object statisticKeyObj = source.getExtra().get("statisticKey"); + if (statisticKeyObj instanceof String) { + String statisticKey = ((String) statisticKeyObj).trim(); + hasStatistic = !statisticKey.isEmpty(); + } + } + target.setHasStatistic(hasStatistic); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/dataobject/appmodule/AppModuleDO.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/dataobject/appmodule/AppModuleDO.java new file mode 100644 index 0000000..f24ddc9 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/dataobject/appmodule/AppModuleDO.java @@ -0,0 +1,164 @@ +package com.zteits.urbanops.module.system.dal.dataobject.appmodule; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.zteits.urbanops.framework.common.enums.CommonStatusEnum; +import com.zteits.urbanops.framework.mybatis.core.dataobject.BaseDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleTypeEnum; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Map; + +/** + * App 功能模块 DO + * + * @author system + */ +@TableName(value = "system_app_module", autoResultMap = true) +@KeySequence("system_app_module_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +public class AppModuleDO extends BaseDO { + + /** + * 模块编号 - 根节点 + */ + public static final Long ID_ROOT = 0L; + + /** + * 模块编号 + */ + @TableId + private Long id; + + /** + * 模块名称(主标题) + */ + private String name; + + /** + * 模块副标题 + */ + private String subtitle; + + /** + * 模块类型 + * + * 枚举 {@link AppModuleTypeEnum} + */ + private Integer type; + + /** + * 显示顺序 + */ + private Integer sort; + + /** + * 父模块ID + */ + private Long parentId; + + /** + * 模块图标 + */ + private String icon; + + /** + * 跳转路径 + * + * - 原生页面: 原生路由标识 (如: /user/profile) + * - H5页面: H5 URL (如: https://www.example.com/page) + * - 小程序: 小程序路径 (如: /pages/index/index) + * - 外部链接: 完整 URL (如: https://www.example.com) + */ + private String jumpUrl; + + /** + * 小程序 AppId + * + * 当 type 为小程序时使用 + */ + private String miniAppId; + + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + + /** + * 是否可见 + * + * true: 展示在首页/列表中 + * false: 不展示但可以通过路径访问 + */ + private Boolean visible; + + /** + * 可见范围 + * + * 枚举 {@link AppModuleVisibleScopeEnum} + */ + private Integer visibleScope; + + /** + * 可见范围配置 + * + * JSON 格式存储具体配置 + * - USER_TYPE: ["VIP", "NORMAL"] + * - USER_TAG: ["tag1", "tag2"] + * - USER_ID: [123, 456] + * - ROLE_ID: ["ROLE_ADMIN", "ROLE_USER"] + */ + @TableField(typeHandler = JacksonTypeHandler.class, updateStrategy = FieldStrategy.ALWAYS) + private List visibleConfig; + + /** + * 是否支持灰度发布 + */ + private Boolean grayRelease; + + /** + * 灰度发布配置 + * + * JSON 格式存储灰度规则 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map grayConfig; + + /** + * 角标文本 + * + * 如: "NEW", "HOT", "1" + */ + private String badgeText; + + /** + * 角标颜色 + * + * 如: "#FF0000" + */ + private String badgeColor; + + /** + * 扩展字段 + * + * JSON 格式存储其他业务需要的字段 + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map extra; + + /** + * 备注 + */ + private String remark; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/mysql/appmodule/AppModuleMapper.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/mysql/appmodule/AppModuleMapper.java new file mode 100644 index 0000000..c235fa7 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/dal/mysql/appmodule/AppModuleMapper.java @@ -0,0 +1,64 @@ +package com.zteits.urbanops.module.system.dal.mysql.appmodule; + +import com.zteits.urbanops.framework.mybatis.core.mapper.BaseMapperX; +import com.zteits.urbanops.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleListReqVO; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * App 功能模块 Mapper + * + * @author system + */ +@Mapper +public interface AppModuleMapper extends BaseMapperX { + + /** + * 根据父模块ID和名称查询 + * + * @param parentId 父模块ID + * @param name 模块名称 + * @return App 功能模块 + */ + default AppModuleDO selectByParentIdAndName(Long parentId, String name) { + return selectOne(AppModuleDO::getParentId, parentId, AppModuleDO::getName, name); + } + + /** + * 统计父模块下的子模块数量 + * + * @param parentId 父模块ID + * @return 子模块数量 + */ + default Long selectCountByParentId(Long parentId) { + return selectCount(AppModuleDO::getParentId, parentId); + } + + /** + * 查询模块列表 + * + * @param reqVO 查询条件 + * @return 模块列表 + */ + default List selectList(AppModuleListReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(AppModuleDO::getName, reqVO.getName()) + .eqIfPresent(AppModuleDO::getStatus, reqVO.getStatus()) + .eqIfPresent(AppModuleDO::getType, reqVO.getType()) + .orderByAsc(AppModuleDO::getSort)); + } + + /** + * 根据父模块ID查询子模块列表 + * + * @param parentId 父模块ID + * @return 子模块列表 + */ + default List selectListByParentId(Long parentId) { + return selectList(AppModuleDO::getParentId, parentId); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/ErrorCodeConstants.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/ErrorCodeConstants.java index 0c6b7cf..7dcba43 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/ErrorCodeConstants.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/ErrorCodeConstants.java @@ -168,4 +168,11 @@ public interface ErrorCodeConstants { // ========== 站内信发送 1-002-028-000 ========== ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, "模板参数({})缺失"); + // ========== App 功能模块 1-002-029-000 ========== + ErrorCode APP_MODULE_NOT_EXISTS = new ErrorCode(1_002_029_000, "App 功能模块不存在"); + ErrorCode APP_MODULE_NAME_DUPLICATE = new ErrorCode(1_002_029_001, "已经存在该名字的 App 功能模块"); + ErrorCode APP_MODULE_PARENT_NOT_EXISTS = new ErrorCode(1_002_029_002, "父模块不存在"); + ErrorCode APP_MODULE_PARENT_ERROR = new ErrorCode(1_002_029_003, "不能设置自己为父模块"); + ErrorCode APP_MODULE_EXISTS_CHILDREN = new ErrorCode(1_002_029_004, "存在子模块,无法删除"); + ErrorCode APP_MODULE_PARENT_NOT_CATALOG = new ErrorCode(1_002_029_005, "父模块必须是目录类型"); } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleTypeEnum.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleTypeEnum.java new file mode 100644 index 0000000..275ea1e --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleTypeEnum.java @@ -0,0 +1,50 @@ +package com.zteits.urbanops.module.system.enums.appmodule; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * App 功能模块类型枚举 + * + * @author system + */ +@Getter +@AllArgsConstructor +public enum AppModuleTypeEnum { + + /** + * 目录 + */ + CATALOG(1, "目录"), + + /** + * 原生页面 + */ + NATIVE(2, "原生页面"), + + /** + * H5页面 + */ + H5(3, "H5页面"), + + /** + * 小程序 + */ + MINI_PROGRAM(4, "小程序"), + + /** + * 外部链接 + */ + EXTERNAL_LINK(5, "外部链接"); + + /** + * 类型 + */ + private final Integer type; + + /** + * 类型名 + */ + private final String name; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleVisibleScopeEnum.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleVisibleScopeEnum.java new file mode 100644 index 0000000..62580bd --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/enums/appmodule/AppModuleVisibleScopeEnum.java @@ -0,0 +1,49 @@ +package com.zteits.urbanops.module.system.enums.appmodule; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * App 功能模块可见范围枚举 + * + * @author system + */ +@Getter +@AllArgsConstructor +public enum AppModuleVisibleScopeEnum { + + /** + * 所有用户 + */ + ALL(1, "所有用户"), + + /** + * 指定用户类型 + */ + USER_TYPE(2, "指定用户类型"), + + /** + * 指定用户标签 + */ + USER_TAG(3, "指定用户标签"), + + /** + * 指定用户ID + */ + USER_ID(4, "指定用户ID"), + /** + * 指定角色编码 + */ + ROLE_ID(5, "指定角色编码"); + + /** + * 范围 + */ + private final Integer scope; + + /** + * 范围名 + */ + private final String name; + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleService.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleService.java new file mode 100644 index 0000000..52d0f31 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleService.java @@ -0,0 +1,103 @@ +package com.zteits.urbanops.module.system.service.appmodule; + + +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleListReqVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSaveVO; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; + +import java.util.Collection; +import java.util.List; + +/** + * App 功能模块 Service 接口 + * + * @author system + */ +public interface AppModuleService { + + /** + * 创建 App 功能模块 + * + * @param createReqVO 创建信息 + * @return 模块编号 + */ + Long createAppModule(AppModuleSaveVO createReqVO); + + /** + * 更新 App 功能模块 + * + * @param updateReqVO 更新信息 + */ + void updateAppModule(AppModuleSaveVO updateReqVO); + + /** + * 删除 App 功能模块 + * + * @param id 模块编号 + */ + void deleteAppModule(Long id); + + /** + * 批量删除 App 功能模块 + * + * @param ids 模块编号数组 + */ + void deleteAppModuleList(List ids); + + /** + * 获得 App 功能模块 + * + * @param id 模块编号 + * @return App 功能模块 + */ + AppModuleDO getAppModule(Long id); + + /** + * 获得 App 功能模块数组 + * + * @param ids 模块编号数组 + * @return App 功能模块数组 + */ + List getAppModuleList(Collection ids); + + /** + * 获得所有 App 功能模块列表 + * + * @return 模块列表 + */ + List getAppModuleList(); + + /** + * 筛选 App 功能模块列表 + * + * @param reqVO 筛选条件 + * @return 模块列表 + */ + List getAppModuleList(AppModuleListReqVO reqVO); + + /** + * 获取用户可见的 App 功能模块列表 + * + * @param userId 用户ID + * @param reqVO 筛选条件 + * @return 模块列表 + */ + List getVisibleAppModuleList(Long userId, AppModuleListReqVO reqVO); + + /** + * 过滤掉关闭的模块及其子模块 + * + * @param list 模块列表 + * @return 过滤后的模块列表 + */ + List filterDisableModules(List list); + + /** + * 填充模块统计数据 + * + * @param modules 模块列表 + * @param userId 用户ID + */ + void fillModuleStatistics(List modules, Long userId); + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleServiceImpl.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleServiceImpl.java new file mode 100644 index 0000000..86772ab --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/AppModuleServiceImpl.java @@ -0,0 +1,515 @@ +package com.zteits.urbanops.module.system.service.appmodule; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import com.zteits.urbanops.framework.common.enums.CommonStatusEnum; +import com.zteits.urbanops.framework.common.util.object.BeanUtils; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleListReqVO; +import com.zteits.urbanops.module.system.controller.admin.appmodule.vo.AppModuleSaveVO; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.dal.mysql.appmodule.AppModuleMapper; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleTypeEnum; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.statistics.StatisticsProviderRegistry; +import com.zteits.urbanops.module.system.service.appmodule.strategy.GrayReleaseStrategy; +import com.zteits.urbanops.module.system.service.appmodule.strategy.GrayReleaseStrategyFactory; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategyFactory; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.util.*; + +import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO.ID_ROOT; +import static com.zteits.urbanops.module.system.enums.ErrorCodeConstants.*; + + +/** + * App 功能模块 Service 实现类 + * + * @author system + */ +@Service +@Validated +@Slf4j +@AllArgsConstructor +public class AppModuleServiceImpl implements AppModuleService { + + private final AppModuleMapper appModuleMapper; + + private final VisibleScopeStrategyFactory visibleScopeStrategyFactory; + + private final GrayReleaseStrategyFactory grayReleaseStrategyFactory; + + private final StatisticsProviderRegistry statisticsProviderRegistry; + + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createAppModule(AppModuleSaveVO createReqVO) { + // 校验父模块存在 + validateParentModule(createReqVO.getParentId(), null); + // 校验模块名称唯一性 + validateModuleName(createReqVO.getParentId(), createReqVO.getName(), null); + + // 插入到数据库 + AppModuleDO module = BeanUtils.toBean(createReqVO, AppModuleDO.class); + initModuleProperty(module); + appModuleMapper.insert(module); + return module.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateAppModule(AppModuleSaveVO updateReqVO) { + // 校验更新的模块是否存在 + if (appModuleMapper.selectById(updateReqVO.getId()) == null) { + throw exception(APP_MODULE_NOT_EXISTS); + } + // 校验父模块存在 + validateParentModule(updateReqVO.getParentId(), updateReqVO.getId()); + // 校验模块名称唯一性 + validateModuleName(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId()); + + // 更新到数据库 + AppModuleDO updateObj = BeanUtils.toBean(updateReqVO, AppModuleDO.class); + initModuleProperty(updateObj); + appModuleMapper.updateById(updateObj); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteAppModule(Long id) { + // 校验是否还有子模块 + if (appModuleMapper.selectCountByParentId(id) > 0) { + throw exception(APP_MODULE_EXISTS_CHILDREN); + } + // 校验删除的模块是否存在 + if (appModuleMapper.selectById(id) == null) { + throw exception(APP_MODULE_NOT_EXISTS); + } + // 标记删除 + appModuleMapper.deleteById(id); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteAppModuleList(List ids) { + // 校验是否还有子模块 + ids.forEach(id -> { + if (appModuleMapper.selectCountByParentId(id) > 0) { + throw exception(APP_MODULE_EXISTS_CHILDREN); + } + }); + + // 标记删除 + appModuleMapper.deleteByIds(ids); + } + + @Override + public AppModuleDO getAppModule(Long id) { + return appModuleMapper.selectById(id); + } + + @Override + public List getAppModuleList(Collection ids) { + // 当 ids 为空时,返回一个空的实例对象 + if (CollUtil.isEmpty(ids)) { + return Lists.newArrayList(); + } + return appModuleMapper.selectByIds(ids); + } + + @Override + public List getAppModuleList() { + return appModuleMapper.selectList(); + } + + @Override + public List getAppModuleList(AppModuleListReqVO reqVO) { + return appModuleMapper.selectList(reqVO); + } + + @Override + public List getVisibleAppModuleList(Long userId, AppModuleListReqVO reqVO) { + + + // 查询所有启用的模块 + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + List modules = getAppModuleList(reqVO); + + // 过滤掉关闭的节点 + modules = filterDisableModules(modules); + + // 根据用户权限、可见范围等进行过滤 + modules = filterByVisibleScope(modules, userId); + + // 灰度发布过滤 + modules = filterByGrayRelease(modules, userId); + + // 父子级联权限过滤 + modules = filterByParentPermission(modules); + + return modules; + } + + @Override + public List filterDisableModules(List moduleList) { + if (CollUtil.isEmpty(moduleList)) { + return Collections.emptyList(); + } + Map moduleMap = new HashMap<>(); + for (AppModuleDO module : moduleList) { + moduleMap.put(module.getId(), module); + } + + // 遍历模块,查找不是禁用的模块,添加到结果 + List enabledModules = new ArrayList<>(); + Set disabledModuleCache = new HashSet<>(); + for (AppModuleDO module : moduleList) { + if (!isModuleDisabled(module, moduleMap, disabledModuleCache)) { + enabledModules.add(module); + } + } + return enabledModules; + } + + /** + * 判断模块是否被禁用(包括父模块) + */ + private boolean isModuleDisabled(AppModuleDO node, Map moduleMap, Set disabledModuleCache) { + // 如果已经判定是禁用的节点,直接结束 + if (disabledModuleCache.contains(node.getId())) { + return true; + } + + // 1. 先判断自身是否禁用 + if (CommonStatusEnum.isDisable(node.getStatus())) { + disabledModuleCache.add(node.getId()); + return true; + } + + // 2. 判断是否不可见 + if (node.getVisible() != null && !node.getVisible()) { + // 注意:不可见不一定是禁用,根据业务需求决定 + // 这里我们认为不可见的模块在列表中不展示 + disabledModuleCache.add(node.getId()); + return true; + } + + // 3. 遍历到 parentId 为根节点,则无需判断 + Long parentId = node.getParentId(); + if (ObjUtil.equal(parentId, ID_ROOT)) { + return false; + } + + // 4. 继续遍历 parent 节点 + AppModuleDO parent = moduleMap.get(parentId); + if (parent == null || isModuleDisabled(parent, moduleMap, disabledModuleCache)) { + disabledModuleCache.add(node.getId()); + return true; + } + return false; + } + + /** + * 根据可见范围过滤模块 + * + * @param modules 模块列表 + * @param userId 用户ID + * @return 过滤后的模块列表 + */ + private List filterByVisibleScope(List modules, Long userId) { + if (CollUtil.isEmpty(modules)) { + return modules; + } + + // 如果用户未登录,只返回所有用户可见的模块 + if (userId == null) { + List result = new ArrayList<>(); + for (AppModuleDO module : modules) { + if (AppModuleVisibleScopeEnum.ALL.getScope().equals(module.getVisibleScope())) { + result.add(module); + } + } + return result; + } + + List result = new ArrayList<>(); + for (AppModuleDO module : modules) { + if (isModuleVisibleToUser(module, userId)) { + result.add(module); + } + } + return result; + } + + /** + * 判断模块对用户是否可见 + * + * @param module 模块 + * @param userId 用户ID + * @return 是否可见 + */ + private boolean isModuleVisibleToUser(AppModuleDO module, Long userId) { + Integer visibleScope = module.getVisibleScope(); + if (visibleScope == null) { + visibleScope = AppModuleVisibleScopeEnum.ALL.getScope(); + } + + VisibleScopeStrategy strategy = visibleScopeStrategyFactory.getStrategy(visibleScope); + if (strategy == null) { + log.warn("[可见范围策略] 未找到对应策略. visibleScope={}", visibleScope); + return false; + } + + return strategy.isVisible(module, userId); + } + + /** + * 根据灰度发布过滤模块 + * + * @param modules 模块列表 + * @param userId 用户ID + * @return 过滤后的模块列表 + */ + private List filterByGrayRelease(List modules, Long userId) { + if (CollUtil.isEmpty(modules) || userId == null) { + return modules; + } + + List result = new ArrayList<>(); + for (AppModuleDO module : modules) { + // 如果模块没有启用灰度发布,直接通过 + if (module.getGrayRelease() == null || !module.getGrayRelease()) { + result.add(module); + continue; + } + // 判断用户是否命中灰度规则 + if (isUserInGrayRelease(module.getGrayConfig(), userId)) { + result.add(module); + } + } + return result; + } + + /** + * 父级不可见时,级联剔除子模块 + */ + private List filterByParentPermission(List modules) { + if (CollUtil.isEmpty(modules)) { + return modules; + } + + Map moduleMap = new HashMap<>(); + for (AppModuleDO module : modules) { + moduleMap.put(module.getId(), module); + } + + Set allowed = new HashSet<>(); + Set blocked = new HashSet<>(); + List result = new ArrayList<>(); + for (AppModuleDO module : modules) { + if (isParentChainVisible(module, moduleMap, allowed, blocked)) { + result.add(module); + } + } + return result; + } + + private boolean isParentChainVisible(AppModuleDO module, Map moduleMap, + Set allowed, Set blocked) { + Long moduleId = module.getId(); + if (allowed.contains(moduleId)) { + return true; + } + if (blocked.contains(moduleId)) { + return false; + } + + Long parentId = module.getParentId(); + if (parentId == null || ObjUtil.equal(parentId, ID_ROOT)) { + allowed.add(moduleId); + return true; + } + + AppModuleDO parent = moduleMap.get(parentId); + if (parent == null) { + blocked.add(moduleId); + return false; + } + + if (isParentChainVisible(parent, moduleMap, allowed, blocked)) { + allowed.add(moduleId); + return true; + } + + blocked.add(moduleId); + return false; + } + + /** + * 判断用户是否命中灰度发布规则 + * + * @param grayConfig 灰度配置 Map + * @param userId 用户ID + * @return 是否命中 + */ + private boolean isUserInGrayRelease(Map grayConfig, Long userId) { + if (grayConfig == null || grayConfig.isEmpty()) { + return false; + } + + try { + String type = (String) grayConfig.get("type"); + + GrayReleaseStrategy strategy = grayReleaseStrategyFactory.getStrategy(type); + if (strategy == null) { + log.warn("[灰度发布策略] 未找到对应策略. type={}", type); + return false; + } + + return strategy.isMatch(grayConfig, userId); + } catch (Exception e) { + log.error("[解析灰度发布配置失败] grayConfig={}", grayConfig, e); + return false; + } + } + + /** + * 校验父模块是否合法 + * + * 1. 不能设置自己为父模块 + * 2. 父模块不存在 + * 3. 父模块必须是目录类型 + */ + private void validateParentModule(Long parentId, Long childId) { + if (parentId == null || ID_ROOT.equals(parentId)) { + return; + } + // 不能设置自己为父模块 + if (parentId.equals(childId)) { + throw exception(APP_MODULE_PARENT_ERROR); + } + AppModuleDO module = appModuleMapper.selectById(parentId); + // 父模块不存在 + if (module == null) { + throw exception(APP_MODULE_PARENT_NOT_EXISTS); + } + // 父模块必须是目录类型 + if (!AppModuleTypeEnum.CATALOG.getType().equals(module.getType())) { + throw exception(APP_MODULE_PARENT_NOT_CATALOG); + } + } + + /** + * 校验模块名称是否合法 + * + * 1. 校验相同父模块编号下,是否存在相同的模块名 + */ + private void validateModuleName(Long parentId, String name, Long id) { + AppModuleDO module = appModuleMapper.selectByParentIdAndName(parentId, name); + if (module == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的模块 + if (id == null) { + throw exception(APP_MODULE_NAME_DUPLICATE); + } + if (!module.getId().equals(id)) { + throw exception(APP_MODULE_NAME_DUPLICATE); + } + } + + /** + * 初始化模块的通用属性 + */ + private void initModuleProperty(AppModuleDO module) { + // 目录类型时,清空跳转路径等属性 + if (AppModuleTypeEnum.CATALOG.getType().equals(module.getType())) { + module.setJumpUrl(""); + module.setMiniAppId(""); + } + + // 设置默认值 + if (module.getVisible() == null) { + module.setVisible(true); + } + if (module.getGrayRelease() == null) { + module.setGrayRelease(false); + } + if (module.getVisibleScope() == null) { + module.setVisibleScope(AppModuleVisibleScopeEnum.ALL.getScope()); + } + } + + @Override + public void fillModuleStatistics(List modules, Long userId) { + if (CollUtil.isEmpty(modules) || userId == null) { + return; + } + + // 需要统计的模块集合 + Map statisticModules = new HashMap<>(); + + for (AppModuleDO module : modules) { + // 从 extra 字段中获取统计配置 + Map extra = module.getExtra(); + if (extra != null && extra.containsKey("statisticKey")) { + String statisticKey = (String) extra.get("statisticKey"); + if (StrUtil.isNotBlank(statisticKey)) { + statisticModules.put(statisticKey, module); + } + } + } + + if (statisticModules.isEmpty()) { + return; + } + + // 并行查询统计数据 + Map statisticCounts = queryStatisticCounts(statisticModules.keySet(), userId); + + // 填充统计数据 + statisticCounts.forEach((key, count) -> { + AppModuleDO module = statisticModules.get(key); + if (module != null) { + // 填充到 extra 字段中 + if (module.getExtra() == null) { + module.setExtra(new HashMap<>()); + } + module.getExtra().put("statisticCount", count); + } + }); + } + + /** + * 查询统计数据(根据模块标识) + * + * @param statisticKeys 统计标识集合 + * @param userId 用户ID + * @return 统计结果 Map<统计标识, 数量> + */ + private Map queryStatisticCounts(Set statisticKeys, Long userId) { + Map result = new HashMap<>(); + + for (String key : statisticKeys) { + try { + Integer count = statisticsProviderRegistry.getCount(key, userId); + result.put(key, count != null ? count : 0); + } catch (Exception e) { + log.error("查询模块统计数据失败, statisticKey={}", key, e); + result.put(key, 0); + } + } + + return result; + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticProvider.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticProvider.java new file mode 100644 index 0000000..b4d4ac3 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticProvider.java @@ -0,0 +1,20 @@ +package com.zteits.urbanops.module.system.service.appmodule.statistics; + +/** + * 统计数据提供者 SPI + * + * 实现方(上层业务模块,如 reserve 等)在自身模块中提供具体实现, + * system 模块只依赖此接口,不依赖具体实现,避免向上依赖。 + */ +public interface StatisticProvider { + + /** + * 返回当前提供者负责的统计标识(例如:manager_authenticated、insured_authenticated 等) + */ + String getKey(); + + /** + * 根据用户 ID 返回统计数量 + */ + Integer getCount(Long userId); +} \ No newline at end of file diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticsProviderRegistry.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticsProviderRegistry.java new file mode 100644 index 0000000..7cc4cb5 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/statistics/StatisticsProviderRegistry.java @@ -0,0 +1,40 @@ +package com.zteits.urbanops.module.system.service.appmodule.statistics; + +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 统计提供者路由器 + * + * 收集所有 {@link StatisticProvider} 实现,并按 key 路由调用。 + */ +@Component +public class StatisticsProviderRegistry { + + private final List providers; + + public StatisticsProviderRegistry(List providers) { + this.providers = providers; + } + + /** + * 根据 statisticKey 查找提供者并返回统计数;找不到或异常时返回 0。 + */ + public Integer getCount(String statisticKey, Long userId) { + if (providers == null || providers.isEmpty()) { + return 0; + } + for (StatisticProvider provider : providers) { + if (provider != null && statisticKey != null && statisticKey.equals(provider.getKey())) { + try { + Integer count = provider.getCount(userId); + return count != null ? count : 0; + } catch (Exception ignored) { + return 0; + } + } + } + return 0; + } +} \ No newline at end of file diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategy.java new file mode 100644 index 0000000..6181391 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategy.java @@ -0,0 +1,28 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy; + +import java.util.Map; + +/** + * 灰度发布策略接口 + * + * @author system + */ +public interface GrayReleaseStrategy { + + /** + * 判断用户是否命中灰度规则 + * + * @param config 灰度配置 + * @param userId 用户ID + * @return 是否命中 + */ + boolean isMatch(Map config, Long userId); + + /** + * 获取策略类型 + * + * @return 策略类型 + */ + String getType(); + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategyFactory.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategyFactory.java new file mode 100644 index 0000000..ac58013 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/GrayReleaseStrategyFactory.java @@ -0,0 +1,43 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 灰度发布策略工厂 + * + * @author system + */ +@Component +public class GrayReleaseStrategyFactory { + + @Resource + private List strategies; + + private final Map strategyMap = new HashMap<>(); + + @PostConstruct + public void init() { + for (GrayReleaseStrategy strategy : strategies) { + strategyMap.put(strategy.getType(), strategy); + } + // userIds 类型使用相同的白名单策略 + strategyMap.put("userIds", strategyMap.get("whitelist")); + } + + /** + * 获取策略 + * + * @param type 策略类型 + * @return 策略实例 + */ + public GrayReleaseStrategy getStrategy(String type) { + return strategyMap.get(type); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategy.java new file mode 100644 index 0000000..877c25d --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategy.java @@ -0,0 +1,29 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy; + + +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; + +/** + * 可见范围策略接口 + * + * @author system + */ +public interface VisibleScopeStrategy { + + /** + * 判断模块对用户是否可见 + * + * @param module 模块信息 + * @param userId 用户ID + * @return 是否可见 + */ + boolean isVisible(AppModuleDO module, Long userId); + + /** + * 获取策略类型 + * + * @return 策略类型 + */ + Integer getType(); + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategyFactory.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategyFactory.java new file mode 100644 index 0000000..a52de1d --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/VisibleScopeStrategyFactory.java @@ -0,0 +1,41 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 可见范围策略工厂 + * + * @author system + */ +@Component +public class VisibleScopeStrategyFactory { + + @Resource + private List strategies; + + private final Map strategyMap = new HashMap<>(); + + @PostConstruct + public void init() { + for (VisibleScopeStrategy strategy : strategies) { + strategyMap.put(strategy.getType(), strategy); + } + } + + /** + * 获取策略 + * + * @param type 策略类型 + * @return 策略实例 + */ + public VisibleScopeStrategy getStrategy(Integer type) { + return strategyMap.get(type); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/AllVisibleStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/AllVisibleStrategy.java new file mode 100644 index 0000000..26c6700 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/AllVisibleStrategy.java @@ -0,0 +1,26 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import org.springframework.stereotype.Component; + +/** + * 所有用户可见策略 + * + * @author system + */ +@Component +public class AllVisibleStrategy implements VisibleScopeStrategy { + + @Override + public boolean isVisible(AppModuleDO module, Long userId) { + return true; + } + + @Override + public Integer getType() { + return AppModuleVisibleScopeEnum.ALL.getScope(); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/PercentGrayStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/PercentGrayStrategy.java new file mode 100644 index 0000000..920f2f6 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/PercentGrayStrategy.java @@ -0,0 +1,34 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import com.zteits.urbanops.module.system.service.appmodule.strategy.GrayReleaseStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Map; + +/** + * 百分比灰度策略 + * + * @author system + */ +@Component +@Slf4j +public class PercentGrayStrategy implements GrayReleaseStrategy { + + @Override + public boolean isMatch(Map config, Long userId) { + try { + Integer percent = (Integer) config.get("value"); + return userId % 100 < percent; + } catch (Exception e) { + log.error("[百分比灰度策略] 解析配置失败. config={}", config, e); + return false; + } + } + + @Override + public String getType() { + return "percent"; + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/RoleVisibleStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/RoleVisibleStrategy.java new file mode 100644 index 0000000..a8acd36 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/RoleVisibleStrategy.java @@ -0,0 +1,81 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.dal.dataobject.permission.RoleDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import com.zteits.urbanops.module.system.service.permission.PermissionService; +import com.zteits.urbanops.module.system.service.permission.RoleService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 指定角色编码可见策略 + */ +@Component +@Slf4j +@RequiredArgsConstructor +public class RoleVisibleStrategy implements VisibleScopeStrategy { + + private final PermissionService permissionService; + private final RoleService roleService; + + @Override + public boolean isVisible(AppModuleDO module, Long userId) { + if (userId == null) { + return false; + } + List visibleConfig = module.getVisibleConfig(); + if (CollUtil.isEmpty(visibleConfig)) { + return false; + } + + Set userRoleIds = permissionService.getUserRoleIdListByUserIdFromCache(userId); + if (CollUtil.isEmpty(userRoleIds)) { + return false; + } + + List roles = roleService.getRoleListFromCache(userRoleIds); + if (CollUtil.isEmpty(roles)) { + return false; + } + + Set userRoleCodes = roles.stream() + .map(RoleDO::getCode) + .filter(StrUtil::isNotBlank) + .collect(Collectors.toSet()); + if (CollUtil.isEmpty(userRoleCodes)) { + return false; + } + + for (Object configValue : visibleConfig) { + String requiredCode = convertToCode(configValue); + if (StrUtil.isBlank(requiredCode)) { + continue; + } + if (userRoleCodes.contains(requiredCode)) { + return true; + } + } + return false; + } + + private String convertToCode(Object configValue) { + if (configValue == null) { + return null; + } + return StrUtil.trimToNull(configValue.toString()); + } + + @Override + public Integer getType() { + return AppModuleVisibleScopeEnum.ROLE_ID.getScope(); + } +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserIdVisibleStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserIdVisibleStrategy.java new file mode 100644 index 0000000..84575cc --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserIdVisibleStrategy.java @@ -0,0 +1,47 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * 指定用户ID可见策略 + * + * @author system + */ +@Component +@Slf4j +public class UserIdVisibleStrategy implements VisibleScopeStrategy { + + @Override + public boolean isVisible(AppModuleDO module, Long userId) { + List visibleConfig = module.getVisibleConfig(); + if (CollectionUtils.isEmpty(visibleConfig)) { + return false; + } + + try { + // 将userId转为字符串 + String userIdStr = String.valueOf(userId); + + // 检查visibleConfig中是否有元素转换为字符串后与userIdStr相等 + return visibleConfig.stream() + .map(String::valueOf) + .anyMatch(userIdStr::equals); + } catch (Exception e) { + log.error("[用户ID可见策略] 解析配置失败. config={}", visibleConfig, e); + return false; + } + } + + @Override + public Integer getType() { + return AppModuleVisibleScopeEnum.USER_ID.getScope(); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTagVisibleStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTagVisibleStrategy.java new file mode 100644 index 0000000..1ba8b3a --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTagVisibleStrategy.java @@ -0,0 +1,43 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 指定用户标签可见策略 + * + * @author system + */ +@Component +@Slf4j +public class UserTagVisibleStrategy implements VisibleScopeStrategy { + + @Override + public boolean isVisible(AppModuleDO module, Long userId) { + // TODO: 根据实际业务获取用户标签,这里是示例实现 + // List userTags = userService.getUserTags(userId); + // String visibleConfig = module.getVisibleConfig(); + // if (StrUtil.isBlank(visibleConfig)) { + // return false; + // } + // try { + // List allowedTags = JSONUtil.toList(visibleConfig, String.class); + // return CollUtil.containsAny(userTags, allowedTags); + // } catch (Exception e) { + // log.error("[用户标签可见策略] 解析配置失败. config={}", visibleConfig, e); + // return false; + // } + + log.warn("[用户标签可见策略] 待实现,默认返回 true. userId={}", userId); + return true; + } + + @Override + public Integer getType() { + return AppModuleVisibleScopeEnum.USER_TAG.getScope(); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTypeVisibleStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTypeVisibleStrategy.java new file mode 100644 index 0000000..f2ceb33 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/UserTypeVisibleStrategy.java @@ -0,0 +1,43 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import com.zteits.urbanops.module.system.dal.dataobject.appmodule.AppModuleDO; +import com.zteits.urbanops.module.system.enums.appmodule.AppModuleVisibleScopeEnum; +import com.zteits.urbanops.module.system.service.appmodule.strategy.VisibleScopeStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 指定用户类型可见策略 + * + * @author system + */ +@Component +@Slf4j +public class UserTypeVisibleStrategy implements VisibleScopeStrategy { + + @Override + public boolean isVisible(AppModuleDO module, Long userId) { + // TODO: 根据实际业务获取用户类型,这里是示例实现 + // String userType = userService.getUserType(userId); + // String visibleConfig = module.getVisibleConfig(); + // if (StrUtil.isBlank(visibleConfig)) { + // return false; + // } + // try { + // List allowedTypes = JSONUtil.toList(visibleConfig, String.class); + // return allowedTypes.contains(userType); + // } catch (Exception e) { + // log.error("[用户类型可见策略] 解析配置失败. config={}", visibleConfig, e); + // return false; + // } + + log.warn("[用户类型可见策略] 待实现,默认返回 true. userId={}", userId); + return true; + } + + @Override + public Integer getType() { + return AppModuleVisibleScopeEnum.USER_TYPE.getScope(); + } + +} diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/WhitelistGrayStrategy.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/WhitelistGrayStrategy.java new file mode 100644 index 0000000..c14c648 --- /dev/null +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/service/appmodule/strategy/impl/WhitelistGrayStrategy.java @@ -0,0 +1,36 @@ +package com.zteits.urbanops.module.system.service.appmodule.strategy.impl; + +import cn.hutool.json.JSONUtil; +import com.zteits.urbanops.module.system.service.appmodule.strategy.GrayReleaseStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * 白名单灰度策略 + * + * @author system + */ +@Component +@Slf4j +public class WhitelistGrayStrategy implements GrayReleaseStrategy { + + @Override + public boolean isMatch(Map config, Long userId) { + try { + List userIds = JSONUtil.toList(JSONUtil.toJsonStr(config.get("value")), Long.class); + return userIds.contains(userId); + } catch (Exception e) { + log.error("[白名单灰度策略] 解析配置失败. config={}", config, e); + return false; + } + } + + @Override + public String getType() { + return "whitelist"; + } + +}