From d14849903c9532aa3fbac81f6da0cdee3e2ec393 Mon Sep 17 00:00:00 2001 From: weitang Date: Wed, 4 Jun 2025 13:52:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=96=B0=E5=A2=9E=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../stdproject/common/OperationLogAspect.java | 17 +- .../java/com/stdproject/config/WebConfig.java | 6 +- .../controller/AppMenuController.java | 211 +++++++++++++----- .../controller/AppOptLogController.java | 22 +- .../controller/AppOrganizationController.java | 35 ++- .../stdproject/controller/AuthController.java | 19 +- 6 files changed, 233 insertions(+), 77 deletions(-) diff --git a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java index ee8485e..82547b3 100644 --- a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java +++ b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java @@ -1,5 +1,8 @@ package com.stdproject.common; +import cn.hutool.http.useragent.Browser; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; import com.fasterxml.jackson.databind.ObjectMapper; import com.stdproject.entity.AppOptLog; import com.stdproject.service.IAppOptLogService; @@ -83,14 +86,14 @@ public class OperationLogAspect { // 创建操作日志对象 AppOptLog optLog = new AppOptLog(); - + // 设置基本信息 optLog.setOpttype(operationLog.type()); optLog.setModule(operationLog.module()); optLog.setDescription(operationLog.description()); optLog.setMethod(request.getMethod() + " " + request.getRequestURI()); optLog.setRequestip(getIpAddress(request)); - optLog.setBrowser(request.getHeader("User-Agent")); + optLog.setBrowser(getBrowser(request)); optLog.setLogtime(LocalDateTime.now()); // 设置操作用户 @@ -109,7 +112,7 @@ public class OperationLogAspect { if (operationLog.recordParams()) { String methodParams = getMethodParams(joinPoint); optLog.setParams(methodParams); - } + } // 保存日志 appOptLogService.save(optLog); @@ -147,6 +150,12 @@ public class OperationLogAspect { } } + public static String getBrowser(HttpServletRequest request) { + UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); + Browser browser = userAgent.getBrowser(); + return browser.getName(); + } + /** * 获取客户端IP地址 */ @@ -169,4 +178,4 @@ public class OperationLogAspect { } return ip; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/config/WebConfig.java b/backend/src/main/java/com/stdproject/config/WebConfig.java index 68e9fd3..ec54cfe 100644 --- a/backend/src/main/java/com/stdproject/config/WebConfig.java +++ b/backend/src/main/java/com/stdproject/config/WebConfig.java @@ -2,6 +2,7 @@ package com.stdproject.config; import cn.hutool.cache.Cache; import cn.hutool.cache.CacheUtil; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; @@ -15,12 +16,15 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { + @Value("${spring.security.jwt.expiration-ms}") + private Long expirationMs; + /** * 用户缓存 */ @Bean public Cache loginuserCache() { - return CacheUtil.newLRUCache(200); + return CacheUtil.newTimedCache(expirationMs); } /** diff --git a/backend/src/main/java/com/stdproject/controller/AppMenuController.java b/backend/src/main/java/com/stdproject/controller/AppMenuController.java index 1f5314d..ae8c164 100644 --- a/backend/src/main/java/com/stdproject/controller/AppMenuController.java +++ b/backend/src/main/java/com/stdproject/controller/AppMenuController.java @@ -1,6 +1,7 @@ package com.stdproject.controller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.stdproject.common.OperationLog; @@ -41,7 +42,6 @@ public class AppMenuController { @Autowired private IAppUserService appUserService; - @Operation(summary = "分页查询菜单列表") @PostMapping("/page") @OperationLog(type = "06", module = "菜单管理", description = "分页查询菜单列表") @@ -52,9 +52,9 @@ public class AppMenuController { // 关键字搜索 if (StringUtils.hasText(pageRequest.getKeyword())) { queryWrapper.and(wrapper -> wrapper - .like("name", pageRequest.getKeyword()) - .or().like("code", pageRequest.getKeyword()) - .or().like("url", pageRequest.getKeyword()) + .like("name", pageRequest.getKeyword()) + .or().like("code", pageRequest.getKeyword()) + .or().like("url", pageRequest.getKeyword()) ); } @@ -207,6 +207,33 @@ public class AppMenuController { return success ? Result.success("修改成功") : Result.error("修改失败"); } + @Operation(summary = "更新菜单及按钮是否有效") + @PutMapping("/display/{id}") + @OperationLog(type = "02", module = "菜单管理", description = "更新菜单及按钮是否有效") + public Result setIsDisplay( + @Parameter(description = "菜单ID") @PathVariable String id, + @RequestParam String isDisplay) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AppMenu::getId, id).set(AppMenu::getIsdisplay, isDisplay).set(AppMenu::getLastmodifydate, + LocalDateTime.now()).set(AppMenu::getLastmodifier, appUserService.getCurrentUsername()); + boolean success = appMenuService.update(updateWrapper); + return success ? Result.success("更新成功") : Result.error("更新失败"); + } + + @Operation(summary = "更新关联模块ID") + @PutMapping("/module/{id}") + @OperationLog(type = "02", module = "菜单管理", description = "更新关联模块ID") + public Result setModuleId( + @Parameter(description = "菜单ID") @PathVariable String id, + @RequestParam String moduleId) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AppMenu::getId, id).set(AppMenu::getModuleId, moduleId).set(AppMenu::getLastmodifydate, + LocalDateTime.now()).set(AppMenu::getLastmodifier, appUserService.getCurrentUsername()); + boolean success = appMenuService.update(updateWrapper); + return success ? Result.success("更新成功") : Result.error("更新失败"); + } + + @Operation(summary = "删除菜单") @DeleteMapping("/{id}") @OperationLog(type = "03", module = "菜单管理", description = "删除菜单") @@ -286,24 +313,24 @@ public class AppMenuController { */ private List buildMenuTree(List allMenus, String parentId) { return allMenus.stream() - .filter(menu -> parentId.equals(menu.getParentid())) - .map(menu -> { - MenuTreeNode node = new MenuTreeNode(); - node.setId(menu.getId()); - node.setCode(menu.getCode()); - node.setName(menu.getName()); - node.setIcon(menu.getIcon()); - node.setUrl(menu.getUrl()); - node.setType(menu.getType()); - node.setIslink(menu.getIslink()); - node.setIsdisplay(menu.getIsdisplay()); - node.setOrderno(menu.getOrderno()); - node.setParentid(menu.getParentid()); - node.setModuleId(menu.getModuleId()); - node.setChildren(buildMenuTree(allMenus, menu.getId())); - return node; - }) - .collect(Collectors.toList()); + .filter(menu -> parentId.equals(menu.getParentid())) + .map(menu -> { + MenuTreeNode node = new MenuTreeNode(); + node.setId(menu.getId()); + node.setCode(menu.getCode()); + node.setName(menu.getName()); + node.setIcon(menu.getIcon()); + node.setUrl(menu.getUrl()); + node.setType(menu.getType()); + node.setIslink(menu.getIslink()); + node.setIsdisplay(menu.getIsdisplay()); + node.setOrderno(menu.getOrderno()); + node.setParentid(menu.getParentid()); + node.setModuleId(menu.getModuleId()); + node.setChildren(buildMenuTree(allMenus, menu.getId())); + return node; + }) + .collect(Collectors.toList()); } /** @@ -330,6 +357,7 @@ public class AppMenuController { * 菜单树节点 */ public static class MenuTreeNode { + private String id; private String code; private String name; @@ -344,42 +372,125 @@ public class AppMenuController { private List children = new ArrayList<>(); // Getters and Setters - public String getId() { return id; } - public void setId(String id) { this.id = id; } - public String getCode() { return code; } - public void setCode(String code) { this.code = code; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getIcon() { return icon; } - public void setIcon(String icon) { this.icon = icon; } - public String getUrl() { return url; } - public void setUrl(String url) { this.url = url; } - public String getType() { return type; } - public void setType(String type) { this.type = type; } - public String getIslink() { return islink; } - public void setIslink(String islink) { this.islink = islink; } - public String getIsdisplay() { return isdisplay; } - public void setIsdisplay(String isdisplay) { this.isdisplay = isdisplay; } - public Integer getOrderno() { return orderno; } - public void setOrderno(Integer orderno) { this.orderno = orderno; } - public String getParentid() { return parentid; } - public void setParentid(String parentid) { this.parentid = parentid; } - public String getModuleId() { return moduleId; } - public void setModuleId(String moduleId) { this.moduleId = moduleId; } - public List getChildren() { return children; } - public void setChildren(List children) { this.children = children; } + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getIslink() { + return islink; + } + + public void setIslink(String islink) { + this.islink = islink; + } + + public String getIsdisplay() { + return isdisplay; + } + + public void setIsdisplay(String isdisplay) { + this.isdisplay = isdisplay; + } + + public Integer getOrderno() { + return orderno; + } + + public void setOrderno(Integer orderno) { + this.orderno = orderno; + } + + public String getParentid() { + return parentid; + } + + public void setParentid(String parentid) { + this.parentid = parentid; + } + + public String getModuleId() { + return moduleId; + } + + public void setModuleId(String moduleId) { + this.moduleId = moduleId; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } } /** * 菜单排序请求 */ public static class MenuOrderRequest { + private String id; private Integer orderno; - public String getId() { return id; } - public void setId(String id) { this.id = id; } - public Integer getOrderno() { return orderno; } - public void setOrderno(Integer orderno) { this.orderno = orderno; } + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Integer getOrderno() { + return orderno; + } + + public void setOrderno(Integer orderno) { + this.orderno = orderno; + } } } diff --git a/backend/src/main/java/com/stdproject/controller/AppOptLogController.java b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java index 4aa881b..5130ea8 100644 --- a/backend/src/main/java/com/stdproject/controller/AppOptLogController.java +++ b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java @@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.util.StringUtils; @@ -33,6 +34,7 @@ import java.util.List; @Tag(name = "操作日志管理", description = "操作日志查询、操作日志删除等功能") @RestController @RequestMapping("/api/optlog") +@Slf4j public class AppOptLogController { @Autowired @@ -173,11 +175,21 @@ public class AppOptLogController { @DeleteMapping("/clean/{days}") @OperationLog(type = "03", module = "操作日志管理", description = "清理历史操作日志") public Result cleanOldLogs(@Parameter(description = "保留天数") @PathVariable Integer days) { - LocalDateTime cutoff = LocalDateTime.now().minusDays(days); - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.lt(AppOptLog::getLogtime, cutoff); - boolean success = appOptLogService.remove(queryWrapper); - return success ? Result.success("清理成功") : Result.error("清理失败"); + try { + // 参数校验 + if (days == null || days < 0 || days > 3650) { + return Result.error("保留天数必须在0到3650之间"); + } + LocalDateTime cutoff = LocalDateTime.now().minusDays(days); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.lt(AppOptLog::getLogtime, cutoff); + boolean success = appOptLogService.remove(queryWrapper); + return success ? Result.success("清理成功") : Result.error("清理失败"); + } catch (Exception e) { + // 记录异常日志以便排查 + log.error("清理操作日志失败", e); + return Result.error("系统异常,请稍后再试"); + } } @Operation(summary = "获取操作日志统计信息") diff --git a/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java index 1feb644..aed968e 100644 --- a/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java +++ b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java @@ -2,12 +2,14 @@ package com.stdproject.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.stdproject.common.OperationLog; import com.stdproject.common.Result; import com.stdproject.entity.AppOrganization; import com.stdproject.service.IAppOrganizationService; import com.stdproject.service.IAppUserService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -43,7 +45,7 @@ public class AppOrganizationController { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("orgtype", "01"); queryWrapper.eq("isvaild", "1"); - queryWrapper.orderByAsc("orgcode"); + queryWrapper.orderByAsc("orgcode"); List children = appOrganizationService.list(queryWrapper); return Result.success(children); } @@ -51,7 +53,7 @@ public class AppOrganizationController { @Operation(summary = "查询部门列表") @GetMapping("/getDepartmentList") @OperationLog(type = "06", module = "组织管理", description = "查询部门列表") - public Result> getDepartmentList(@RequestParam String parentid,@RequestParam String keystr) { + public Result> getDepartmentList(@RequestParam String parentid, @RequestParam String keystr) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("orgtype", "02"); queryWrapper.eq("parentid", parentid); @@ -68,8 +70,6 @@ public class AppOrganizationController { return Result.success(children); } - - @Operation(summary = "根据ID查询组织详情") @GetMapping("getDetailById/{id}") @OperationLog(type = "06", module = "组织管理", description = "根据ID查询组织详情") @@ -78,7 +78,6 @@ public class AppOrganizationController { return Result.success(organization); } - @Operation(summary = "新增组织") @PostMapping("add") @OperationLog(type = "01", module = "组织管理", description = "新增组织") @@ -115,7 +114,7 @@ public class AppOrganizationController { if (parent == null || !"1".equals(parent.getIsvaild())) { return Result.error("父级组织不存在或已禁用"); } - + if ("02".equals(parent.getOrgtype())) { return Result.error("部门下不能有子组织"); } @@ -126,6 +125,19 @@ public class AppOrganizationController { return success ? Result.success("修改成功") : Result.error("修改失败"); } + @Operation(summary = "设置组织是否有效") + @PutMapping("/isValid/{id}") + @OperationLog(type = "02", module = "组织管理", description = "设置组织是否有效") + public Result setIsValid( + @Parameter(description = "组织ID") @PathVariable String id, + @RequestParam String isValid) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(AppOrganization::getId, id).set(AppOrganization::getIsvaild, isValid).set(AppOrganization::getLastmodifydate, + LocalDateTime.now()).set(AppOrganization::getLastmodifier, appUserService.getCurrentUsername()); + boolean success = appOrganizationService.update(updateWrapper); + return success ? Result.success("设置成功") : Result.error("设置失败"); + } + @Operation(summary = "删除组织") @PostMapping("delete/{id}") @OperationLog(type = "03", module = "组织管理", description = "删除组织") @@ -137,7 +149,7 @@ public class AppOrganizationController { /** * 根据组织类型和父节点生成新的组织编码 * - * @param orgType 组织类型:"01"表示公司,"02"表示部门 + * @param orgType 组织类型:"01"表示公司,"02"表示部门 * @param parentId 父节点ID,新增部门时使用 * @return 新的组织编码 */ @@ -156,7 +168,7 @@ public class AppOrganizationController { String parentCode = parentOrg != null ? parentOrg.getOrgcode() : "01"; // 获取当前父节点下的最大子节点编码 - String maxCompanyCode = getMaxCodeByParentId("01",parentId); + String maxCompanyCode = getMaxCodeByParentId("01", parentId); int nextLevel = 1; if (maxCompanyCode != null && !maxCompanyCode.isEmpty()) { @@ -182,7 +194,7 @@ public class AppOrganizationController { } // 获取该父节点下最大部门编号 - String maxDeptCodeStr = getMaxCodeByParentId("02",parentId); + String maxDeptCodeStr = getMaxCodeByParentId("02", parentId); int nextSeq = 1; if (maxDeptCodeStr != null && !maxDeptCodeStr.isEmpty()) { @@ -203,8 +215,7 @@ public class AppOrganizationController { } } - - private String getMaxCodeByParentId(String orgType,String parentId) { + private String getMaxCodeByParentId(String orgType, String parentId) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() .eq(AppOrganization::getOrgtype, orgType) .eq(AppOrganization::getParentid, parentId) @@ -218,4 +229,4 @@ public class AppOrganizationController { return list.isEmpty() ? null : list.get(0).getOrgcode(); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AuthController.java b/backend/src/main/java/com/stdproject/controller/AuthController.java index 0473cfe..56a4a8b 100644 --- a/backend/src/main/java/com/stdproject/controller/AuthController.java +++ b/backend/src/main/java/com/stdproject/controller/AuthController.java @@ -212,10 +212,19 @@ public class AuthController { String token = request.getHeader(Constants.JWT_HEADER); if (token != null && token.startsWith(Constants.JWT_PREFIX)) { token = token.substring(Constants.JWT_PREFIX.length()); - // 注意:在实际应用中,应该实现Token失效机制 - // 可以考虑使用短期Token或其他方式实现Token失效 - // 此处省略Token黑名单实现 - webConfig.loginuserCache().put(token, "1"); + // 实现token失效 + Date expirationDateFromToken = jwtUtils.getExpirationDateFromToken(token); + // 获取当前时间的时间戳(毫秒) + long nowMillis = System.currentTimeMillis(); + + // 获取 Token 过期时间的时间戳(毫秒) + long expirationMillis = expirationDateFromToken.getTime(); + + // 计算剩余有效时间(毫秒) + long remainingMillis = expirationMillis - nowMillis; + if (remainingMillis > 0) { + webConfig.loginuserCache().put(token, "1", remainingMillis); + } } // 清除安全上下文 @@ -281,6 +290,7 @@ public class AuthController { } @PostMapping("/refreshToken") + @Operation(summary = "刷新令牌", description = "刷新当前用户的令牌") public Result refreshToken(@RequestBody Map params) { String refreshToken = params.get("refreshToken"); @@ -309,5 +319,4 @@ public class AuthController { Date expirationDateFromToken = jwtUtils.getExpirationDateFromToken(token); return Result.success(expirationDateFromToken); } - }