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 4e1aeda5..a7899b69 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 @@ -212,4 +212,7 @@ public interface ErrorCodeConstants { // ========== 系统序列号记录 1-002-032-000 ========== ErrorCode SEQUENCE_RECORD_NOT_EXISTS = new ErrorCode(1_002_032_000, "系统序列号记录不存在"); + // ========== 门户网站 1-002-033-000 ========== + ErrorCode PORTAL_NOT_EXISTS = new ErrorCode(1_002_033_000, "门户不存在"); + } diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/PortalController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/PortalController.java new file mode 100644 index 00000000..c956f82e --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/PortalController.java @@ -0,0 +1,105 @@ +package com.zt.plat.module.system.controller.admin.portal; + +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.framework.excel.core.util.ExcelUtils; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalPageReqVO; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalRespVO; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.portal.PortalDO; +import com.zt.plat.module.system.service.portal.PortalService; +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.servlet.http.HttpServletResponse; +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.io.IOException; +import java.util.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * 门户网站 Controller + * + * @author 中铜数字供应链平台 + */ +@Tag(name = "管理后台 - 门户网站") +@RestController +@RequestMapping("/system/portal") +@Validated +public class PortalController { + + @Resource + private PortalService portalService; + + @PostMapping("/create") + @Operation(summary = "创建门户网站") + @PreAuthorize("@ss.hasPermission('system:portal:create')") + public CommonResult createPortal(@Valid @RequestBody PortalSaveReqVO createReqVO) { + return success(portalService.createPortal(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新门户网站") + @PreAuthorize("@ss.hasPermission('system:portal:update')") + public CommonResult updatePortal(@Valid @RequestBody PortalSaveReqVO updateReqVO) { + portalService.updatePortal(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除门户网站") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:portal:delete')") + public CommonResult deletePortal(@RequestParam("id") Long id) { + portalService.deletePortal(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得门户网站") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:portal:query')") + public CommonResult getPortal(@RequestParam("id") Long id) { + PortalDO portal = portalService.getPortal(id); + return success(BeanUtils.toBean(portal, PortalRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得门户网站分页") + @PreAuthorize("@ss.hasPermission('system:portal:query')") + public CommonResult> getPortalPage(@Valid PortalPageReqVO pageReqVO) { + PageResult pageResult = portalService.getPortalPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, PortalRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出门户网站 Excel") + @PreAuthorize("@ss.hasPermission('system:portal:export')") + public void exportPortalExcel(@Valid PortalPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + List list = portalService.getPortalPage(pageReqVO).getList(); + // 导出 Excel + List data = BeanUtils.toBean(list, PortalRespVO.class); + ExcelUtils.write(response, "门户网站.xls", "数据", PortalRespVO.class, data); + } + /** + * 获取当前用户可访问的门户列表 + * 此接口无需权限验证,因为已经通过登录验证, + * 返回的门户列表已经根据用户权限进行了过滤 + */ + @GetMapping("/list") + @Operation(summary = "获取我的门户列表") + public CommonResult> getMyPortalList() { + Long userId = getLoginUserId(); + List portals = portalService.getPortalListByUserId(userId); + return success(BeanUtils.toBean(portals, PortalRespVO.class)); + } +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalPageReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalPageReqVO.java new file mode 100644 index 00000000..099c6693 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalPageReqVO.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.system.controller.admin.portal.vo; + +import com.zt.plat.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 门户网站分页查询 Request VO + * + * @author 中铜数字供应链平台 + */ +@Schema(description = "管理后台 - 门户网站分页查询 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class PortalPageReqVO extends PageParam { + + @Schema(description = "门户名称", example = "采购") + private String name; + + @Schema(description = "状态", example = "0") + private Integer status; + + @Schema(description = "门户分类", example = "业务系统") + private String category; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalRespVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalRespVO.java new file mode 100644 index 00000000..5150c662 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalRespVO.java @@ -0,0 +1,73 @@ +package com.zt.plat.module.system.controller.admin.portal.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 门户网站 Response VO + * + * @author 中铜数字供应链平台 + */ +@Schema(description = "管理后台 - 门户网站 Response VO") +@Data +@ExcelIgnoreUnannotated +public class PortalRespVO { + + @Schema(description = "门户编号", example = "1024") + @ExcelProperty("门户编号") + private Long id; + + @Schema(description = "门户名称", example = "采购管理系统") + @ExcelProperty("门户名称") + private String name; + + @Schema(description = "门户描述", example = "统一采购平台,支持在线询价、比价") + @ExcelProperty("门户描述") + private String description; + + @Schema(description = "门户地址", example = "http://purchase.zt.com") + @ExcelProperty("门户地址") + private String url; + + @Schema(description = "门户图标(Element Plus图标)", example = "ep:shopping-cart") + private String icon; + + @Schema(description = "图标类型", example = "1") + @ExcelProperty("图标类型") + private Integer iconType; + + @Schema(description = "图标图片URL(当 iconType=2 时使用)", example = "https://example.com/icon.png") + private String iconUrl; + + @Schema(description = "门户分类", example = "业务系统") + @ExcelProperty("门户分类") + private String category; + + @Schema(description = "显示排序", example = "1") + @ExcelProperty("显示排序") + private Integer sort; + + @Schema(description = "状态", example = "0") + @ExcelProperty(value = "状态", converter = com.zt.plat.framework.excel.core.convert.DictConvert.class) + private Integer status; + + @Schema(description = "权限标识", example = "portal:purchase:access") + @ExcelProperty("权限标识") + private String permission; + + @Schema(description = "打开方式", example = "1") + @ExcelProperty("打开方式") + private Integer openType; + + @Schema(description = "认证配置(JSON格式)", example = "{\"enabled\":true,\"type\":\"url_params\",\"urlParams\":[{\"key\":\"token\",\"value\":\"${userToken}\"}]}") + private String authConfig; + + @Schema(description = "创建时间", example = "2025-11-10 10:00:00") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalSaveReqVO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalSaveReqVO.java new file mode 100644 index 00000000..85adc014 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/admin/portal/vo/PortalSaveReqVO.java @@ -0,0 +1,81 @@ +package com.zt.plat.module.system.controller.admin.portal.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 门户网站创建/修改 Request VO + * + * @author 中铜数字供应链平台 + */ +@Schema(description = "管理后台 - 门户网站创建/修改 Request VO") +@Data +public class PortalSaveReqVO { + + @Schema(description = "门户编号", example = "1024") + private Long id; + + @Schema(description = "门户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "采购管理系统") + @NotEmpty(message = "门户名称不能为空") + @Size(max = 50, message = "门户名称长度不能超过50个字符") + private String name; + + @Schema(description = "门户描述", example = "统一采购平台,支持在线询价、比价") + @Size(max = 200, message = "门户描述长度不能超过200个字符") + private String description; + + @Schema(description = "门户地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://purchase.zt.com") + @NotEmpty(message = "门户地址不能为空") + @Size(max = 500, message = "门户地址长度不能超过500个字符") + @Pattern(regexp = "^(http|https)://.*", message = "门户地址必须以 http:// 或 https:// 开头") + private String url; + + @Schema(description = "门户图标(Element Plus图标)", example = "ep:shopping-cart") + @Size(max = 100, message = "门户图标长度不能超过100个字符") + private String icon; + + @Schema(description = "图标类型", example = "1") + @Min(value = 1, message = "图标类型值不正确") + @Max(value = 2, message = "图标类型值不正确") + private Integer iconType; + + @Schema(description = "图标图片URL(当 iconType=2 时使用)", example = "https://example.com/icon.png") + @Size(max = 500, message = "图标图片URL长度不能超过500个字符") + private String iconUrl; + + @Schema(description = "门户分类", example = "业务系统") + @Size(max = 50, message = "门户分类长度不能超过50个字符") + private String category; + + @Schema(description = "显示排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "显示排序不能为空") + @Min(value = 0, message = "显示排序不能小于0") + private Integer sort; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + @Min(value = 0, message = "状态值不正确") + @Max(value = 1, message = "状态值不正确") + private Integer status; + + @Schema(description = "权限标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "portal:purchase:access") + @NotEmpty(message = "权限标识不能为空") + @Size(max = 100, message = "权限标识长度不能超过100个字符") + @Pattern(regexp = "^portal:[a-z]+:access$", message = "权限标识格式不正确,应为 portal:{module}:access") + private String permission; + + @Schema(description = "打开方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "打开方式不能为空") + @Min(value = 1, message = "打开方式值不正确") + @Max(value = 3, message = "打开方式值不正确") + private Integer openType; + + @Schema(description = "父菜单ID(权限自动同步到该菜单下)", example = "2500") + private Long parentMenuId; + + @Schema(description = "认证配置(JSON格式)", example = "{\"enabled\":true,\"type\":\"url_params\",\"urlParams\":[{\"key\":\"token\",\"value\":\"${userToken}\"}]}") + private String authConfig; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/app/portal/AppPortalController.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/app/portal/AppPortalController.java new file mode 100644 index 00000000..a1386fe7 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/controller/app/portal/AppPortalController.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.system.controller.app.portal; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalRespVO; +import com.zt.plat.module.system.dal.dataobject.portal.PortalDO; +import com.zt.plat.module.system.service.portal.PortalService; +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.List; + +import static com.zt.plat.framework.common.pojo.CommonResult.success; +import static com.zt.plat.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +/** + * 用户端 - 门户网站 Controller + * + * @author 中铜数字供应链平台 + */ +@Tag(name = "用户端 - 门户网站") +@RestController +@RequestMapping("/system/portal") +@Validated +public class AppPortalController { + + @Resource + private PortalService portalService; + + /** + * 获取当前用户可访问的门户列表 + * 此接口无需权限验证,因为已经通过登录验证, + * 返回的门户列表已经根据用户权限进行了过滤 + */ + @GetMapping("/list") + @Operation(summary = "获取我的门户列表") + public CommonResult> getMyPortalList() { + Long userId = getLoginUserId(); + List portals = portalService.getPortalListByUserId(userId); + return success(BeanUtils.toBean(portals, PortalRespVO.class)); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/portal/PortalDO.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/portal/PortalDO.java new file mode 100644 index 00000000..4cb722c6 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/dataobject/portal/PortalDO.java @@ -0,0 +1,106 @@ +package com.zt.plat.module.system.dal.dataobject.portal; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zt.plat.framework.tenant.core.db.TenantBaseDO; +import lombok.*; + +/** + * 门户网站 DO + * + * @author 中铜数字供应链平台 + */ +@TableName("system_portal") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PortalDO extends TenantBaseDO { + + /** + * 门户编号 + */ + @TableId + private Long id; + + /** + * 门户名称 + */ + private String name; + + /** + * 门户描述 + */ + private String description; + + /** + * 门户地址 + */ + private String url; + + /** + * 门户图标(Element Plus图标,如:ep:shopping-cart) + */ + private String icon; + + /** + * 图标类型 + * + * 1 - 图标库(使用 icon 字段) + * 2 - 自定义图片(使用 iconUrl 字段) + */ + private Integer iconType; + + /** + * 图标图片URL(当 iconType=2 时使用) + */ + private String iconUrl; + + /** + * 门户分类 + * + * 枚举 {@link PortalCategoryEnum} + */ + private String category; + + /** + * 显示排序 + */ + private Integer sort; + + /** + * 状态 + * + * 枚举 {@link com.zt.plat.framework.common.enums.CommonStatusEnum} + */ + private Integer status; + + /** + * 权限标识 + * + * 格式:portal:{module}:access + * 例如:portal:purchase:access + */ + private String permission; + + /** + * 打开方式 + * + * 枚举 {@link PortalOpenTypeEnum} + * 1 - 新窗口打开 + * 2 - 当前窗口打开 + * 3 - iframe 内嵌 + */ + private Integer openType; + + /** + * 认证配置(JSON格式) + * + * 用于配置跳转时的认证信息(URL参数或请求头) + * 示例:{"enabled":true,"type":"url_params","urlParams":[{"key":"token","value":"${userToken}"}]} + */ + private String authConfig; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/portal/PortalMapper.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/portal/PortalMapper.java new file mode 100644 index 00000000..5e9f73ac --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/dal/mysql/portal/PortalMapper.java @@ -0,0 +1,44 @@ +package com.zt.plat.module.system.dal.mysql.portal; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX; +import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalPageReqVO; +import com.zt.plat.module.system.dal.dataobject.portal.PortalDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 门户网站 Mapper + * + * @author 中铜数字供应链平台 + */ +@Mapper +public interface PortalMapper extends BaseMapperX { + + default PageResult selectPage(PortalPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(PortalDO::getName, reqVO.getName()) + .eqIfPresent(PortalDO::getStatus, reqVO.getStatus()) + .eqIfPresent(PortalDO::getCategory, reqVO.getCategory()) + .betweenIfPresent(PortalDO::getCreateTime, reqVO.getCreateTime()) + .orderByAsc(PortalDO::getSort) + .orderByDesc(PortalDO::getId)); + } + + /** + * 根据权限标识列表查询门户列表 + * + * @param permissions 权限标识列表 + * @return 门户列表 + */ + default List selectListByPermissions(List permissions) { + return selectList(new LambdaQueryWrapperX() + .in(PortalDO::getPermission, permissions) + .eq(PortalDO::getStatus, 0) // 只查询启用的门户 + .orderByAsc(PortalDO::getSort) + .orderByDesc(PortalDO::getId)); + } + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalCategoryEnum.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalCategoryEnum.java new file mode 100644 index 00000000..2a8e990b --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalCategoryEnum.java @@ -0,0 +1,29 @@ +package com.zt.plat.module.system.enums.portal; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 门户分类枚举 + * + * @author 中铜数字供应链平台 + */ +@Getter +@AllArgsConstructor +public enum PortalCategoryEnum { + + BUSINESS("业务系统", "核心业务系统,如采购、销售、财务等"), + MANAGEMENT("管理工具", "管理辅助工具,如OA、数据分析等"), + THIRD_PARTY("第三方系统", "外部集成系统,如ERP、E办等"); + + /** + * 分类名称 + */ + private final String name; + + /** + * 分类描述 + */ + private final String description; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalOpenTypeEnum.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalOpenTypeEnum.java new file mode 100644 index 00000000..5e109797 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/enums/portal/PortalOpenTypeEnum.java @@ -0,0 +1,34 @@ +package com.zt.plat.module.system.enums.portal; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 门户打开方式枚举 + * + * @author 中铜数字供应链平台 + */ +@Getter +@AllArgsConstructor +public enum PortalOpenTypeEnum { + + NEW_WINDOW(1, "新窗口", "在新窗口中打开门户"), + CURRENT_WINDOW(2, "当前窗口", "在当前窗口中打开门户"), + IFRAME(3, "iframe内嵌", "在iframe中内嵌显示门户"); + + /** + * 类型 + */ + private final Integer type; + + /** + * 名称 + */ + private final String name; + + /** + * 描述 + */ + private final String description; + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalService.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalService.java new file mode 100644 index 00000000..192b1851 --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalService.java @@ -0,0 +1,63 @@ +package com.zt.plat.module.system.service.portal; + +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalPageReqVO; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.portal.PortalDO; + +import java.util.List; + +/** + * 门户网站 Service 接口 + * + * @author 中铜数字供应链平台 + */ +public interface PortalService { + + /** + * 创建门户 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createPortal(PortalSaveReqVO createReqVO); + + /** + * 更新门户 + * + * @param updateReqVO 更新信息 + */ + void updatePortal(PortalSaveReqVO updateReqVO); + + /** + * 删除门户 + * + * @param id 编号 + */ + void deletePortal(Long id); + + /** + * 获得门户 + * + * @param id 编号 + * @return 门户 + */ + PortalDO getPortal(Long id); + + /** + * 获得门户分页 + * + * @param pageReqVO 分页查询 + * @return 门户分页 + */ + PageResult getPortalPage(PortalPageReqVO pageReqVO); + + /** + * 获得用户有权限访问的门户列表 + * + * @param userId 用户ID + * @return 门户列表 + */ + List getPortalListByUserId(Long userId); + +} diff --git a/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalServiceImpl.java b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalServiceImpl.java new file mode 100644 index 00000000..6749e9db --- /dev/null +++ b/zt-module-system/zt-module-system-server/src/main/java/com/zt/plat/module/system/service/portal/PortalServiceImpl.java @@ -0,0 +1,193 @@ +package com.zt.plat.module.system.service.portal; + +import com.google.common.annotations.VisibleForTesting; +import com.zt.plat.framework.common.pojo.PageResult; +import com.zt.plat.framework.common.util.object.BeanUtils; +import com.zt.plat.module.system.controller.admin.permission.vo.menu.MenuSaveVO; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalPageReqVO; +import com.zt.plat.module.system.controller.admin.portal.vo.PortalSaveReqVO; +import com.zt.plat.module.system.dal.dataobject.permission.MenuDO; +import com.zt.plat.module.system.dal.dataobject.portal.PortalDO; +import com.zt.plat.module.system.dal.mysql.portal.PortalMapper; +import com.zt.plat.module.system.enums.permission.MenuTypeEnum; +import com.zt.plat.module.system.service.permission.MenuService; +import com.zt.plat.module.system.service.permission.PermissionService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +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.PORTAL_NOT_EXISTS; + +/** + * 门户网站 Service 实现类 + * + * @author 中铜数字供应链平台 + */ +@Service +public class PortalServiceImpl implements PortalService { + + @Resource + private PortalMapper portalMapper; + + @Resource + private PermissionService permissionService; + + @Resource + private MenuService menuService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createPortal(PortalSaveReqVO createReqVO) { + // 1. 创建门户 + PortalDO portal = BeanUtils.toBean(createReqVO, PortalDO.class); + portalMapper.insert(portal); + + // 2. 自动创建对应的菜单权限 + if (createReqVO.getParentMenuId() != null) { + syncMenuPermission(portal, createReqVO.getParentMenuId(), null); + } + + return portal.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePortal(PortalSaveReqVO updateReqVO) { + // 1. 校验是否存在 + PortalDO oldPortal = validatePortalExists(updateReqVO.getId()); + + // 2. 更新门户 + PortalDO updateObj = BeanUtils.toBean(updateReqVO, PortalDO.class); + portalMapper.updateById(updateObj); + + // 3. 自动更新对应的菜单权限 + if (updateReqVO.getParentMenuId() != null) { + syncMenuPermission(updateObj, updateReqVO.getParentMenuId(), oldPortal.getPermission()); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePortal(Long id) { + // 1. 校验是否存在 + PortalDO portal = validatePortalExists(id); + + // 2. 删除门户 + portalMapper.deleteById(id); + + // 3. 自动删除对应的菜单权限 + deleteMenuPermission(portal.getPermission()); + } + + @Override + public PortalDO getPortal(Long id) { + return portalMapper.selectById(id); + } + + @Override + public PageResult getPortalPage(PortalPageReqVO pageReqVO) { + return portalMapper.selectPage(pageReqVO); + } + + @Override + public List getPortalListByUserId(Long userId) { + // 1. 获取用户的角色ID列表 + Set roleIds = permissionService.getUserRoleIdListByUserIdFromCache(userId); + if (roleIds.isEmpty()) { + return Collections.emptyList(); + } + + // 2. 获取角色对应的菜单ID列表 + Set menuIds = permissionService.getRoleMenuListByRoleId(roleIds); + if (menuIds.isEmpty()) { + return Collections.emptyList(); + } + + // 3. 获取菜单列表并提取权限标识 + List menus = menuService.getMenuList(menuIds); + List permissions = menus.stream() + .map(MenuDO::getPermission) + .filter(permission -> permission != null && !permission.isEmpty()) + .collect(Collectors.toList()); + + if (permissions.isEmpty()) { + return Collections.emptyList(); + } + + // 4. 根据权限标识查询门户列表 + return portalMapper.selectListByPermissions(permissions); + } + + @VisibleForTesting + public PortalDO validatePortalExists(Long id) { + if (id == null) { + return null; + } + PortalDO portal = portalMapper.selectById(id); + if (portal == null) { + throw exception(PORTAL_NOT_EXISTS); + } + return portal; + } + + /** + * 同步菜单权限 + * - 如果菜单权限不存在,则创建 + * - 如果菜单权限已存在,则更新 + * + * @param portal 门户对象 + * @param parentMenuId 父菜单 ID + * @param oldPermission 旧的权限标识(用于更新场景) + */ + private void syncMenuPermission(PortalDO portal, Long parentMenuId, String oldPermission) { + // 1. 如果权限标识发生变化,先删除旧的菜单权限 + if (oldPermission != null && !oldPermission.equals(portal.getPermission())) { + deleteMenuPermission(oldPermission); + } + + // 2. 查询是否已存在该权限的菜单 + List existMenuIds = menuService.getMenuIdListByPermissionFromCache(portal.getPermission()); + + // 3. 构建菜单对象 + MenuSaveVO menuVO = new MenuSaveVO(); + menuVO.setName("访问" + portal.getName()); + menuVO.setPermission(portal.getPermission()); + menuVO.setType(MenuTypeEnum.BUTTON.getType()); + menuVO.setSort(portal.getSort()); + menuVO.setParentId(parentMenuId); + menuVO.setStatus(portal.getStatus()); + menuVO.setVisible(false); // 按钮权限不显示在菜单中 + + // 4. 如果菜单已存在则更新,否则创建 + if (!CollectionUtils.isEmpty(existMenuIds)) { + menuVO.setId(existMenuIds.get(0)); + menuService.updateMenu(menuVO); + } else { + menuService.createMenu(menuVO); + } + } + + /** + * 删除菜单权限 + * + * @param permission 权限标识 + */ + private void deleteMenuPermission(String permission) { + if (permission == null || permission.isEmpty()) { + return; + } + + List menuIds = menuService.getMenuIdListByPermissionFromCache(permission); + if (!CollectionUtils.isEmpty(menuIds)) { + menuIds.forEach(menuService::deleteMenu); + } + } + +}