diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/ClarityFetchStatus.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/ClarityFetchStatus.java new file mode 100644 index 000000000..b3680efe4 --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/ClarityFetchStatus.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.api.constant; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Clarity 数据拉取状态枚举 + * + * @author lengleng + * @date 2026-03-31 + */ +@Getter +@RequiredArgsConstructor +public enum ClarityFetchStatus { + + /** + * 占位中:已插入占位行,API 调用尚未完成 + */ + PENDING("pending", "占位中"), + + /** + * 成功:API 调用成功,数据已写入 + */ + SUCCESS("success", "成功"), + + /** + * 失败:API 调用失败,下次调度可重试 + */ + FAILED("failed", "失败"); + + @EnumValue + @JsonValue + private final String value; + + private final String desc; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/SystemConfigTypeEnum.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/SystemConfigTypeEnum.java index 07935e6b1..b7ce04254 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/SystemConfigTypeEnum.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/SystemConfigTypeEnum.java @@ -48,6 +48,6 @@ public enum SystemConfigTypeEnum { WETALK("wetalk"); @Getter - private final String value; + private final String value; } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java index 9379612df..029666fc8 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/constant/UpmsErrorCodes.java @@ -59,11 +59,21 @@ public interface UpmsErrorCodes { String SYS_MENU_BUTTON_CANNOT_HOME = "sys.menu.button.cannot.home"; + String SYS_MENU_SORT_DUPLICATE = "sys.menu.sort.duplicate"; + + String SYS_MENU_SORT_SCOPE_INVALID = "sys.menu.sort.scopeInvalid"; + // === 字典相关 === String SYS_DICT_DELETE_SYSTEM = "sys.dict.delete.system"; String SYS_DICT_UPDATE_SYSTEM = "sys.dict.update.system"; + String SYS_DICT_NOT_FOUND = "sys.dict.notFound"; + + String SYS_DICT_ITEM_SORT_DUPLICATE = "sys.dict.item.sort.duplicate"; + + String SYS_DICT_ITEM_SORT_SCOPE_INVALID = "sys.dict.item.sort.scopeInvalid"; + // === 参数相关 === String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error"; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/AiChatMessageDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/AiChatMessageDTO.java deleted file mode 100644 index d35f8d82d..000000000 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/AiChatMessageDTO.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.pig4cloud.pigx.admin.api.dto; - -import lombok.Data; - -/** - * AI 聊天消息 DTO - * - * @author lengleng - * @date 2025/02/20 - */ -@Data -public class AiChatMessageDTO { - - /** - * 消息 - */ - private String message; - - /** - * 网络搜索 - */ - private boolean webSearch; - -} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/ApiKeyCreateDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/ApiKeyCreateDTO.java new file mode 100644 index 000000000..10c00606e --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/ApiKeyCreateDTO.java @@ -0,0 +1,51 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 创建 API 密钥请求体 + * + * @author lengleng + * @date 2026-03-23 + */ +@Data +@Schema(description = "创建API密钥请求") +public class ApiKeyCreateDTO { + + @Schema(description = "Key名称") + private String name; + + @Schema(description = "IP白名单,多个以逗号分隔") + private String allowedIps; + + @Schema(description = "过期时间") + private LocalDateTime expiresAt; + + @NotBlank(message = "当前密码不能为空") + @Schema(description = "当前登录用户密码(用于二次验证)") + private String password; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageEmailDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageEmailDTO.java index 394386ec9..b7c4f6590 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageEmailDTO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageEmailDTO.java @@ -72,4 +72,14 @@ public class MessageEmailDTO { */ private String bizCode; + /** + * 图形验证码类型 + */ + private String captchaType; + + /** + * 图形验证码凭据 + */ + private String captchaVerification; + } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageHookDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageHookDTO.java index 850a60593..cf8ee805d 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageHookDTO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageHookDTO.java @@ -53,7 +53,7 @@ public class MessageHookDTO { /** * 图片链接 - */ - private String picUrl; + */ + private String picUrl; } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageSmsDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageSmsDTO.java index ad5266d6a..a721f484d 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageSmsDTO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/MessageSmsDTO.java @@ -40,6 +40,20 @@ public class MessageSmsDTO { @Setter private LinkedHashMap params; + /** + * 图形验证码类型 + */ + @Getter + @Setter + private String captchaType; + + /** + * 图形验证码凭据 + */ + @Getter + @Setter + private String captchaVerification; + /** * ext 参数 */ @@ -57,8 +71,8 @@ public class MessageSmsDTO { // 静态方法用于创建Builder实例 public static Builder builder() { - return new Builder(); - } + return new Builder(); + } // Builder类 public static class Builder { diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysDictItemSortDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysDictItemSortDTO.java new file mode 100644 index 000000000..68151720c --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysDictItemSortDTO.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ +package com.pig4cloud.pigx.admin.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 字典项排序DTO + * + * @author lengleng + * @date 2026/5/21 + */ +@Data +@Schema(description = "字典项排序") +public class SysDictItemSortDTO { + + /** + * 字典ID + */ + @NotNull(message = "字典ID不能为空") + @Schema(description = "字典ID") + private Long dictId; + + /** + * 当前页字典项ID列表,按目标顺序排列 + */ + @NotEmpty(message = "字典项ID列表不能为空") + @Schema(description = "字典项ID列表") + private List itemIds; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysMenuSortDTO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysMenuSortDTO.java new file mode 100644 index 000000000..e4c797de0 --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/dto/SysMenuSortDTO.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ +package com.pig4cloud.pigx.admin.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * 菜单排序DTO + * + * @author lengleng + * @date 2026/5/21 + */ +@Data +@Schema(description = "菜单排序") +public class SysMenuSortDTO { + + /** + * 父菜单ID + */ + @NotNull(message = "菜单父ID不能为空") + @Schema(description = "父菜单ID") + private Long parentId; + + /** + * 同级菜单ID列表,按目标顺序排列 + */ + @NotEmpty(message = "菜单ID列表不能为空") + @Schema(description = "菜单ID列表") + private List menuIds; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysApiKey.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysApiKey.java new file mode 100644 index 000000000..4b28f9a0b --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysApiKey.java @@ -0,0 +1,94 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.api.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.pig4cloud.pigx.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * API密钥管理 + * + * @author lengleng + * @date 2026-03-23 + */ +@Data +@TenantTable +@Schema(description = "API密钥") +@EqualsAndHashCode(callSuper = true) +public class SysApiKey extends Model { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "所属用户ID") + private Long userId; + + @Schema(description = "所属用户名") + private String username; + + @Schema(description = "Key名称") + private String name; + + @Schema(description = "SHA-256哈希值") + private String apiKeyHash; + + @Schema(description = "IP白名单") + private String allowedIps; + + @Schema(description = "过期时间") + private LocalDateTime expiresAt; + + @Schema(description = "状态:0-正常,1-禁用") + private String status; + + @Schema(description = "最后使用时间") + private LocalDateTime lastUsedAt; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记,1:已删除,0:正常") + private String delFlag; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建者") + private String createBy; + + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "更新者") + private String updateBy; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysClarityData.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysClarityData.java new file mode 100644 index 000000000..a9de6fb21 --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysClarityData.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.api.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.pig4cloud.pigx.admin.api.constant.ClarityFetchStatus; +import com.pig4cloud.pigx.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * Clarity 监控数据缓存 + * + * @author lengleng + * @date 2026-03-26 + */ +@Data +@TenantTable +@Schema(description = "Clarity 监控数据缓存") +@EqualsAndHashCode(callSuper = true) +public class SysClarityData extends Model { + + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "数据日期") + private LocalDate dataDate; + + @Schema(description = "总会话数(Traffic.totalSessionCount)") + private Integer totalSessions; + + @Schema(description = "独立访客数 UV(Traffic.distinctUserCount)") + private Integer distinctUsers; + + @Schema(description = "每会话页面数(Traffic.pagesPerSessionPercentage)") + private BigDecimal pagesPerSession; + + @Schema(description = "平均滚动深度 %(ScrollDepth.averageScrollDepth)") + private BigDecimal scrollDepth; + + @Schema(description = "死点击率 %(DeadClickCount.sessionsWithMetricPercentage)") + private BigDecimal deadClickRate; + + @Schema(description = "激怒点击率 %(RageClickCount.sessionsWithMetricPercentage)") + private BigDecimal rageClickRate; + + @Schema(description = "设备分布 JSON 文本,格式:[{name,value}]") + private String deviceData; + + @Schema(description = "热门页面 Top10 JSON 文本,格式:[{name,value}]") + private String topUrls; + + @Schema(description = "来源页面 Top5 JSON,格式:[{name,value}]") + private String referrerUrlData; + + @Schema(description = "页面标题 Top5 JSON,格式:[{name,value}]") + private String pageTitleData; + + @Schema(description = "浏览器分布 JSON,格式:[{name,value}]") + private String browserData; + + @Schema(description = "Clarity API numOfDays 参数:1=24h, 2=48h, 3=72h") + private Integer numOfDays; + + @Schema(description = "数据拉取状态:pending=占位中, success=成功, failed=失败") + private ClarityFetchStatus fetchStatus; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "修改人") + private String updateBy; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记,1:已删除,0:正常") + private String delFlag; + + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "更新时间") + private LocalDateTime updateTime; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysFile.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysFile.java index 46f6d792c..172e43d0d 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysFile.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -132,7 +132,7 @@ public class SysFile extends Model { */ @TableLogic @TableField(fill = FieldFill.INSERT) - @Schema(description = "删除标记,1:已删除,0:正常") - private String delFlag; + @Schema(description = "删除标记,1:已删除,0:正常") + private String delFlag; } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysLog.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysLog.java index 85ec81328..4a229a0ea 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysLog.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysLog.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -19,8 +19,8 @@ package com.pig4cloud.pigx.admin.api.entity; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.*; import com.pig4cloud.pigx.common.core.util.TenantTable; import com.pig4cloud.pigx.common.sensitive.annotation.Sensitive; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRoleWidget.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRoleWidget.java new file mode 100644 index 000000000..1fcc1dc48 --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRoleWidget.java @@ -0,0 +1,83 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.api.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.pig4cloud.pigx.common.core.util.TenantTable; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 角色首页组件配置 + * + * @author lengleng + */ +@Data +@TenantTable +@Schema(description = "角色首页组件配置") +@EqualsAndHashCode(callSuper = true) +public class SysRoleWidget extends Model { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.ASSIGN_ID) + @Schema(description = "主键") + private Long id; + + @Schema(description = "角色ID") + private Long roleId; + + @Schema(description = "允许的组件key列表,逗号分隔") + private String widgetKeys; + + @Schema(description = "默认布局JSON,格式同前端grid对象") + private String layoutConfig; + + /** 创建人 */ + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建人") + private String createBy; + + /** 修改人 */ + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "修改人") + private String updateBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** 修改时间 */ + @TableField(fill = FieldFill.UPDATE) + @Schema(description = "修改时间") + private LocalDateTime updateTime; + + /** 删除标识(0-正常,1-删除) */ + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "删除标记,1:已删除,0:正常") + private String delFlag; + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRouteConf.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRouteConf.java index 11f6e9ad4..0ee0d6b69 100755 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRouteConf.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysRouteConf.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -117,6 +117,6 @@ public class SysRouteConf extends Model { @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") - private String delFlag; + private String delFlag; } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysScheduleEntity.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysScheduleEntity.java index 9f2a8cae8..2c3cd38bf 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysScheduleEntity.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysScheduleEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -120,7 +120,7 @@ public class SysScheduleEntity extends Model { */ @TableLogic @Schema(description = "删除标记") - @TableField(fill = FieldFill.INSERT) - private String delFlag; + @TableField(fill = FieldFill.INSERT) + private String delFlag; } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysTenant.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysTenant.java index f1385be5b..de8f389c0 100755 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysTenant.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/entity/SysTenant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,7 +17,7 @@ package com.pig4cloud.pigx.admin.api.entity; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteApiKeyService.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteApiKeyService.java new file mode 100644 index 000000000..aae6d9f1b --- /dev/null +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteApiKeyService.java @@ -0,0 +1,60 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.api.feign; + +import com.pig4cloud.pigx.admin.api.entity.SysApiKey; +import com.pig4cloud.pigx.common.core.constant.ServiceNameConstants; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.feign.annotation.NoToken; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; + +/** + * API密钥远程调用接口 + * + * @author lengleng + * @date 2026-03-23 + */ +@FeignClient(contextId = "remoteApiKeyService", value = ServiceNameConstants.UPMS_SERVICE) +public interface RemoteApiKeyService { + + /** + * 根据 SHA-256 哈希值查询 API Key(仅限内部调用) + * + * @param hash API Key 的 SHA-256 哈希值 + * @return 对应的 SysApiKey 记录,不存在时 data 为 null + */ + @NoToken + @GetMapping("/api-key/inner/hash/{hash}") + R getByHash(@PathVariable String hash); + + /** + * 更新 API Key 的最后使用时间(仅限内部调用) + * + * @param id API Key 主键 + * @return 更新是否成功 + */ + @NoToken + @PutMapping("/api-key/inner/last-used/{id}") + R updateLastUsed(@PathVariable Long id); + +} diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDataScopeService.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDataScopeService.java index 3bf96d560..a0ea18338 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDataScopeService.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDataScopeService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,22 +38,20 @@ import java.util.List; @FeignClient(contextId = "remoteDataScopeService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteDataScopeService { - /** - * 通过角色ID 查询角色列表 - * - * @param roleIdList 角色ID - * @return - */ - @PostMapping("/role/getRoleList") - R> getRoleList(@RequestBody List roleIdList); + /** + * 通过角色ID 查询角色列表 + * @param roleIdList 角色ID + * @return + */ + @PostMapping("/role/getRoleList") + R> getRoleList(@RequestBody List roleIdList); - /** - * 获取子级部门 - * - * @param deptId 部门ID - * @return - */ - @GetMapping("/dept/getDescendantList/{deptId}") - R> getDescendantList(@PathVariable("deptId") Long deptId); + /** + * 获取子级部门 + * @param deptId 部门ID + * @return + */ + @GetMapping("/dept/getDescendantList/{deptId}") + R> getDescendantList(@PathVariable("deptId") Long deptId); } diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDeptService.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDeptService.java index 92ce52da3..05308af10 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDeptService.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/feign/RemoteDeptService.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -22,7 +22,6 @@ package com.pig4cloud.pigx.admin.api.feign; import com.pig4cloud.pigx.admin.api.entity.SysDept; import com.pig4cloud.pigx.common.core.constant.ServiceNameConstants; import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -50,7 +49,6 @@ public interface RemoteDeptService { * @param deptId 部门ID * @return 负责人ID列表 */ - @NoToken @GetMapping("/dept/leader/{deptId}") R> getAllDeptLeader(@PathVariable("deptId") Long deptId); diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/CpUserExcelVo.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/CpUserExcelVo.java index cd5192a49..0374cec1d 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/CpUserExcelVo.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/CpUserExcelVo.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DeptExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DeptExcelVO.java index abdd4c752..84e8d21bf 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DeptExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DeptExcelVO.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingTalkDeptExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingTalkDeptExcelVO.java index d8b4facae..1f0c4a5f6 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingTalkDeptExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingTalkDeptExcelVO.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingUserExcelVo.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingUserExcelVo.java index b0f205768..b02ecdfbb 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingUserExcelVo.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/DingUserExcelVo.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/PostExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/PostExcelVO.java index d4d7a5f08..67238b62c 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/PostExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/PostExcelVO.java @@ -1,8 +1,8 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; -import cn.idev.excel.annotation.write.style.ColumnWidth; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.write.style.ColumnWidth; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/RoleExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/RoleExcelVO.java index f59b157eb..51f060fba 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/RoleExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/RoleExcelVO.java @@ -1,8 +1,8 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; -import cn.idev.excel.annotation.write.style.ColumnWidth; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.write.style.ColumnWidth; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/UserExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/UserExcelVO.java index 6c6daea9c..b89c251a5 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/UserExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/UserExcelVO.java @@ -1,8 +1,8 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; -import cn.idev.excel.annotation.write.style.ColumnWidth; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.write.style.ColumnWidth; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatDeptExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatDeptExcelVO.java index bfe21e4fc..02c582d1c 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatDeptExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatDeptExcelVO.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatUserExcelVO.java b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatUserExcelVO.java index 40e153b85..2548698a8 100644 --- a/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatUserExcelVO.java +++ b/pigx-upms/pigx-upms-api/src/main/java/com/pig4cloud/pigx/admin/api/vo/WeChatUserExcelVO.java @@ -1,7 +1,7 @@ package com.pig4cloud.pigx.admin.api.vo; -import cn.idev.excel.annotation.ExcelIgnore; -import cn.idev.excel.annotation.ExcelProperty; +import org.apache.fesod.sheet.annotation.ExcelIgnore; +import org.apache.fesod.sheet.annotation.ExcelProperty; import com.pig4cloud.pigx.common.excel.annotation.ExcelLine; import lombok.Data; diff --git a/pigx-upms/pigx-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports b/pigx-upms/pigx-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports index 7a1673f03..68e8a53e2 100644 --- a/pigx-upms/pigx-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports +++ b/pigx-upms/pigx-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports @@ -13,3 +13,4 @@ com.pig4cloud.pigx.admin.api.feign.RemoteAuditLogService com.pig4cloud.pigx.admin.api.feign.RemoteMenuService com.pig4cloud.pigx.admin.api.feign.RemoteRouteConfService com.pig4cloud.pigx.admin.api.feign.RemoteMessageService +com.pig4cloud.pigx.admin.api.feign.RemoteApiKeyService diff --git a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties index b63c0595f..7b6c7de5c 100644 --- a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties +++ b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_en.properties @@ -32,10 +32,15 @@ sys.menu.notFound=Menu does not exist sys.menu.home.set.succeed=Homepage set successfully sys.menu.home.cancel.succeed=Homepage removed successfully sys.menu.button.cannot.home=Button-type menu cannot be set as homepage +sys.menu.sort.duplicate=Menu ID list cannot contain duplicates +sys.menu.sort.scopeInvalid=Menu sort data does not belong to the same parent node # Dictionary related sys.dict.delete.system=Built-in dictionary items cannot be deleted sys.dict.update.system=Built-in dictionary items cannot be modified +sys.dict.notFound=Dictionary does not exist +sys.dict.item.sort.duplicate=Dictionary item ID list cannot contain duplicates +sys.dict.item.sort.scopeInvalid=Dictionary item sort data does not belong to the current dictionary # Parameter related sys.param.config.error=System parameter configuration error: {0} diff --git a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties index adf8cd6f6..0aa994e22 100644 --- a/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties +++ b/pigx-upms/pigx-upms-api/src/main/resources/i18n/messages_zh_CN.properties @@ -32,10 +32,15 @@ sys.menu.notFound=\u83DC\u5355\u4E0D\u5B58\u5728 sys.menu.home.set.succeed=\u8BBE\u7F6E\u9996\u9875\u6210\u529F sys.menu.home.cancel.succeed=\u53D6\u6D88\u9996\u9875\u6210\u529F sys.menu.button.cannot.home=\u6309\u94AE\u7C7B\u578B\u4E0D\u80FD\u8BBE\u7F6E\u4E3A\u9996\u9875 +sys.menu.sort.duplicate=\u83DC\u5355ID\u5217\u8868\u4E0D\u80FD\u91CD\u590D +sys.menu.sort.scopeInvalid=\u83DC\u5355\u6392\u5E8F\u6570\u636E\u4E0D\u5C5E\u4E8E\u540C\u4E00\u7236\u8282\u70B9 # 字典相关 sys.dict.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u5220\u9664 sys.dict.update.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u4FEE\u6539 +sys.dict.notFound=\u5B57\u5178\u4E0D\u5B58\u5728 +sys.dict.item.sort.duplicate=\u5B57\u5178\u9879ID\u5217\u8868\u4E0D\u80FD\u91CD\u590D +sys.dict.item.sort.scopeInvalid=\u5B57\u5178\u9879\u6392\u5E8F\u6570\u636E\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5B57\u5178 # 参数相关 sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/PigxAdminApplication.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/PigxAdminApplication.java index 503448481..18b0b478c 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/PigxAdminApplication.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/PigxAdminApplication.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -37,8 +37,8 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication public class PigxAdminApplication { - public static void main(String[] args) { - SpringApplication.run(PigxAdminApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(PigxAdminApplication.class, args); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/ClientDetailsInitRunner.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/ClientDetailsInitRunner.java index f6060d14f..c6cbee01b 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/ClientDetailsInitRunner.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/ClientDetailsInitRunner.java @@ -9,7 +9,7 @@ import com.pig4cloud.pigx.common.data.tenant.TenantBroker; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; @@ -38,11 +38,11 @@ public class ClientDetailsInitRunner implements InitializingBean { private final SysTenantService tenantService; /** - * WebServerInitializedEvent 使用 TransactionalEventListener 时启动时无法获取到事件 + * ApplicationReadyEvent 使用 TransactionalEventListener 时启动时无法获取到事件 */ @Async @Order - @EventListener({ WebServerInitializedEvent.class }) + @EventListener({ApplicationReadyEvent.class}) public void webServerInit() { this.initClientDetails(); } @@ -63,7 +63,7 @@ public class ClientDetailsInitRunner implements InitializingBean { // 3. 拼接key 1:client_config_flag:clinetId String key = String.format("%s:%s:%s", tenantId, CacheConstants.CLIENT_FLAG, client.getClientId()); // 4. hashkey clientId 保存客户端信息 - RedisUtils.set(key, client.getAdditionalInformation()); + RedisUtils.set(key, client.getAdditionalInformation()); }); }); }); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java index d834bc53b..af83bdce6 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/config/DynamicRouteInitRunner.java @@ -26,7 +26,7 @@ import com.pig4cloud.pigx.common.gateway.vo.RouteDefinitionVo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; -import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.cloud.client.ConditionalOnDiscoveryEnabled; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; @@ -37,7 +37,6 @@ import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.scheduling.annotation.Async; import org.springframework.transaction.event.TransactionalEventListener; @@ -54,6 +53,7 @@ import java.util.Map; @Configuration @RequiredArgsConstructor @ConditionalOnDiscoveryEnabled +@SuppressWarnings("removal") public class DynamicRouteInitRunner implements InitializingBean { private final RedisTemplate redisTemplate; @@ -63,11 +63,11 @@ public class DynamicRouteInitRunner implements InitializingBean { private final RedisMessageListenerContainer listenerContainer; /** - * WebServerInitializedEvent 使用 TransactionalEventListener 时启动时无法获取到事件 + * ApplicationReadyEvent 使用 TransactionalEventListener 时启动时无法获取到事件 */ @Async @Order - @EventListener({WebServerInitializedEvent.class}) + @EventListener({ApplicationReadyEvent.class}) public void WebServerInit() { this.initRoute(); } @@ -93,7 +93,8 @@ public class DynamicRouteInitRunner implements InitializingBean { vo.setMetadata(JSONUtil.toBean(route.getMetadata(), Map.class)); log.info("加载路由ID:{},{}", route.getRouteId(), vo); - redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); + redisTemplate.setHashValueSerializer( + new org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class)); HashOperations stringStringValueOperations = redisTemplate.opsForHash(); stringStringValueOperations.put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo); }); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/AiChatController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/AiChatController.java deleted file mode 100644 index 4124e2b23..000000000 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/AiChatController.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.pig4cloud.pigx.admin.controller; - -import com.pig4cloud.pigx.admin.api.dto.AiChatMessageDTO; -import io.github.pigmesh.ai.deepseek.core.DeepSeekClient; -import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; - -import java.util.Optional; - -/** - * AI 聊天控制器 - * - * @author lengleng - * @date 2025/02/20 - */ -@Slf4j -@RestController -@RequestMapping("/ai") -@RequiredArgsConstructor -public class AiChatController { - - private final Optional deepSeekClientOptional; - - /** - * 聊天 - * - * @param message 消息 - * @return {@link Flux }<{@link ChatCompletionResponse }> - */ - @PostMapping("/chat") - public Flux chat(@RequestBody AiChatMessageDTO message) { - - if (deepSeekClientOptional.isEmpty()) { - log.warn("DeepSeek 大模型聊天未开启,请检查配置"); - return Flux.empty(); - } - - if (message.isWebSearch()) { - return deepSeekClientOptional.get().chatSearchCompletion(message.getMessage()); - } - return deepSeekClientOptional.get().chatFluxCompletion(message.getMessage()); - } - -} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysApiKeyController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysApiKeyController.java new file mode 100644 index 000000000..ac036caa0 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysApiKeyController.java @@ -0,0 +1,180 @@ + /* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.controller; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.pig4cloud.pigx.admin.api.dto.ApiKeyCreateDTO; +import com.pig4cloud.pigx.admin.api.entity.SysApiKey; +import com.pig4cloud.pigx.admin.service.SysApiKeyService; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.log.annotation.SysLog; +import com.pig4cloud.pigx.common.security.annotation.HasPermission; +import com.pig4cloud.pigx.common.security.annotation.Inner; +import com.pig4cloud.pigx.common.security.util.SecurityUtils; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; + +/** + * API密钥管理 + * + * @author lengleng + * @date 2026-03-23 + */ +@RestController +@AllArgsConstructor +@RequestMapping("/api-key") +@Tag(description = "api-key", name = "API密钥管理") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class SysApiKeyController { + + private final SysApiKeyService apiKeyService; + + /** + * 创建 API Key + *

+ * 业务逻辑由 Service 完成:校验当前密码(防 CSRF)、数量上限(每用户最多 5 个)、 + * 生成 sk- 前缀密钥并只存储 SHA-256 哈希。明文 Key 仅在本次响应中返回,之后不可再取。 + * + * @param dto 创建请求(name、allowedIps、expiresAt、password) + * @return 明文 API Key,格式:sk-xxx + */ + @SysLog("创建API密钥") + @PostMapping + public R create(@Valid @RequestBody ApiKeyCreateDTO dto) { + return apiKeyService.createApiKey(dto); + } + + /** + * 查询当前用户的 API Key 列表 + *

+ * 仅返回当前登录用户自己的 Key,按创建时间倒序排列。 + * 列表中不包含明文 Key 和哈希值,仅展示元数据。 + */ + @GetMapping("/list") + public R list() { + Long userId = SecurityUtils.getUser().getId(); + return R.ok(apiKeyService.list(Wrappers.lambdaQuery() + .eq(SysApiKey::getUserId, userId) + .orderByDesc(SysApiKey::getCreateTime))); + } + + /** + * 管理员分页查询所有用户的 API Key + *

+ * 需要 admin_apikey_view 权限。支持按 username 模糊搜索。 + * + * @param page 分页参数 + * @param sysApiKey 查询条件(username 字段用于模糊匹配) + */ + @GetMapping("/page") + @HasPermission("sys_social_details_view") + public R page(@ParameterObject Page page, @ParameterObject SysApiKey sysApiKey) { + return apiKeyService.page(page, sysApiKey); + } + + /** + * 更新 API Key 元数据 + *

+ * 仅允许修改 name、allowedIps、expiresAt、status,不可修改 apiKeyHash。 + * Service 层校验归属权,并在更新后精确清除对应缓存。 + * + * @param apiKey 待更新字段(必须包含 id) + */ + @SysLog("更新API密钥") + @PutMapping + public R update(@RequestBody SysApiKey apiKey) { + return apiKeyService.updateApiKey(apiKey); + } + + /** + * 删除当前用户自己的 API Key + *

+ * Service 层校验归属权后删除,并精确清除该 Key 的缓存。 + * + * @param id API Key 主键 + */ + @SysLog("删除API密钥") + @DeleteMapping("/{id}") + @HasPermission("sys_social_details_del") + public R delete(@PathVariable Long id) { + return apiKeyService.deleteApiKey(id); + } + + /** + * 管理员批量删除 API Key + *

+ * 需要 admin_apikey_del 权限。Service 层逐条清除缓存后执行批量删除。 + * + * @param ids 待删除的 API Key 主键数组 + */ + @SysLog("批量删除API密钥") + @DeleteMapping + @HasPermission("sys_social_details_del") + public R deleteBatch(@RequestBody Long[] ids) { + return apiKeyService.deleteBatchApiKey(ids); + } + + /** + * 内部接口:根据 SHA-256 哈希值查询有效的 API Key + *

+ * 仅供 pigx-common-security 的 token 校验流程调用,不对外暴露。 + * 结果会被 Spring Cache 缓存(key 为 hash),避免每次请求都查库。 + * + * @param hash API Key 的 SHA-256 哈希值 + * @return 状态正常的 SysApiKey,不存在或已禁用时 data 为 null + */ + @Inner + @GetMapping("/inner/hash/{hash}") + @Cacheable(value = CacheConstants.API_KEY_DETAILS, key = "#hash", unless = "#result.data == null") + public R getByHash(@PathVariable String hash) { + return R.ok(apiKeyService.getOne(Wrappers.lambdaQuery() + .eq(SysApiKey::getApiKeyHash, hash) + .eq(SysApiKey::getStatus, YesNoEnum.NO.getCode()))); + } + + /** + * 内部接口:更新 API Key 最后使用时间 + *

+ * 由 token 校验流程在认证成功后异步调用,记录 Key 的活跃时间。 + * 该操作不影响认证结果,失败时仅打印警告日志。 + * + * @param id API Key 主键 + */ + @Inner + @PutMapping("/inner/last-used/{id}") + public R updateLastUsed(@PathVariable Long id) { + SysApiKey update = new SysApiKey(); + update.setId(id); + update.setLastUsedAt(LocalDateTime.now()); + return R.ok(apiKeyService.updateById(update)); + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAreaController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAreaController.java index 8e9e665e3..285161091 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAreaController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAreaController.java @@ -129,7 +129,7 @@ public class SysAreaController { @HasPermission("sys_sysArea_export") public List export(SysAreaEntity sysArea, Long[] ids) { return sysAreaService.list(Wrappers.lambdaQuery(sysArea) - .in(ArrayUtil.isNotEmpty(ids), SysAreaEntity::getId, CollUtil.toList(ids))); - } + .in(ArrayUtil.isNotEmpty(ids), SysAreaEntity::getId, CollUtil.toList(ids))); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAuditLogController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAuditLogController.java index 4e6919b43..17327af80 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAuditLogController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysAuditLogController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -111,8 +111,8 @@ public class SysAuditLogController { @GetMapping("/export") @HasPermission("sys_audit_export") public List export(SysAuditLog sysAuditLog, Long[] ids) { - return sysAuditLogService.list(Wrappers.lambdaQuery(sysAuditLog) - .in(ArrayUtil.isNotEmpty(ids), SysAuditLog::getId, CollUtil.toList(ids))); + return sysAuditLogService.list(Wrappers.lambdaQuery(sysAuditLog) + .in(ArrayUtil.isNotEmpty(ids), SysAuditLog::getId, CollUtil.toList(ids))); } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDeptController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDeptController.java index 1b0830e9e..af946e889 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDeptController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDeptController.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -27,7 +27,6 @@ import com.pig4cloud.pigx.common.excel.annotation.RequestExcel; import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; import com.pig4cloud.pigx.common.log.annotation.SysLog; import com.pig4cloud.pigx.common.security.annotation.HasPermission; -import com.pig4cloud.pigx.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -130,7 +129,6 @@ public class SysDeptController { return R.ok(sysDeptService.listDescendant(deptId)); } - @Inner @GetMapping(value = "/leader/{deptId}") public R getAllDeptLeader(@PathVariable Long deptId) { return R.ok(sysDeptService.listDeptLeader(deptId)); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDictController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDictController.java index 75db60d2b..999a20635 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDictController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysDictController.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -23,6 +23,7 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.pig4cloud.pigx.admin.api.dto.SysDictItemSortDTO; import com.pig4cloud.pigx.admin.api.entity.SysDict; import com.pig4cloud.pigx.admin.api.entity.SysDictItem; import com.pig4cloud.pigx.admin.service.SysDictItemService; @@ -112,7 +113,8 @@ public class SysDictController { return R.ok(sysDictItemService.list(Wrappers.query() .lambda() .eq(SysDictItem::getDictType, type) - .orderByDesc(SysDictItem::getSortOrder))); + .orderByAsc(SysDictItem::getSortOrder) + .orderByAsc(SysDictItem::getId))); } /** @@ -180,7 +182,9 @@ public class SysDictController { */ @GetMapping("/item/page") public R getSysDictItemPage(Page page, SysDictItem sysDictItem) { - return R.ok(sysDictItemService.page(page, Wrappers.query(sysDictItem))); + return R.ok(sysDictItemService.page(page, Wrappers.lambdaQuery(sysDictItem) + .orderByAsc(SysDictItem::getSortOrder) + .orderByAsc(SysDictItem::getId))); } /** @@ -227,6 +231,18 @@ public class SysDictController { return sysDictItemService.updateDictItem(sysDictItem); } + /** + * 更新字典项排序 + * + * @param sortDTO 字典项排序信息 + * @return R + */ + @SysLog("更新字典项排序") + @PutMapping("/item/sort") + public R sortDictItem(@Valid @RequestBody SysDictItemSortDTO sortDTO) { + return sysDictItemService.updateDictItemSort(sortDTO); + } + /** * 通过id删除字典项 * @param id id @@ -247,12 +263,12 @@ public class SysDictController { @PutMapping("/sync") public R sync() { return sysDictService.syncDictCache(); - } + } - @ResponseExcel - @GetMapping("/export") - public List export(SysDictItem sysDictItem) { - return sysDictItemService.list(Wrappers.query(sysDictItem)); - } + @ResponseExcel + @GetMapping("/export") + public List export(SysDictItem sysDictItem) { + return sysDictItemService.list(Wrappers.query(sysDictItem)); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysFileController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysFileController.java index 126f08858..f60a05031 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysFileController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysFileController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -125,7 +125,7 @@ public class SysFileController { * @param fileName 自定义文件名 * @return 文件上传结果 */ - @Inner + @Inner(value = false) @PostMapping(value = "/inner/upload") public R innerUpload(@RequestPart("file") MultipartFile file, @RequestParam(value = "dir", required = false) String dir, @@ -208,10 +208,10 @@ public class SysFileController { * 移动文件组 * @param fileGroupDTO SysFileGroupDTO对象,要移动的文件组信息 * @return 包含移动结果的R对象 - */ - @PutMapping("/group/move") - public R moveFileGroup(@RequestBody SysFileGroupDTO fileGroupDTO) { - return R.ok(sysFileService.moveFileGroup(fileGroupDTO)); - } + */ + @PutMapping("/group/move") + public R moveFileGroup(@RequestBody SysFileGroupDTO fileGroupDTO) { + return R.ok(sysFileService.moveFileGroup(fileGroupDTO)); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysI18nController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysI18nController.java index 5d1ec1030..0dea5426f 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysI18nController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysI18nController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -30,7 +30,6 @@ import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.excel.annotation.ResponseExcel; import com.pig4cloud.pigx.common.log.annotation.SysLog; import com.pig4cloud.pigx.common.security.annotation.HasPermission; -import com.pig4cloud.pigx.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -55,115 +54,110 @@ import java.util.List; @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysI18nController { - private final SysI18nService sysI18nService; + private final SysI18nService sysI18nService; - /** - * 分页查询 - * @param page 分页对象 - * @param sysI18n 系统表-国际化 - * @return - */ - @Operation(summary = "分页查询", description = "分页查询") - @GetMapping("/page") - @HasPermission("sys_i18n_view") - public R getsysI18nPage(@ParameterObject Page page, @ParameterObject SysI18nEntity sysI18n) { - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.like(StrUtil.isNotBlank(sysI18n.getName()), SysI18nEntity::getName, sysI18n.getName()); - wrapper.like(StrUtil.isNotBlank(sysI18n.getZhCn()), SysI18nEntity::getZhCn, sysI18n.getZhCn()); - wrapper.like(StrUtil.isNotBlank(sysI18n.getEn()), SysI18nEntity::getEn, sysI18n.getEn()); - return R.ok(sysI18nService.page(page, wrapper)); - } + /** + * 分页查询 + * + * @param page 分页对象 + * @param sysI18n 系统表-国际化 + * @return + */ + @Operation(summary = "分页查询", description = "分页查询") + @GetMapping("/page") + @HasPermission("sys_i18n_view") + public R getsysI18nPage(@ParameterObject Page page, @ParameterObject SysI18nEntity sysI18n) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.like(StrUtil.isNotBlank(sysI18n.getName()), SysI18nEntity::getName, sysI18n.getName()); + wrapper.like(StrUtil.isNotBlank(sysI18n.getZhCn()), SysI18nEntity::getZhCn, sysI18n.getZhCn()); + wrapper.like(StrUtil.isNotBlank(sysI18n.getEn()), SysI18nEntity::getEn, sysI18n.getEn()); + return R.ok(sysI18nService.page(page, wrapper)); + } - /** - * 通过id查询系统表-国际化 - * @param id id - * @return R - */ - @Operation(summary = "通过id查询", description = "通过id查询") - @GetMapping("/details/{id}") - public R getById(@PathVariable("id") Long id) { - return R.ok(sysI18nService.getById(id)); - } + /** + * 通过id查询系统表-国际化 + * + * @param id id + * @return R + */ + @Operation(summary = "通过id查询", description = "通过id查询") + @GetMapping("/details/{id}") + public R getById(@PathVariable("id") Long id) { + return R.ok(sysI18nService.getById(id)); + } - @GetMapping("/details") - public R getDetails(@ParameterObject SysI18nEntity entity) { - return R.ok(sysI18nService.getOne(Wrappers.query(entity))); - } + @GetMapping("/details") + public R getDetails(@ParameterObject SysI18nEntity entity) { + return R.ok(sysI18nService.getOne(Wrappers.query(entity))); + } - /** - * 新增系统表-国际化 - * @param sysI18n 系统表-国际化 - * @return R - */ - @Operation(summary = "新增系统表-国际化", description = "新增系统表-国际化") - @SysLog("新增系统表-国际化") - @PostMapping - @CacheEvict(value = CacheConstants.I18N_DETAILS, allEntries = true) - @HasPermission("sys_i18n_add") - public R save(@RequestBody SysI18nEntity sysI18n) { - return R.ok(sysI18nService.save(sysI18n)); - } + /** + * 新增系统表-国际化 + * + * @param sysI18n 系统表-国际化 + * @return R + */ + @Operation(summary = "新增系统表-国际化", description = "新增系统表-国际化") + @SysLog("新增系统表-国际化") + @PostMapping + @HasPermission("sys_i18n_add") + @CacheEvict(value = {CacheConstants.SITE_CONFIG_DETAILS}, allEntries = true) + public R save(@RequestBody SysI18nEntity sysI18n) { + return R.ok(sysI18nService.save(sysI18n)); + } - /** - * 修改系统表-国际化 - * @param sysI18n 系统表-国际化 - * @return R - */ - @Operation(summary = "修改系统表-国际化", description = "修改系统表-国际化") - @SysLog("修改系统表-国际化") - @PutMapping - @CacheEvict(value = CacheConstants.I18N_DETAILS, allEntries = true) - @HasPermission("sys_i18n_edit") - public R updateById(@RequestBody SysI18nEntity sysI18n) { - return R.ok(sysI18nService.updateById(sysI18n)); - } + /** + * 修改系统表-国际化 + * + * @param sysI18n 系统表-国际化 + * @return R + */ + @Operation(summary = "修改系统表-国际化", description = "修改系统表-国际化") + @SysLog("修改系统表-国际化") + @PutMapping + @CacheEvict(value = {CacheConstants.SITE_CONFIG_DETAILS}, allEntries = true) + @HasPermission("sys_i18n_edit") + public R updateById(@RequestBody SysI18nEntity sysI18n) { + return R.ok(sysI18nService.updateById(sysI18n)); + } - /** - * 通过id删除系统表-国际化 - * @param ids id 列表 - * @return R - */ - @Operation(summary = "通过id删除系统表-国际化", description = "通过id删除系统表-国际化") - @SysLog("通过id删除系统表-国际化") - @DeleteMapping - @CacheEvict(value = CacheConstants.I18N_DETAILS, allEntries = true) - @HasPermission("sys_i18n_del") - public R removeById(@RequestBody Long[] ids) { - return R.ok(sysI18nService.removeBatchByIds(CollUtil.toList(ids))); - } + /** + * 通过id删除系统表-国际化 + * + * @param ids id 列表 + * @return R + */ + @Operation(summary = "通过id删除系统表-国际化", description = "通过id删除系统表-国际化") + @SysLog("通过id删除系统表-国际化") + @DeleteMapping + @HasPermission("sys_i18n_del") + @CacheEvict(value = {CacheConstants.SITE_CONFIG_DETAILS}, allEntries = true) + public R removeById(@RequestBody Long[] ids) { + return R.ok(sysI18nService.removeBatchByIds(CollUtil.toList(ids))); + } - /** - * 导出excel 表格 - * @param sysI18n 查询条件 - * @return excel 文件流 - */ - @ResponseExcel - @GetMapping("/export") - @HasPermission("sys_i18n_export") - public List export(SysI18nEntity sysI18n, Long[] ids) { - return sysI18nService.list(Wrappers.lambdaQuery(sysI18n) - .in(ArrayUtil.isNotEmpty(ids), SysI18nEntity::getId, CollUtil.toList(ids))); - } + /** + * 导出excel 表格 + * + * @param sysI18n 查询条件 + * @return excel 文件流 + */ + @ResponseExcel + @GetMapping("/export") + @HasPermission("sys_i18n_export") + public List export(SysI18nEntity sysI18n, Long[] ids) { + return sysI18nService.list(Wrappers.lambdaQuery(sysI18n).in(ArrayUtil.isNotEmpty(ids), SysI18nEntity::getId, CollUtil.toList(ids))); + } - /** - * 获取系统I18N配置 - * @return I18N 配置 - */ - @Operation(summary = "获取系统配置-国际化", description = "获取系统配置-国际化") - @Inner(false) - @GetMapping("/info") - public R list() { - return R.ok(sysI18nService.listMap()); - } - - /** - * 同步数据 - * @return R - */ - @SysLog("同步数据") - @PutMapping("/sync") - public R sync() { - return sysI18nService.syncI18nCache(); - } + /** + * 同步数据 + * + * @return R + */ + @SysLog("同步数据") + @PutMapping("/sync") + public R sync() { + return sysI18nService.syncI18nCache(); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysLogController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysLogController.java index 12ff0d888..2ac7d02ef 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysLogController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysLogController.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -120,8 +120,8 @@ public class SysLogController { @GetMapping("/export") @HasPermission("sys_log_export") public List export(SysLog sysLog, Long[] ids) { - return sysLogService - .list(Wrappers.lambdaQuery(sysLog).in(Objects.nonNull(ids), SysLog::getId, CollUtil.toList(ids))); + return sysLogService + .list(Wrappers.lambdaQuery(sysLog).in(Objects.nonNull(ids), SysLog::getId, CollUtil.toList(ids))); } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMenuController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMenuController.java index 8979d5a47..5c55b2e00 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMenuController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMenuController.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,8 +20,10 @@ package com.pig4cloud.pigx.admin.controller; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.pig4cloud.pigx.admin.api.dto.SysMenuSortDTO; import com.pig4cloud.pigx.admin.api.entity.SysMenu; import com.pig4cloud.pigx.admin.service.SysMenuService; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.log.annotation.SysLog; import com.pig4cloud.pigx.common.security.annotation.HasPermission; @@ -31,6 +33,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; @@ -139,4 +142,34 @@ public class SysMenuController { return R.ok(sysMenuService.updateMenuById(sysMenu)); } + /** + * 更新菜单同级排序 + * + * @param sortDTO 菜单排序信息 + * @return success/false + */ + @SysLog("更新菜单排序") + @PutMapping("/sort") + @HasPermission("sys_menu_edit") + public R sort(@Valid @RequestBody SysMenuSortDTO sortDTO) { + return sysMenuService.updateMenuSort(sortDTO); + } + + /** + * 刷新指定角色的菜单权限缓存 + *

+ * menu_details 缓存 key = roleId,精确清除该角色的菜单缓存, + * 与 SysRoleMenuServiceImpl.saveRoleMenus 的 @CacheEvict(key = "#roleId") 逻辑保持一致。 + * + * @param roleId 角色ID + * @return success/fail + */ + @SysLog("刷新角色菜单缓存") + @DeleteMapping("/cache/{roleId}") + @HasPermission("sys_role_edit") + @CacheEvict(value = CacheConstants.MENU_DETAILS, key = "#roleId") + public R clearMenuCache(@PathVariable Long roleId) { + return R.ok(); + } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMessageController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMessageController.java index 0dcac18ab..552b69ed1 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMessageController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysMessageController.java @@ -185,14 +185,18 @@ public class SysMessageController { /** * 发送短信验证码 * - * @param mobile 手机号 - * @param registered 是否已注册,默认true + * @param mobile 手机号 + * @param registered 是否已注册,默认true + * @param captchaType 图形验证码类型 + * @param captchaVerification 图形验证码凭据 * @return 发送结果 */ @Inner(value = false) @GetMapping("/send/smsCode") - public R sendSmsCode(@RequestParam String mobile, @RequestParam(required = false, defaultValue = "true") boolean registered) { - return sysMessageService.sendSmsCode(mobile, registered); + public R sendSmsCode(@RequestParam String mobile, @RequestParam(required = false, defaultValue = "true") boolean registered, + @RequestParam(required = false) String captchaType, + @RequestParam(required = false) String captchaVerification) { + return sysMessageService.sendSmsCode(mobile, registered, captchaType, captchaVerification); } /** diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysPublicParamController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysPublicParamController.java index 2a1a69172..419c0337a 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysPublicParamController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysPublicParamController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -144,7 +144,7 @@ public class SysPublicParamController { /** * 通过id删除公共参数 - * @param publicId id + * @param ids id * @return R */ @Operation(description = "删除公共参数", summary = "删除公共参数") diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRegisterController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRegisterController.java index c892fe429..c9e8103a9 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRegisterController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRegisterController.java @@ -2,7 +2,9 @@ package com.pig4cloud.pigx.admin.controller; import com.pig4cloud.pigx.admin.api.dto.RegisterUserDTO; import com.pig4cloud.pigx.admin.service.SysUserService; +import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum; import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.data.resolver.ParamResolver; import com.pig4cloud.pigx.common.log.annotation.SysLog; import com.pig4cloud.pigx.common.security.annotation.Inner; import lombok.RequiredArgsConstructor; @@ -33,6 +35,9 @@ public class SysRegisterController { @PostMapping("/user") @ConditionalOnProperty(name = "register.user", matchIfMissing = true) public R registerUser(@RequestBody RegisterUserDTO userDto) { + if (!YesNoEnum.YES.getCode().equals(ParamResolver.getStr("SITE_REGISTER_ENABLE", YesNoEnum.NO.getCode()))) { + return R.failed("系统已关闭注册功能"); + } return userService.registerUser(userDto); } @@ -59,6 +64,6 @@ public class SysRegisterController { @PostMapping("/forget/{code}") public R forgetUserPassword(@RequestBody RegisterUserDTO userDto, @PathVariable String code) { return userService.forgetUserPassword(userDto, code); - } + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRoleController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRoleController.java index 85dd4d041..e6f1f5e06 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRoleController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRoleController.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -24,9 +24,11 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pigx.admin.api.entity.SysRole; +import com.pig4cloud.pigx.admin.api.entity.SysRoleWidget; import com.pig4cloud.pigx.admin.api.vo.RoleExcelVO; import com.pig4cloud.pigx.admin.api.vo.RoleMenuVO; import com.pig4cloud.pigx.admin.service.SysRoleService; +import com.pig4cloud.pigx.admin.service.SysRoleWidgetService; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.excel.annotation.RequestExcel; @@ -58,6 +60,8 @@ public class SysRoleController { private final SysRoleService sysRoleService; + private final SysRoleWidgetService sysRoleWidgetService; + /** * 通过ID查询角色信息 * @param id ID @@ -186,4 +190,36 @@ public class SysRoleController { return sysRoleService.importRole(excelVOList, bindingResult); } + /** + * 查询当前用户角色的首页widget配置(取 widgetKeys 最多的那条) + * @return widget配置,无配置时返回 null + */ + @GetMapping("/widget/current") + public R getCurrentUserWidget() { + return R.ok(sysRoleWidgetService.getByCurrentUser()); + } + + /** + * 查询角色的首页widget配置 + * @param roleId 角色ID + * @return widget配置,无配置时返回 null + */ + @GetMapping("/widget/{roleId}") + @HasPermission("sys_role_view") + public R getRoleWidget(@PathVariable Long roleId) { + return R.ok(sysRoleWidgetService.getByRoleId(roleId)); + } + + /** + * 保存角色的首页widget配置 + * @param sysRoleWidget widget配置对象 + * @return success/fail + */ + @SysLog("保存角色首页配置") + @PutMapping("/widget") + @HasPermission("sys_role_edit") + public R saveRoleWidget(@RequestBody SysRoleWidget sysRoleWidget) { + return R.ok(sysRoleWidgetService.saveOrUpdateByRoleId(sysRoleWidget)); + } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRouteConfController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRouteConfController.java index 7d91afca7..eb899f3a4 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRouteConfController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysRouteConfController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -82,6 +82,6 @@ public class SysRouteConfController { @HasPermission("sys_route_manage") public R saveRoute(@RequestBody SysRouteConf routeConf) { return R.ok(sysRouteConfService.saveRoute(routeConf)); - } + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSensitiveWordController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSensitiveWordController.java index fb987ce9e..642318545 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSensitiveWordController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSensitiveWordController.java @@ -175,10 +175,10 @@ public class SysSensitiveWordController { @HasPermission("admin_sysSensitiveWord_del") public R refresh() { RedisUtils.execute((RedisCallback) connection -> { - connection.publish(CacheConstants.SENSITIVE_REDIS_RELOAD_TOPIC.getBytes(), "刷新敏感词缓存".getBytes()); - return null; - }); - return R.ok(); - } + connection.publish(CacheConstants.SENSITIVE_REDIS_RELOAD_TOPIC.getBytes(), "刷新敏感词缓存".getBytes()); + return null; + }); + return R.ok(); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSocialDetailsController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSocialDetailsController.java index 76f50c45b..6948e7cdf 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSocialDetailsController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSocialDetailsController.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -72,7 +72,7 @@ public class SysSocialDetailsController { */ @Inner(value = false) @GetMapping("/getLoginAppList") - public R getLoginAppList() { + public R getLoginAppList() { return R.ok(sysSocialDetailsService.selectList()); } @@ -156,8 +156,8 @@ public class SysSocialDetailsController { */ @GetMapping("/export") public List export(SysSocialDetails sysSocialDetails, Long[] ids) { - return sysSocialDetailsService.list(Wrappers.lambdaQuery(sysSocialDetails) - .in(ArrayUtil.isNotEmpty(ids), SysSocialDetails::getId, CollUtil.toList(ids))); + return sysSocialDetailsService.list(Wrappers.lambdaQuery(sysSocialDetails) + .in(ArrayUtil.isNotEmpty(ids), SysSocialDetails::getId, CollUtil.toList(ids))); } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemConfigController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemConfigController.java index c0e593be7..02f71c0f3 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemConfigController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemConfigController.java @@ -117,7 +117,7 @@ public class SysSystemConfigController { @HasPermission("sys_message_export") public List export(SysSystemConfigEntity sysSystemConfig, Long[] ids) { return sysSystemConfigService.list(Wrappers.lambdaQuery(sysSystemConfig) - .in(ArrayUtil.isNotEmpty(ids), SysSystemConfigEntity::getId, CollUtil.toList(ids))); - } + .in(ArrayUtil.isNotEmpty(ids), SysSystemConfigEntity::getId, CollUtil.toList(ids))); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemInfoController.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemInfoController.java index 080593810..75c7183f4 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemInfoController.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/controller/SysSystemInfoController.java @@ -1,21 +1,18 @@ package com.pig4cloud.pigx.admin.controller; -import com.pig4cloud.pigx.admin.api.constant.UpmsErrorCodes; -import com.pig4cloud.pigx.common.core.util.MsgUtils; +import com.pig4cloud.pigx.admin.dto.SiteConfigDTO; +import com.pig4cloud.pigx.admin.service.SysSiteConfigService; +import com.pig4cloud.pigx.admin.service.SysSystemInfoService; import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.data.cache.RedisUtils; +import com.pig4cloud.pigx.common.security.annotation.HasPermission; +import com.pig4cloud.pigx.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.data.redis.connection.RedisServerCommands; -import org.springframework.data.redis.core.RedisCallback; import org.springframework.http.HttpHeaders; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import java.util.*; +import java.util.Map; @RestController @RequestMapping("/system") @@ -24,37 +21,68 @@ import java.util.*; @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysSystemInfoController { + private final SysSystemInfoService sysSystemInfoService; + + private final SysSiteConfigService sysSiteConfigService; + /** * 缓存监控 * * @return R */ + @HasPermission("sys_cache_view") @GetMapping("/cache") - public R cache() { - Properties info = RedisUtils.execute((RedisCallback) RedisServerCommands::info); - Properties commandStats = RedisUtils - .execute((RedisCallback) connection -> connection.serverCommands().info("commandstats")); - Object dbSize = RedisUtils.execute((RedisCallback) RedisServerCommands::dbSize); - - if (commandStats == null) { - return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_SYSTEM_CACHE_FETCH_ERROR)); - } - - Map result = new HashMap<>(3); - result.put("info", info); - result.put("dbSize", dbSize); - - List> pieList = new ArrayList<>(); - commandStats.stringPropertyNames().forEach(key -> { - Map data = new HashMap<>(2); - String property = commandStats.getProperty(key); - data.put("name", StringUtils.removeStart(key, "cmdstat_")); - data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); - pieList.add(data); - }); - - result.put("commandStats", pieList); - return R.ok(result); + public R> cache() { + return sysSystemInfoService.getCacheInfo(); } + /** + * Clarity 站点监控数据 + * + * @return R + */ + @HasPermission("sys_clarity_view") + @GetMapping("/clarity") + public R clarity(@RequestParam(defaultValue = "1") Integer numOfDays) { + return sysSystemInfoService.getClarityData(numOfDays); + } + + /** + * 聚合配置(i18n + site 配置)- 公开接口 + */ + @Inner(false) + @GetMapping("/config") + public R> config() { + return R.ok(sysSiteConfigService.getAggregatedConfig()); + } + + /** + * 获取网站配置(管理端用) + */ + @HasPermission("sys_site_config_view") + @GetMapping("/site-config") + public R getSiteConfig() { + return R.ok(sysSiteConfigService.getSiteConfig()); + } + + /** + * 更新网站配置 + */ + @HasPermission("sys_site_config_edit") + @PutMapping("/site-config") + public R updateSiteConfig(@RequestBody SiteConfigDTO dto) { + sysSiteConfigService.updateSiteConfig(dto); + return R.ok(); + } + + /** + * 刷新网站配置缓存(清空 Redis) + */ + @HasPermission("sys_site_config_edit") + @DeleteMapping("/site-config/refresh") + public R refreshSiteConfig() { + sysSiteConfigService.refreshCache(); + return R.ok(); + } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/dto/SiteConfigDTO.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/dto/SiteConfigDTO.java new file mode 100644 index 000000000..1043566ed --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/dto/SiteConfigDTO.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 站点配置DTO + * + * @author lengleng + * @date 2026-03-27 + */ +@Data +@Schema(description = "站点配置") +public class SiteConfigDTO { + + @Schema(description = "Clarity 统计ID") + private String clarityId; + + @Schema(description = "验证码类型:clickWord=点按,blockPuzzle=滑动,math=图形,none=关闭") + private String captchaType; + + @Schema(description = "密码规则预设:letterNumber=英文+数字,letterNumberSymbol=英文+数字+符号,upperLowerNumberSymbol=大小写+数字+符号") + private String passwordRule; + + @Schema(description = "是否强制重置密码") + private Boolean forceResetPwd; + + @Schema(description = "是否强制下线") + private Boolean forceLogout; + + @Schema(description = "是否开启短信登录") + private Boolean smsLoginEnable; + + @Schema(description = "是否开启社交登录") + private Boolean socialLoginEnable; + + @Schema(description = "是否开启注册") + private Boolean registerEnable; + + @Schema(description = "是否开启找回/重置密码") + private Boolean resetPassword; + + @Schema(description = "是否开启国际化") + private Boolean i18nEnable; + + @Schema(description = "是否开启暗黑模式") + private Boolean darkModeEnable; + + @Schema(description = "是否开启反调试保护") + private Boolean antiDebugEnable; + + @Schema(description = "反调试绕过密钥") + private String antiDebugKey; + + @Schema(description = "是否开启钉钉导入") + private Boolean syncDingtalkEnabled; + + @Schema(description = "是否开启企业微信导入") + private Boolean syncWechatEnabled; + + @Schema(description = "站点标题") + private String title; + + @Schema(description = "页脚信息") + private String footer; + + @Schema(description = "隐私提示") + private String privacyTip; + + @Schema(description = "网站 Logo 图片路径") + private String logo; + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/ClarityFetchTaskHandler.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/ClarityFetchTaskHandler.java new file mode 100644 index 000000000..5955d1040 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/ClarityFetchTaskHandler.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.handler; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.pig4cloud.pigx.admin.api.constant.ClarityFetchStatus; +import com.pig4cloud.pigx.admin.api.entity.SysClarityData; +import com.pig4cloud.pigx.admin.api.entity.SysSocialDetails; +import com.pig4cloud.pigx.admin.mapper.SysClarityDataMapper; +import com.pig4cloud.pigx.admin.mapper.SysSocialDetailsMapper; +import com.pig4cloud.pigx.common.data.tenant.TenantBroker; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Clarity API 异步数据拉取任务 + * + * @author lengleng + * @date 2026-03-26 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ClarityFetchTaskHandler { + + private static final String CLARITY_API_BASE = "https://www.clarity.ms/export-data/api/v1/project-live-insights?numOfDays="; + + private static final String CLARITY_TYPE = "clarity"; + + private final SysClarityDataMapper clarityDataMapper; + + private final SysSocialDetailsMapper socialDetailsMapper; + + /** + * 异步从 Clarity API 拉取数据并存库 + * 策略:先插入占位行(PENDING),调用成功后更新数据(SUCCESS),失败则标记(FAILED)。 + * 并发时第二个请求查到 PENDING/SUCCESS 即跳过,彻底避免重复调用。 + * 使用 TenantBroker.runAs 恢复调用线程的租户上下文(@Async 新线程不继承 ThreadLocal) + * + * @param tenantId 调用方当前租户 ID + * @param numOfDays Clarity API numOfDays 参数(1=24h, 2=48h, 3=72h) + */ + @Async + public void fetchAndSave(Long tenantId, int numOfDays) { + try { + TenantBroker.runAs(tenantId, t -> { + // 幂等检查:今日已有 PENDING 或 SUCCESS 记录则跳过(FAILED 允许重试) + boolean alreadyRunning = clarityDataMapper.exists(Wrappers.lambdaQuery() + .eq(SysClarityData::getDataDate, LocalDate.now()) + .eq(SysClarityData::getNumOfDays, numOfDays) + .in(SysClarityData::getFetchStatus, ClarityFetchStatus.PENDING, ClarityFetchStatus.SUCCESS)); + if (alreadyRunning) { + log.debug("[Clarity] 今日 numOfDays={} 已有进行中或成功记录,跳过", numOfDays); + return; + } + + // 获取 Clarity 配置(type='clarity') + SysSocialDetails config = socialDetailsMapper.selectOne( + Wrappers.lambdaQuery().eq(SysSocialDetails::getType, CLARITY_TYPE)); + if (config == null) { + log.warn("[Clarity] 未找到 type='clarity' 的配置,跳过拉取"); + return; + } + + // 插入占位行,防止并发重复调用 + SysClarityData placeholder = new SysClarityData(); + placeholder.setDataDate(LocalDate.now()); + placeholder.setNumOfDays(numOfDays); + placeholder.setFetchStatus(ClarityFetchStatus.PENDING); + clarityDataMapper.insert(placeholder); + log.info("[Clarity] 占位行已插入,id={},numOfDays={}", placeholder.getId(), numOfDays); + + // 调用 Clarity API + HttpResponse response = HttpRequest.get(CLARITY_API_BASE + numOfDays) + .header("Authorization", "Bearer " + config.getAppSecret()) + .header("Content-Type", "application/json") + .execute(); + + if (!response.isOk()) { + log.error("[Clarity] API 调用失败,状态码:{}", response.getStatus()); + // 标记失败,下次调度可重试 + updateStatus(placeholder.getId(), ClarityFetchStatus.FAILED); + return; + } + + // 解析响应并填充数据 + JSONArray body = JSONUtil.parseArray(response.body()); + SysClarityData data = parseResponse(body); + data.setId(placeholder.getId()); + data.setFetchStatus(ClarityFetchStatus.SUCCESS); + clarityDataMapper.updateById(data); + log.info("[Clarity] 数据拉取并存库成功,日期:{},numOfDays:{}", LocalDate.now(), numOfDays); + }); + } + catch (Exception e) { + log.error("[Clarity] 数据拉取异常", e); + } + } + + private void updateStatus(Long id, ClarityFetchStatus status) { + SysClarityData update = new SysClarityData(); + update.setId(id); + update.setFetchStatus(status); + clarityDataMapper.updateById(update); + } + + /** + * 解析 Clarity API 响应体(JSONArray 结构)到实体 + * 字段映射已通过真实 API 调用确认(2026-03-26) + * 所有 count 类字段为 String 类型,使用 Convert.toInt 安全转换(null/空串返回 0) + */ + private SysClarityData parseResponse(JSONArray responseArray) { + SysClarityData data = new SysClarityData(); + + for (Object item : responseArray) { + if (!(item instanceof JSONObject metric)) { + continue; + } + String metricName = metric.getStr("metricName"); + JSONArray info = metric.getJSONArray("information"); + if (info == null || info.isEmpty()) { + continue; + } + JSONObject first = info.getJSONObject(0); + + switch (metricName) { + case "Traffic" -> { + data.setTotalSessions(Convert.toInt(first.getStr("totalSessionCount"), 0)); + data.setDistinctUsers(Convert.toInt(first.getStr("distinctUserCount"), 0)); + data.setPagesPerSession(first.getBigDecimal("pagesPerSessionPercentage")); + } + case "ScrollDepth" -> data.setScrollDepth(first.getBigDecimal("averageScrollDepth")); + case "DeadClickCount" -> data.setDeadClickRate(first.getBigDecimal("sessionsWithMetricPercentage")); + case "RageClickCount" -> data.setRageClickRate(first.getBigDecimal("sessionsWithMetricPercentage")); + case "Device" -> data.setDeviceData(toJsonRanking(info.stream(), dev -> + dev.getStr("name"), dev -> Convert.toInt(dev.getStr("sessionsCount"), 0))); + case "PopularPages" -> data.setTopUrls(toJsonRanking(info.stream().limit(5), page -> + page.getStr("url"), page -> Convert.toInt(page.getStr("visitsCount"), 0))); + case "ReferrerUrl" -> data.setReferrerUrlData(toJsonRanking(info.stream().limit(5), ref -> + StrUtil.blankToDefault(ref.getStr("url"), ref.getStr("name", "unknown")), + ref -> Convert.toInt(ref.getStr("sessionsCount"), 0))); + case "PageTitle" -> data.setPageTitleData(toJsonRanking(info.stream().limit(5), pt -> + StrUtil.blankToDefault(pt.getStr("name"), pt.getStr("title", "unknown")), + pt -> Convert.toInt(pt.getStr("sessionsCount"), 0))); + case "Browser" -> data.setBrowserData(toJsonRanking(info.stream(), br -> + StrUtil.blankToDefault(br.getStr("name"), "unknown"), + br -> Convert.toInt(br.getStr("sessionsCount"), 0))); + default -> { + } + } + } + + return data; + } + + /** + * 将 JSONArray 流转换为 [{name, value}] 格式的 JSON 字符串 + */ + private String toJsonRanking(Stream stream, + Function nameExtractor, + Function valueExtractor) { + List> list = new ArrayList<>(); + stream.forEach(item -> { + if (item instanceof JSONObject obj) { + list.add(Map.of("name", nameExtractor.apply(obj), "value", valueExtractor.apply(obj))); + } + }); + return JSONUtil.toJsonStr(list); + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/DingTalkLoginHandler.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/DingTalkLoginHandler.java index 45aca172a..117c6ca97 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/DingTalkLoginHandler.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/DingTalkLoginHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -121,7 +121,7 @@ public class DingTalkLoginHandler extends AbstractLoginHandler { public Boolean bind(SysUser user, String identify) { user.setWxDingUserid(identify); sysUserService.updateById(user); - return true; - } + return true; + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/MiniAppLoginHandler.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/MiniAppLoginHandler.java index ca3b1cb1a..76a3cef1d 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/MiniAppLoginHandler.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/MiniAppLoginHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -34,6 +34,7 @@ import com.pig4cloud.pigx.common.core.constant.enums.LoginTypeEnum; import com.pig4cloud.pigx.common.core.exception.CheckedException; import com.pig4cloud.pigx.common.core.util.MsgUtils; import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -100,8 +101,8 @@ public class MiniAppLoginHandler extends AbstractLoginHandler { R userInfoR = sysUserService.getUserInfo(userDTO); if (userInfoR.getData() == null) { - log.info("小程序不存在用户:{}", openId); - return null; + log.info("小程序不存在用户:{}, 开始自动创建", openId); + return createMiniAppUser(openId); } return userInfoR.getData(); @@ -121,4 +122,39 @@ public class MiniAppLoginHandler extends AbstractLoginHandler { return true; } + /** + * 自动创建小程序用户 + * + * @param openId 小程序openId + * @return 创建成功返回用户信息,失败返回null + */ + private UserInfo createMiniAppUser(String openId) { + try { + log.info("开始为openId[{}]创建小程序用户", openId); + + // 构造用户DTO + UserDTO newUser = new UserDTO(); + newUser.setMiniOpenid(openId); + newUser.setUsername(openId); + newUser.setPassword(openId); + newUser.setTenantId(TenantContextHolder.getTenantId()); + // 保存用户 + sysUserService.saveUser(newUser); + log.info("小程序用户创建成功,openId: {}", openId); + + // 重新获取用户信息 + UserDTO queryDTO = new UserDTO(); + queryDTO.setMiniOpenid(openId); + R userInfoR = sysUserService.getUserInfo(queryDTO); + + if (userInfoR.getData() != null) { + return userInfoR.getData(); + } + } catch (Exception e) { + log.error("创建小程序用户异常,openId: {}, 错误信息: {}", openId, e.getMessage(), e); + } + + return null; + } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/OscChinaLoginHandler.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/OscChinaLoginHandler.java index 8a51db533..5a796b694 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/OscChinaLoginHandler.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/handler/OscChinaLoginHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -127,7 +127,7 @@ public class OscChinaLoginHandler extends AbstractLoginHandler { public Boolean bind(SysUser user, String identify) { user.setOscId(identify); sysUserService.updateById(user); - return true; - } + return true; + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysApiKeyMapper.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysApiKeyMapper.java new file mode 100644 index 000000000..093f3e08c --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysApiKeyMapper.java @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.mapper; + +import com.pig4cloud.pigx.admin.api.entity.SysApiKey; +import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * API密钥 Mapper + * + * @author lengleng + * @date 2026-03-23 + */ +@Mapper +public interface SysApiKeyMapper extends PigxBaseMapper { + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysClarityDataMapper.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysClarityDataMapper.java new file mode 100644 index 000000000..2cb1d336c --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysClarityDataMapper.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.mapper; + +import com.pig4cloud.pigx.admin.api.entity.SysClarityData; +import com.pig4cloud.pigx.common.data.datascope.PigxBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * Clarity 监控数据 Mapper + * + * @author lengleng + * @date 2026-03-26 + */ +@Mapper +public interface SysClarityDataMapper extends PigxBaseMapper { + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysRoleWidgetMapper.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysRoleWidgetMapper.java new file mode 100644 index 000000000..e29415fc0 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/mapper/SysRoleWidgetMapper.java @@ -0,0 +1,34 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.pig4cloud.pigx.admin.api.entity.SysRoleWidget; +import org.apache.ibatis.annotations.Mapper; + +/** + * 角色首页组件配置 Mapper + * + * @author lengleng + */ +@Mapper +public interface SysRoleWidgetMapper extends BaseMapper { + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysApiKeyService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysApiKeyService.java new file mode 100644 index 000000000..f0e12bcf3 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysApiKeyService.java @@ -0,0 +1,67 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.pig4cloud.pigx.admin.api.dto.ApiKeyCreateDTO; +import com.pig4cloud.pigx.admin.api.entity.SysApiKey; +import com.pig4cloud.pigx.common.core.util.R; + +/** + * API密钥管理 Service + * + * @author lengleng + * @date 2026-03-23 + */ +public interface SysApiKeyService extends IService { + + /** + * 校验密码、数量上限,生成并持久化 API Key + * @return 明文 Key(仅此一次) + */ + R createApiKey(ApiKeyCreateDTO dto); + + /** + * 校验归属权,白名单更新字段,清除缓存 + */ + R updateApiKey(SysApiKey apiKey); + + /** + * 校验归属权,删除记录,清除缓存 + */ + R deleteApiKey(Long id); + + /** + * 批量清除缓存后批量删除 + */ + R deleteBatchApiKey(Long[] ids); + + /** + * 管理员分页查询,按 username 模糊过滤 + */ + R page(Page page, SysApiKey query); + + /** + * 精确清除指定 hash 的缓存 + */ + void evictApiKeyCache(String hash); + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDeptService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDeptService.java index e0cd38ecd..f335bb05e 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDeptService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDeptService.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -40,50 +40,68 @@ import java.util.Map; */ public interface SysDeptService extends IService { - /** - * 查询部门树菜单 - * @param deptName 部门名称 - * @return 树 - */ - List> selectTree(String deptName, Long parentId); + /** + * 查询部门树菜单 + * + * @param deptName 部门名称 + * @return 树 + */ + List> selectTree(String deptName, Long parentId); - /** - * 删除部门 - * @param id 部门 ID - * @return 成功、失败 - */ - Boolean removeDeptById(Long id); + /** + * 删除部门 + * + * @param id 部门 ID + * @return 成功、失败 + */ + Boolean removeDeptById(Long id); - List listExcelVo(); + /** + * 获取部门Excel视图对象列表 + * + * @return 部门Excel视图对象列表 + */ + List listExcelVo(); - R importDept(List excelVOList, BindingResult bindingResult); + /** + * 导入部门数据 + * + * @param excelVOList 部门Excel数据列表 + * @param bindingResult 数据绑定结果 + * @return 导入结果 + */ + R importDept(List excelVOList, BindingResult bindingResult); - /** - * 获取部门的所有后代部门列表 - * @param deptId 部门ID - * @return 后代部门列表 - */ - List listDescendant(Long deptId); + /** + * 获取部门的所有后代部门列表 + * + * @param deptId 部门ID + * @return 后代部门列表 + */ + List listDescendant(Long deptId); - /** - * 获取部门负责人 - * @param deptId deptId - * @return user id list - */ - List listDeptLeader(Long deptId); + /** + * 获取部门负责人 + * + * @param deptId deptId + * @return user id list + */ + List listDeptLeader(Long deptId); - /** - * 查询全部部门包含用户 - * @param parentDeptId 父部门ID - * @param type 查询类型 - */ - Map listOrgTree(Long parentDeptId, String type); + /** + * 查询全部部门包含用户 + * + * @param parentDeptId 父部门ID + * @param type 查询类型 + */ + Map listOrgTree(Long parentDeptId, String type); - /** - * 根据用户名模糊搜索用户 - * @param username 用户名 - * @return List user - */ - List getOrgTreeUser(String username); + /** + * 根据用户名模糊搜索用户 + * + * @param username 用户名 + * @return List user + */ + List getOrgTreeUser(String username); } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDictItemService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDictItemService.java index 427163f2a..a0508614b 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDictItemService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysDictItemService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,6 +17,7 @@ package com.pig4cloud.pigx.admin.service; import com.baomidou.mybatisplus.extension.service.IService; +import com.pig4cloud.pigx.admin.api.dto.SysDictItemSortDTO; import com.pig4cloud.pigx.admin.api.entity.SysDictItem; import com.pig4cloud.pigx.common.core.util.R; @@ -42,4 +43,12 @@ public interface SysDictItemService extends IService { */ R updateDictItem(SysDictItem item); + /** + * 更新字典项排序 + * + * @param sortDTO 字典项排序信息(包含字典 ID 和按目标顺序排列的字典项 ID 列表) + * @return 成功 / 失败 + */ + R updateDictItemSort(SysDictItemSortDTO sortDTO); + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysFileService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysFileService.java index edca549fe..e3eed2cba 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysFileService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysFileService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -38,11 +38,11 @@ public interface SysFileService extends IService { /** * 上传文件 - * @param file 文件流 + * @param file 文件流 * @param fileName 文件名 - * @param dir 存储目录 - * @param groupId 文件分组ID - * @param type 文件类型 + * @param dir 存储目录 + * @param groupId 文件分组ID + * @param type 文件类型 * @return 上传结果 */ R uploadFile(MultipartFile file, String fileName, String dir, Long groupId, String type); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMenuService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMenuService.java index 278e7837a..2c72b2cc4 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMenuService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMenuService.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -21,6 +21,7 @@ package com.pig4cloud.pigx.admin.service; import cn.hutool.core.lang.tree.Tree; import com.baomidou.mybatisplus.extension.service.IService; +import com.pig4cloud.pigx.admin.api.dto.SysMenuSortDTO; import com.pig4cloud.pigx.admin.api.entity.SysMenu; import com.pig4cloud.pigx.common.core.util.R; @@ -58,6 +59,14 @@ public interface SysMenuService extends IService { */ Boolean updateMenuById(SysMenu sysMenu); + /** + * 更新同级菜单排序 + * + * @param sortDTO 菜单排序信息 + * @return 成功、失败 + */ + R updateMenuSort(SysMenuSortDTO sortDTO); + /** * 构建树 * @param parentId 父节点ID diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMessageService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMessageService.java index e8554f26f..32b422b38 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMessageService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysMessageService.java @@ -65,11 +65,13 @@ public interface SysMessageService extends IService { /** * 发送手机验证码 * - * @param mobile 手机号码 - * @param registered 是否已注册 + * @param mobile 手机号码 + * @param registered 是否已注册 + * @param captchaType 图形验证码类型 + * @param captchaVerification 图形验证码凭据 * @return 发送结果 */ - R sendSmsCode(String mobile, boolean registered); + R sendSmsCode(String mobile, boolean registered, String captchaType, String captchaVerification); /** * 发送短信 @@ -99,9 +101,9 @@ public interface SysMessageService extends IService { * 列出 Hook 业务代码 * * @param messageHookDTO 消息钩子 DTO - * @return {@link R } - */ - R listHookBizCode(MessageHookDTO messageHookDTO); + * @return {@link R } + */ + R listHookBizCode(MessageHookDTO messageHookDTO); /** * 发送站内消息/公告 diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysRoleWidgetService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysRoleWidgetService.java new file mode 100644 index 000000000..1d7601e0f --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysRoleWidgetService.java @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.pig4cloud.pigx.admin.api.entity.SysRoleWidget; + +/** + * 角色首页组件配置 Service + * + * @author lengleng + */ +public interface SysRoleWidgetService extends IService { + + /** + * 根据角色ID查询widget配置 + * @param roleId 角色ID + * @return widget配置,无配置时返回 null + */ + SysRoleWidget getByRoleId(Long roleId); + + /** + * 根据当前登录用户的角色列表,返回 widgetKeys 最多的那条配置 + * @return widget配置,无配置时返回 null + */ + SysRoleWidget getByCurrentUser(); + + /** + * 保存或更新角色widget配置 + * @param sysRoleWidget widget配置 + * @return 是否成功 + */ + boolean saveOrUpdateByRoleId(SysRoleWidget sysRoleWidget); + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSiteConfigService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSiteConfigService.java new file mode 100644 index 000000000..c2eb507e2 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSiteConfigService.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.service; + +import com.pig4cloud.pigx.admin.dto.SiteConfigDTO; + +import java.util.Map; + +/** + * 站点配置服务 + * + * @author lengleng + * @date 2026-03-27 + */ +public interface SysSiteConfigService { + + /** + * 获取聚合配置(i18n + 站点配置) + * @return Map + */ + Map getAggregatedConfig(); + + /** + * 获取站点配置 + * @return SiteConfigDTO + */ + SiteConfigDTO getSiteConfig(); + + /** + * 更新站点配置 + * @param dto 站点配置DTO + */ + void updateSiteConfig(SiteConfigDTO dto); + + /** + * 刷新站点配置缓存(清空 Redis 中的 site_config_details) + */ + void refreshCache(); + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSocialDetailsService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSocialDetailsService.java index b9e57c401..2542213f7 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSocialDetailsService.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSocialDetailsService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -47,6 +47,6 @@ public interface SysSocialDetailsService extends IService { */ UserInfo getUserInfo(String inStr); - List selectList(); + List selectList(); } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSystemInfoService.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSystemInfoService.java new file mode 100644 index 000000000..d56f1b52d --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/SysSystemInfoService.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.pig4cloud.pigx.admin.api.entity.SysClarityData; +import com.pig4cloud.pigx.common.core.util.R; + +import java.util.Map; + +/** + * 系统监控服务 + * + * @author lengleng + * @date 2026-03-26 + */ +public interface SysSystemInfoService extends IService { + + /** + * 获取 Redis 缓存监控信息 + * @return R + */ + R> getCacheInfo(); + + /** + * 获取今日 Clarity 数据 + * 查库优先:有数据直接返回;无数据触发异步拉取并返回 loading 状态 + * @param numOfDays Clarity API numOfDays 参数(1=24h, 2=48h, 3=72h) + * @return R + */ + R getClarityData(int numOfDays); + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysApiKeyServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysApiKeyServiceImpl.java new file mode 100644 index 000000000..3eae4031f --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysApiKeyServiceImpl.java @@ -0,0 +1,172 @@ +/* + * + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + * + */ + +package com.pig4cloud.pigx.admin.service.impl; + +import cn.hutool.core.codec.Base62; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.pig4cloud.pigx.admin.api.dto.ApiKeyCreateDTO; +import com.pig4cloud.pigx.admin.api.entity.SysApiKey; +import com.pig4cloud.pigx.admin.mapper.SysApiKeyMapper; +import com.pig4cloud.pigx.admin.service.SysApiKeyService; +import com.pig4cloud.pigx.admin.service.SysUserService; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.security.util.SecurityUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.HexFormat; + +/** + * API密钥管理 Service 实现 + * + * @author lengleng + * @date 2026-03-23 + */ +@Service +@RequiredArgsConstructor +public class SysApiKeyServiceImpl extends ServiceImpl implements SysApiKeyService { + + private static final String API_KEY_PREFIX = "sk-"; + + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + + private static final int MAX_KEYS_PER_USER = 5; + + private final SysUserService userService; + + /** + * 校验密码、数量上限,生成并持久化 API Key + */ + @Override + public R createApiKey(ApiKeyCreateDTO dto) { + String username = SecurityUtils.getUser().getUsername(); + R checkResult = userService.checkPassword(username, dto.getPassword()); + if (!(boolean) checkResult.getData()) { + return R.failed("密码验证失败"); + } + + Long userId = SecurityUtils.getUser().getId(); + long count = this.count(Wrappers.lambdaQuery().eq(SysApiKey::getUserId, userId)); + if (count >= MAX_KEYS_PER_USER) { + return R.failed("每个用户最多创建" + MAX_KEYS_PER_USER + "个API密钥"); + } + + byte[] randomBytes = new byte[32]; + SECURE_RANDOM.nextBytes(randomBytes); + String rawKey = API_KEY_PREFIX + Base62.encode(randomBytes); + + SysApiKey apiKey = new SysApiKey(); + apiKey.setName(dto.getName()); + apiKey.setAllowedIps(dto.getAllowedIps()); + apiKey.setExpiresAt(dto.getExpiresAt()); + apiKey.setApiKeyHash(sha256(rawKey)); + apiKey.setUserId(userId); + apiKey.setUsername(username); + apiKey.setStatus("0"); + this.save(apiKey); + + return R.ok(rawKey); + } + + /** + * 校验归属权,白名单更新字段,清除缓存 + */ + @Override + public R updateApiKey(SysApiKey apiKey) { + SysApiKey existing = this.getById(apiKey.getId()); + if (existing == null || !existing.getUserId().equals(SecurityUtils.getUser().getId())) { + return R.failed("无权操作"); + } + SysApiKey update = new SysApiKey(); + update.setId(apiKey.getId()); + update.setName(apiKey.getName()); + update.setAllowedIps(apiKey.getAllowedIps()); + update.setExpiresAt(apiKey.getExpiresAt()); + update.setStatus(apiKey.getStatus()); + this.updateById(update); + evictApiKeyCache(existing.getApiKeyHash()); + return R.ok(); + } + + /** + * 校验归属权,删除记录,清除缓存 + */ + @Override + public R deleteApiKey(Long id) { + SysApiKey existing = this.getById(id); + if (existing == null || !existing.getUserId().equals(SecurityUtils.getUser().getId())) { + return R.failed("无权操作"); + } + this.removeById(id); + evictApiKeyCache(existing.getApiKeyHash()); + return R.ok(); + } + + /** + * 批量清除缓存后批量删除 + */ + @Override + public R deleteBatchApiKey(Long[] ids) { + CollUtil.toList(ids).forEach(id -> { + SysApiKey existing = this.getById(id); + if (existing != null) { + evictApiKeyCache(existing.getApiKeyHash()); + } + }); + return R.ok(this.removeBatchByIds(CollUtil.toList(ids))); + } + + /** + * 管理员分页查询,按 username 模糊过滤 + */ + @Override + public R page(Page page, SysApiKey query) { + return R.ok(this.page(page, Wrappers.lambdaQuery() + .like(StrUtil.isNotBlank(query.getUsername()), SysApiKey::getUsername, query.getUsername()) + .orderByDesc(SysApiKey::getCreateTime))); + } + + @Override + @CacheEvict(value = CacheConstants.API_KEY_DETAILS, key = "#hash") + public void evictApiKeyCache(String hash) { + // 缓存清除由 @CacheEvict 注解驱动 + } + + public static String sha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(hash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysAreaServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysAreaServiceImpl.java index 1d3815fa7..01fa00a3f 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysAreaServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysAreaServiceImpl.java @@ -80,6 +80,6 @@ public class SysAreaServiceImpl extends ServiceImpl itemIds = sortDTO.getItemIds(); + Set uniqueItemIds = new HashSet<>(itemIds); + if (uniqueItemIds.size() != itemIds.size()) { + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_DICT_ITEM_SORT_DUPLICATE)); + } + + List sortedItems = this.list(Wrappers.lambdaQuery() + .eq(SysDictItem::getDictId, sortDTO.getDictId()) + .orderByAsc(SysDictItem::getSortOrder) + .orderByAsc(SysDictItem::getId)); + Map itemMap = sortedItems.stream().collect(Collectors.toMap(SysDictItem::getId, Function.identity())); + + if (!itemMap.keySet().containsAll(itemIds)) { + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_DICT_ITEM_SORT_SCOPE_INVALID)); + } + + // 兼容分页排序:以当前页字典项在全量列表中的最小位置作为插入点,将整页按新顺序回插 + Map itemIndexMap = new HashMap<>(sortedItems.size()); + for (int index = 0; index < sortedItems.size(); index++) { + itemIndexMap.put(sortedItems.get(index).getId(), index); + } + int insertIndex = itemIds.stream().map(itemIndexMap::get).min(Integer::compareTo).orElse(sortedItems.size()); + + List orderedPageItems = itemIds.stream().map(itemMap::get).toList(); + List reorderedItems = new ArrayList<>(sortedItems); + reorderedItems.removeAll(orderedPageItems); + reorderedItems.addAll(insertIndex, orderedPageItems); + + List updateList = new ArrayList<>(reorderedItems.size()); + for (int index = 0; index < reorderedItems.size(); index++) { + SysDictItem updateItem = new SysDictItem(); + updateItem.setId(reorderedItems.get(index).getId()); + updateItem.setSortOrder(index); + updateList.add(updateItem); + } + + return R.ok(this.updateBatchById(updateList)); + } + } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java index ff78da248..5c45d96d1 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysFileServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,7 +28,6 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.http.HttpUtil; -import com.amazonaws.services.s3.model.S3Object; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pigx.admin.api.dto.SysFileGroupDTO; @@ -40,6 +39,7 @@ import com.pig4cloud.pigx.admin.service.SysFileService; import com.pig4cloud.pigx.common.core.constant.CommonConstants; import com.pig4cloud.pigx.common.core.util.R; import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; +import com.pig4cloud.pigx.common.file.core.FileObject; import com.pig4cloud.pigx.common.file.core.FileProperties; import com.pig4cloud.pigx.common.file.core.FileTemplate; import jakarta.servlet.http.HttpServletResponse; @@ -128,13 +128,13 @@ public class SysFileServiceImpl extends ServiceImpl impl return; } - try (S3Object s3Object = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName)) { + try (FileObject fileObject = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName)) { response.setContentType("application/octet-stream; charset=UTF-8"); // 文件唯一hash response.addHeader(SysFile.Fields.hash, sysFile.getHash()); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLUtil.encode(sysFile.getOriginal())); - IoUtil.copy(s3Object.getObjectContent(), response.getOutputStream()); + IoUtil.copy(fileObject.getObjectContent(), response.getOutputStream()); } catch (Exception e) { log.error("文件读取异常: {}", e.getLocalizedMessage()); } @@ -250,12 +250,12 @@ public class SysFileServiceImpl extends ServiceImpl impl sysFile.setOriginal(file.getOriginalFilename()); sysFile.setFileSize(file.getSize()); sysFile.setBucketName(properties.getBucketName()); - sysFile.setType(type); - sysFile.setGroupId(groupId); + sysFile.setType(type); + sysFile.setGroupId(groupId); - sysFile.setHash(SecureUtil.md5(file.getInputStream())); - // 调用save方法保存SysFile对象 - this.save(sysFile); - } + sysFile.setHash(SecureUtil.md5(file.getInputStream())); + // 调用save方法保存SysFile对象 + this.save(sysFile); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysI18nServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysI18nServiceImpl.java index 6dda66de6..1a33e055c 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysI18nServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysI18nServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -23,7 +23,6 @@ import com.pig4cloud.pigx.admin.service.SysI18nService; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.util.R; import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -45,7 +44,6 @@ public class SysI18nServiceImpl extends ServiceImpl sysI18nEntities = baseMapper.selectList(null); HashMap>> stringListHashMap = new HashMap<>(); @@ -69,7 +67,7 @@ public class SysI18nServiceImpl extends ServiceImpl impleme }) // 按createTime升序排序 .sorted(Comparator.comparing(map -> map.get(SysLog.Fields.createTime).toString())) - .toList(); - } + .toList(); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMenuServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMenuServiceImpl.java index c7a0247e6..707adeed9 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMenuServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMenuServiceImpl.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -28,6 +28,7 @@ import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pigx.admin.api.constant.UpmsErrorCodes; +import com.pig4cloud.pigx.admin.api.dto.SysMenuSortDTO; import com.pig4cloud.pigx.admin.api.entity.SysI18nEntity; import com.pig4cloud.pigx.admin.api.entity.SysMenu; import com.pig4cloud.pigx.admin.api.entity.SysRoleMenu; @@ -38,10 +39,8 @@ import com.pig4cloud.pigx.admin.service.SysMenuService; import com.pig4cloud.pigx.common.core.constant.CacheConstants; import com.pig4cloud.pigx.common.core.constant.CommonConstants; import com.pig4cloud.pigx.common.core.constant.enums.MenuTypeEnum; -import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum; import com.pig4cloud.pigx.common.core.util.MsgUtils; import com.pig4cloud.pigx.common.core.util.R; -import com.pig4cloud.pigx.common.security.util.SecurityUtils; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -54,6 +53,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; /** *

@@ -125,6 +125,37 @@ public class SysMenuServiceImpl extends ServiceImpl impl return this.updateById(sysMenu); } + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(value = CacheConstants.MENU_DETAILS, allEntries = true) + public R updateMenuSort(SysMenuSortDTO sortDTO) { + List menuIds = sortDTO.getMenuIds(); + Set uniqueMenuIds = new HashSet<>(menuIds); + if (uniqueMenuIds.size() != menuIds.size()) { + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_MENU_SORT_DUPLICATE)); + } + + List menus = this.list(Wrappers.lambdaQuery() + .in(SysMenu::getMenuId, menuIds) + .eq(SysMenu::getParentId, sortDTO.getParentId())); + + if (menus.size() != menuIds.size()) { + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_MENU_SORT_SCOPE_INVALID)); + } + + Map menuMap = menus.stream().collect(Collectors.toMap(SysMenu::getMenuId, Function.identity())); + List updateList = new ArrayList<>(menuIds.size()); + for (int index = 0; index < menuIds.size(); index++) { + SysMenu menu = menuMap.get(menuIds.get(index)); + SysMenu updateMenu = new SysMenu(); + updateMenu.setMenuId(menu.getMenuId()); + updateMenu.setSortOrder(index); + updateList.add(updateMenu); + } + + return R.ok(this.updateBatchById(updateList)); + } + /** * 构建树查询 1. 不是懒加载情况,查询全部 2. 是懒加载,根据parentId 查询 2.1 父节点为空,则查询ID -1 * @@ -136,7 +167,11 @@ public class SysMenuServiceImpl extends ServiceImpl impl public List> treeMenu(Long parentId, String menuName, String type) { Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId; - List> collect = baseMapper.selectList(Wrappers.lambdaQuery().eq(Objects.nonNull(parentId), SysMenu::getParentId, parentId).like(StrUtil.isNotBlank(menuName), SysMenu::getName, menuName).eq(StrUtil.isNotBlank(type), SysMenu::getMenuType, type).orderByAsc(SysMenu::getSortOrder)).stream().map(getNodeFunction()).toList(); + List> collect = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(Objects.nonNull(parentId), SysMenu::getParentId, parentId) + .like(StrUtil.isNotBlank(menuName), SysMenu::getName, menuName) + .eq(StrUtil.isNotBlank(type), SysMenu::getMenuType, type) + .orderByAsc(SysMenu::getSortOrder)).stream().map(getNodeFunction()).toList(); // 模糊查询 不组装树结构 直接返回 表格方便编辑 if (StrUtil.isNotBlank(menuName)) { @@ -215,7 +250,7 @@ public class SysMenuServiceImpl extends ServiceImpl impl return MenuTypeEnum.TOP_MENU.getType().equals(vo.getMenuType()); } // 其他查询 左侧 + 顶部 - return !MenuTypeEnum.BUTTON.getType().equals(vo.getMenuType()); + return StrUtil.isNotBlank(vo.getMenuType()) && !MenuTypeEnum.BUTTON.getType().equals(vo.getMenuType()); }; } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMessageServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMessageServiceImpl.java index 0c5864877..b591bbd09 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMessageServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysMessageServiceImpl.java @@ -14,7 +14,6 @@ import cn.hutool.extra.template.TemplateEngine; import cn.hutool.extra.template.TemplateUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; -import com.amazonaws.services.s3.model.S3Object; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -44,10 +43,13 @@ import com.pig4cloud.pigx.common.data.cache.RedisUtils; import com.pig4cloud.pigx.common.data.resolver.ParamResolver; import com.pig4cloud.pigx.common.data.tenant.TenantBroker; import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; +import com.pig4cloud.pigx.common.file.core.FileObject; import com.pig4cloud.pigx.common.file.core.FileTemplate; import com.pig4cloud.pigx.common.log.util.JacksonSensitiveFieldUtil; import com.pig4cloud.pigx.common.log.util.LogTypeEnum; import com.pig4cloud.pigx.common.log.util.SysLogUtils; +import com.pig4cloud.pigx.common.security.captcha.CaptchaResult; +import com.pig4cloud.pigx.common.security.captcha.CaptchaValidator; import com.pig4cloud.pigx.common.security.util.SecurityUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; @@ -117,6 +119,8 @@ public class SysMessageServiceImpl extends ServiceImpl sendSmsCode(String mobile, boolean registered) { - // IP 限制校验,单位时间内同一IP发送次数限制 + public R sendSmsCode(String mobile, boolean registered, String captchaType, String captchaVerification) { + // IP 限制校验前置,避免恶意请求反向放大图形验证码服务压力 String clientIp = WebUtils.getIP(); String ipLimitKey = CacheConstants.DEFAULT_CODE_KEY + "IP_LIMIT:" + clientIp; if (StrUtil.isNotBlank(clientIp)) { @@ -295,6 +301,11 @@ public class SysMessageServiceImpl extends ServiceImpl userList = TenantBroker .noneAs(() -> userMapper.selectList(Wrappers.query().lambda().eq(SysUser::getPhone, mobile))); @@ -332,6 +343,13 @@ public class SysMessageServiceImpl extends ServiceImpl configEntityList = sysSystemConfigMapper @@ -475,6 +493,14 @@ public class SysMessageServiceImpl extends ServiceImpl configEntityList = sysSystemConfigMapper .selectList(Wrappers.lambdaQuery() @@ -493,10 +519,11 @@ public class SysMessageServiceImpl extends ServiceImpllambdaQuery().eq(SysFile::getFileName, fileName)); - S3Object s3Object = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName); - File file = FileUtil.file(FileUtil.getTmpDirPath(), sysFile.getOriginal()); - tempFileList.add(file); - IoUtil.copy(s3Object.getObjectContent(), FileUtil.getOutputStream(file)); + try (FileObject fileObject = fileTemplate.getObject(sysFile.getBucketName(), sysFile.getDir(), fileName)) { + File file = FileUtil.file(FileUtil.getTmpDirPath(), sysFile.getOriginal()); + tempFileList.add(file); + IoUtil.copy(fileObject.getObjectContent(), FileUtil.getOutputStream(file)); + } } } @@ -533,6 +560,22 @@ public class SysMessageServiceImpl extends ServiceImpl + implements SysRoleWidgetService { + + /** + * 根据角色ID查询widget配置 + * @param roleId 角色ID + * @return widget配置,无配置时返回 null + */ + @Override + public SysRoleWidget getByRoleId(Long roleId) { + return getOne(Wrappers.lambdaQuery() + .eq(SysRoleWidget::getRoleId, roleId)); + } + + /** + * 根据当前登录用户的角色列表查询widget配置 + *

遍历用户所有角色对应的配置,返回 widgetKeys 数量最多的那条, + * 以保证多角色场景下首页显示最完整的widget集合。

+ * @return widget配置,用户无角色或角色均无配置时返回 null + */ + @Override + public SysRoleWidget getByCurrentUser() { + List roleIds = SecurityUtils.getRoleIds(); + if (roleIds.isEmpty()) { + return null; + } + // 查询当前用户所有角色的 widget 配置,取 widgetKeys 数量最多的 + return list(Wrappers.lambdaQuery() + .in(SysRoleWidget::getRoleId, roleIds)) + .stream() + .filter(w -> StringUtils.hasText(w.getWidgetKeys())) + .max(Comparator.comparingInt( + w -> w.getWidgetKeys().split(StrUtil.COMMA).length)) + .orElse(null); + } + + /** + * 保存或更新角色widget配置 + *

若该角色已存在配置则更新,否则新增。

+ * @param sysRoleWidget widget配置(roleId 必填) + * @return 操作成功返回 true + */ + @Override + public boolean saveOrUpdateByRoleId(SysRoleWidget sysRoleWidget) { + SysRoleWidget existing = getOne(Wrappers.lambdaQuery() + .eq(SysRoleWidget::getRoleId, sysRoleWidget.getRoleId())); + if (existing != null) { + sysRoleWidget.setId(existing.getId()); + return updateById(sysRoleWidget); + } + return save(sysRoleWidget); + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysRouteConfServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysRouteConfServiceImpl.java index 4d01381ee..92d6e4cea 100755 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysRouteConfServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysRouteConfServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2026, lengleng All rights reserved. + * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -141,6 +141,6 @@ public class SysRouteConfServiceImpl extends ServiceImpl getScheduleByScope(Page page, SysScheduleEntity sysSchedule) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.like(StrUtil.isNotBlank(sysSchedule.getTitle()), SysScheduleEntity::getTitle, sysSchedule.getTitle()); - wrapper.like(StrUtil.isNotBlank(sysSchedule.getScheduleType()), SysScheduleEntity::getScheduleType, - sysSchedule.getScheduleType()); - wrapper.eq(Objects.nonNull(sysSchedule.getScheduleDate()), SysScheduleEntity::getScheduleDate, - sysSchedule.getScheduleDate()); + wrapper.like(StrUtil.isNotBlank(sysSchedule.getScheduleType()), SysScheduleEntity::getScheduleType, + sysSchedule.getScheduleType()); + wrapper.eq(Objects.nonNull(sysSchedule.getScheduleDate()), SysScheduleEntity::getScheduleDate, + sysSchedule.getScheduleDate()); DataScope dataScope = new DataScope(); dataScope.setUsername(SecurityUtils.getUser().getUsername()); return baseMapper.selectPageByScope(page, wrapper, dataScope); diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSensitiveWordServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSensitiveWordServiceImpl.java index 06673adba..a4f405eab 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSensitiveWordServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSensitiveWordServiceImpl.java @@ -63,8 +63,8 @@ public class SysSensitiveWordServiceImpl extends ServiceImpl) connection -> { connection.publish(CacheConstants.SENSITIVE_REDIS_RELOAD_TOPIC.getBytes(), "刷新敏感词缓存".getBytes()); return null; - }); - return Boolean.TRUE; - } + }); + return Boolean.TRUE; + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSiteConfigServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSiteConfigServiceImpl.java new file mode 100644 index 000000000..ab7ad8847 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSiteConfigServiceImpl.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.pig4cloud.pigx.admin.api.entity.SysOauthClientDetails; +import com.pig4cloud.pigx.admin.api.entity.SysPublicParam; +import com.pig4cloud.pigx.admin.config.ClientDetailsInitRunner; +import com.pig4cloud.pigx.admin.dto.SiteConfigDTO; +import com.pig4cloud.pigx.admin.service.SysI18nService; +import com.pig4cloud.pigx.admin.service.SysOauthClientDetailsService; +import com.pig4cloud.pigx.admin.service.SysPublicParamService; +import com.pig4cloud.pigx.admin.service.SysSiteConfigService; +import com.pig4cloud.pigx.common.core.constant.CacheConstants; +import com.pig4cloud.pigx.common.core.constant.CommonConstants; +import com.pig4cloud.pigx.common.core.constant.enums.DictTypeEnum; +import com.pig4cloud.pigx.common.core.constant.enums.YesNoEnum; +import com.pig4cloud.pigx.common.core.util.SpringContextHolder; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +/** + * 站点配置服务实现 + * + * @author lengleng + * @date 2026-03-27 + */ +@Service +@RequiredArgsConstructor +public class SysSiteConfigServiceImpl implements SysSiteConfigService { + + /** + * SITE_ 参数键与 DTO 字段名的映射 + */ + private static final Map KEY_FIELD_MAP = new LinkedHashMap<>(); + + /** + * 布尔类型的字段集合 + */ + private static final Set BOOLEAN_FIELDS = Set.of("forceResetPwd", + "forceLogout", "smsLoginEnable", "socialLoginEnable", "registerEnable", "resetPassword", "i18nEnable", "darkModeEnable", + "antiDebugEnable", "syncDingtalkEnabled", "syncWechatEnabled"); + + static { + KEY_FIELD_MAP.put("SITE_CLARITY_ID", "clarityId"); + KEY_FIELD_MAP.put("SITE_CAPTCHA_TYPE", "captchaType"); + KEY_FIELD_MAP.put("SITE_PASSWORD_RULE", "passwordRule"); + KEY_FIELD_MAP.put("SITE_FORCE_RESET_PWD", "forceResetPwd"); + KEY_FIELD_MAP.put("SITE_FORCE_LOGOUT", "forceLogout"); + KEY_FIELD_MAP.put("SITE_SMS_LOGIN_ENABLE", "smsLoginEnable"); + KEY_FIELD_MAP.put("SITE_SOCIAL_LOGIN_ENABLE", "socialLoginEnable"); + KEY_FIELD_MAP.put("SITE_REGISTER_ENABLE", "registerEnable"); + KEY_FIELD_MAP.put("SITE_RESET_PASSWORD", "resetPassword"); + KEY_FIELD_MAP.put("SITE_I18N_ENABLE", "i18nEnable"); + KEY_FIELD_MAP.put("SITE_DARK_MODE_ENABLE", "darkModeEnable"); + KEY_FIELD_MAP.put("SITE_ANTI_DEBUG_ENABLE", "antiDebugEnable"); + KEY_FIELD_MAP.put("SITE_ANTI_DEBUG_KEY", "antiDebugKey"); + KEY_FIELD_MAP.put("SITE_SYNC_DINGTALK_ENABLED", "syncDingtalkEnabled"); + KEY_FIELD_MAP.put("SITE_SYNC_WECHAT_ENABLED", "syncWechatEnabled"); + KEY_FIELD_MAP.put("SITE_TITLE", "title"); + KEY_FIELD_MAP.put("SITE_FOOTER", "footer"); + KEY_FIELD_MAP.put("SITE_PRIVACY_TIP", "privacyTip"); + KEY_FIELD_MAP.put("SITE_LOGO", "logo"); + } + + private final SysPublicParamService sysPublicParamService; + + private final SysI18nService sysI18nService; + + private final SysOauthClientDetailsService sysOauthClientDetailsService; + + @Override + @Cacheable(value = CacheConstants.SITE_CONFIG_DETAILS) + public Map getAggregatedConfig() { + Map result = new HashMap<>(4); + result.put("i18n", sysI18nService.listMap()); + result.put("site", getSiteConfig()); + return result; + } + + @Override + public SiteConfigDTO getSiteConfig() { + List paramList = sysPublicParamService + .list(Wrappers.lambdaQuery().likeRight(SysPublicParam::getPublicKey, "SITE_")); + + SiteConfigDTO dto = new SiteConfigDTO(); + for (SysPublicParam param : paramList) { + String fieldName = KEY_FIELD_MAP.get(param.getPublicKey()); + if (fieldName == null) { + continue; + } + + Object value; + if (BOOLEAN_FIELDS.contains(fieldName)) { + value = YesNoEnum.YES.getCode().equals(param.getPublicValue()); + } else { + value = param.getPublicValue(); + } + BeanUtil.setFieldValue(dto, fieldName, value); + } + return dto; + } + + @Override + @Transactional(rollbackFor = Exception.class) + @CacheEvict(value = {CacheConstants.SITE_CONFIG_DETAILS, CacheConstants.PARAMS_DETAILS}, allEntries = true) + public void updateSiteConfig(SiteConfigDTO dto) { + Map fieldValues = BeanUtil.beanToMap(dto, false, true); + + for (Map.Entry entry : KEY_FIELD_MAP.entrySet()) { + String paramKey = entry.getKey(); + String fieldName = entry.getValue(); + Object fieldValue = fieldValues.get(fieldName); + if (fieldValue == null) { + continue; + } + + String publicValue; + if (BOOLEAN_FIELDS.contains(fieldName)) { + publicValue = YesNoEnum.getCode((Boolean) fieldValue); + } else { + publicValue = fieldValue.toString(); + } + + SysPublicParam existing = sysPublicParamService + .getOne(Wrappers.lambdaQuery().eq(SysPublicParam::getPublicKey, paramKey)); + if (Objects.nonNull(existing)) { + existing.setPublicValue(publicValue); + sysPublicParamService.updateById(existing); + } else { + SysPublicParam newParam = new SysPublicParam(); + newParam.setPublicKey(paramKey); + newParam.setPublicValue(publicValue); + newParam.setPublicName(paramKey); + newParam.setStatus(YesNoEnum.NO.getCode()); + newParam.setSystemFlag(DictTypeEnum.SYSTEM.getType()); + sysPublicParamService.save(newParam); + } + } + + // 同步 captcha_flag 到 OAuth 客户端扩展信息 + if (dto.getCaptchaType() != null) { + syncCaptchaFlagToClients(!"none".equals(dto.getCaptchaType())); + } + } + + /** + * 刷新站点配置缓存 + */ + @Override + @CacheEvict(value = {CacheConstants.SITE_CONFIG_DETAILS, CacheConstants.PARAMS_DETAILS}, allEntries = true) + public void refreshCache() { + // 仅触发 @CacheEvict,无需额外逻辑 + } + + /** + * 同步验证码开关到所有 OAuth 客户端的 additionalInformation + * + * @param captchaEnable 是否启用验证码 + */ + private void syncCaptchaFlagToClients(Boolean captchaEnable) { + String captchaFlag = YesNoEnum.getCode(captchaEnable); + List clients = sysOauthClientDetailsService.list(); + + for (SysOauthClientDetails client : clients) { + if (StrUtil.isBlank(client.getAdditionalInformation())) { + continue; + } + JSONObject informationObj = JSONUtil.parseObj(client.getAdditionalInformation()); + informationObj.set(CommonConstants.CAPTCHA_FLAG, captchaFlag); + client.setAdditionalInformation(informationObj.toString()); + sysOauthClientDetailsService.updateById(client); + } + + // 刷新 Redis 客户端缓存 + SpringContextHolder.publishEvent(new ClientDetailsInitRunner.ClientDetailsInitEvent(this)); + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemConfigServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemConfigServiceImpl.java index 311430e29..b8d4888c3 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemConfigServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemConfigServiceImpl.java @@ -113,9 +113,9 @@ public class SysSystemConfigServiceImpl extends ServiceImpl configs = (Map) configsField.get(null); - configs.clear(); - } - return R.ok(); - } + configs.clear(); + } + return R.ok(); + } } diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemInfoServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemInfoServiceImpl.java new file mode 100644 index 000000000..c0c84e426 --- /dev/null +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysSystemInfoServiceImpl.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2025, lengleng All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * Neither the name of the pig4cloud.com developer nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * Author: lengleng (wangiegie@gmail.com) + */ + +package com.pig4cloud.pigx.admin.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.pig4cloud.pigx.admin.api.entity.SysClarityData; +import com.pig4cloud.pigx.admin.handler.ClarityFetchTaskHandler; +import com.pig4cloud.pigx.admin.mapper.SysClarityDataMapper; +import com.pig4cloud.pigx.admin.service.SysSystemInfoService; +import com.pig4cloud.pigx.admin.api.constant.UpmsErrorCodes; +import com.pig4cloud.pigx.common.core.util.MsgUtils; +import com.pig4cloud.pigx.common.core.util.R; +import com.pig4cloud.pigx.common.data.cache.RedisUtils; +import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.connection.RedisServerCommands; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.util.*; + +/** + * 系统监控服务实现 + * + * @author lengleng + * @date 2026-03-26 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SysSystemInfoServiceImpl extends ServiceImpl + implements SysSystemInfoService { + + private final ClarityFetchTaskHandler clarityFetchTask; + + @Override + public R> getCacheInfo() { + Properties info = RedisUtils.execute((RedisCallback) RedisServerCommands::info); + Properties commandStats = RedisUtils + .execute((RedisCallback) connection -> connection.serverCommands().info("commandstats")); + Object dbSize = RedisUtils.execute((RedisCallback) RedisServerCommands::dbSize); + + if (commandStats == null) { + return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_SYSTEM_CACHE_FETCH_ERROR)); + } + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + + result.put("commandStats", pieList); + return R.ok(result); + } + + @Override + public R getClarityData(int numOfDays) { + // 查询今日对应 numOfDays 的缓存数据 + SysClarityData today = baseMapper.selectOne(Wrappers.lambdaQuery() + .eq(SysClarityData::getDataDate, LocalDate.now()) + .eq(SysClarityData::getNumOfDays, numOfDays)); + + if (today != null) { + return R.ok(today); + } + + // 无数据:触发异步拉取,立即返回 loading 状态 + // 提前捕获租户 ID,避免 @Async 新线程丢失 ThreadLocal 上下文 + clarityFetchTask.fetchAndSave(TenantContextHolder.getTenantId(), numOfDays); + + Map loading = new HashMap<>(2); + loading.put("loading", true); + loading.put("message", "数据获取中,请稍后手动刷新"); + return R.ok(loading); + } + +} diff --git a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysUserServiceImpl.java b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysUserServiceImpl.java index f0dc17a7f..e22da8cb9 100644 --- a/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysUserServiceImpl.java +++ b/pigx-upms/pigx-upms-biz/src/main/java/com/pig4cloud/pigx/admin/service/impl/SysUserServiceImpl.java @@ -246,24 +246,68 @@ public class SysUserServiceImpl extends ServiceImpl impl @Override @Transactional(rollbackFor = Exception.class) public Boolean deleteUserByIds(Long[] ids) { + Long currentTenantId = SecurityUtils.getUser().getTenantId(); + List userIds = CollUtil.toList(ids); + // 删除 spring cache - List userList = baseMapper.selectByIds(CollUtil.toList(ids)); + List userList = baseMapper.selectByIds(userIds); Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); for (SysUser sysUser : userList) { cache.evict(sysUser.getUsername()); } // 删除用户角色 - sysUserRoleMapper.delete(Wrappers.lambdaQuery().in(SysUserRole::getUserId, CollUtil.toList(ids))); - sysUserDeptMapper.delete(Wrappers.lambdaQuery().in(SysUserDept::getUserId, CollUtil.toList(ids))); - sysUserPostMapper.delete(Wrappers.lambdaQuery().in(SysUserPost::getUserId, CollUtil.toList(ids))); + sysUserRoleMapper.delete(Wrappers.lambdaQuery().in(SysUserRole::getUserId, userIds)); + sysUserDeptMapper.delete(Wrappers.lambdaQuery().in(SysUserDept::getUserId, userIds)); + sysUserPostMapper.delete(Wrappers.lambdaQuery().in(SysUserPost::getUserId, userIds)); sysTenantUserMapper.delete(Wrappers.lambdaQuery() - .in(SysTenantUser::getUserId, CollUtil.toList(ids)) - .eq(SysTenantUser::getTenantId, SecurityUtils.getUser().getTenantId())); - this.removeBatchByIds(CollUtil.toList(ids)); + .in(SysTenantUser::getUserId, userIds) + .eq(SysTenantUser::getTenantId, currentTenantId)); + + // 用户降级处理,如果用户关联了其他租户,则不删除用户信息,仅删除当前租户关联关系并更新用户所属租户;如果没有关联其他租户,则删除用户信息 + List removableUserIds = findRemovableUserIds(userList, currentTenantId); + if (CollUtil.isNotEmpty(removableUserIds)) { + this.removeBatchByIds(removableUserIds); + } return Boolean.TRUE; } + /** + * 查找可移除的用户ID列表 + * + * @param userList 用户列表 + * @param currentTenantId 当前租户ID + * @return 可移除的用户ID列表 + */ + private List findRemovableUserIds(List userList, Long currentTenantId) { + return TenantBroker.noneAs(() -> { + List removableUserIds = new ArrayList<>(userList.size()); + for (SysUser sysUser : userList) { + SysTenantUser otherTenantUser = sysTenantUserMapper.selectOne(Wrappers.lambdaQuery() + .eq(SysTenantUser::getUserId, sysUser.getUserId()) + .ne(SysTenantUser::getTenantId, currentTenantId), false); + + if (Objects.isNull(otherTenantUser)) { + removableUserIds.add(sysUser.getUserId()); + continue; + } + + UserDTO userDTO = new UserDTO(); + userDTO.setUserId(sysUser.getUserId()); + userDTO.setUsername(sysUser.getUsername()); + userDTO.setTenantId(otherTenantUser.getTenantId()); + sysTenantService.updateUserTenant(userDTO); + } + return removableUserIds; + }); + } + + /** + * 更新用户信息 + * + * @param userDto 用户信息传输对象 + * @return 更新结果 + */ @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public R updateUserInfo(UserDTO userDto) { @@ -282,14 +326,25 @@ public class SysUserServiceImpl extends ServiceImpl impl if (!StrUtil.equals(userDto.getCode(), codeObj.toString())) { return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_APP_SMS_ERROR)); } + sysUser.setPhone(userDto.getPhone()); } return R.ok(this.updateById(sysUser)); } + /** + * 更新用户信息。 + *

+ * 字段审计:根据 {@code userId} 在方法执行前后分别调用当前 Service 的 Mapper 查询用户快照, + * 对比结果由 pigx-common-audit 写入 sys_audit_log。 + *

+ * + * @param userDto 用户更新数据 + * @return 更新结果 + */ @Override @Transactional(rollbackFor = Exception.class) - @Audit(name = "用户更新", spel = "@sysUserMapper.selectById(#userDto.userId)") + @Audit(name = "用户更新", id = "#userDto.userId") @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public Boolean updateUser(UserDTO userDto) { // 更新用户表信息 @@ -537,7 +592,7 @@ public class SysUserServiceImpl extends ServiceImpl impl public R registerUser(RegisterUserDTO userDto) { String feignHeader = WebUtils.getRequest().getHeader(SecurityConstants.FROM); // 外部注册校验短信验证码,对 AI 的内部注册跳过验证码处理 - if (!SecurityConstants.FROM_IN.equals(feignHeader)){ + if (!SecurityConstants.FROM_IN.equals(feignHeader)) { if (StrUtil.isBlank(userDto.getCode())) { return R.failed(MsgUtils.getMessage(UpmsErrorCodes.SYS_PARAM_ILLEGAL)); } diff --git a/pigx-upms/pigx-upms-biz/src/main/resources/application.yml b/pigx-upms/pigx-upms-biz/src/main/resources/application.yml index ab5f384f5..3bd4ae543 100644 --- a/pigx-upms/pigx-upms-biz/src/main/resources/application.yml +++ b/pigx-upms/pigx-upms-biz/src/main/resources/application.yml @@ -12,10 +12,11 @@ spring: server-addr: ${NACOS_HOST:pigx-register}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} + gateway.server.webflux: # 关闭 Spring Cloud Gateway 的能力,避免与 Spring MVC 冲突。 + redis.enabled: false + enabled: false config: import: - optional:nacos:application-@profiles.active@.yml - optional:nacos:${spring.application.name}-@profiles.active@.yml - - diff --git a/pigx-upms/pigx-upms-biz/src/main/resources/file/qa.xlsx b/pigx-upms/pigx-upms-biz/src/main/resources/file/qa.xlsx index 0d2627edf..11d31f1aa 100644 Binary files a/pigx-upms/pigx-upms-biz/src/main/resources/file/qa.xlsx and b/pigx-upms/pigx-upms-biz/src/main/resources/file/qa.xlsx differ diff --git a/pigx-upms/pigx-upms-biz/src/main/resources/mapper/SysDeptMapper.xml b/pigx-upms/pigx-upms-biz/src/main/resources/mapper/SysDeptMapper.xml index 10a2feb26..0ea564cea 100644 --- a/pigx-upms/pigx-upms-biz/src/main/resources/mapper/SysDeptMapper.xml +++ b/pigx-upms/pigx-upms-biz/src/main/resources/mapper/SysDeptMapper.xml @@ -1,7 +1,7 @@