feat: reorganize system menus and add error logs
| | |
| | | **æ¥ææ³¨æ** |
| | | - ä¸è¦æäº¤ `.codex-run`ãOffice éæä»¶æ `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt`ã |
| | | - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå
¬å¸ Git ä»åº `main` 忝ã |
| | | |
| | | ## 19. æ¶å°¾å¿«ç
§ï¼2026-10-06 å¨ä¸ï¼éæ± 21-22 èåæéå
¥å£ä¸é误æ¥å¿ï¼ |
| | | |
| | | **æ¬è½®å®æå
容** |
| | | - éæ± 21ï¼ç§»é¤ç¬ç«âèåæé管çâèåï¼å¨è§è²ç®¡çæä½åæ°å¢âæé设置âæé®ï¼å¹¶å¤ç¨èå/æé®æéæ å¼¹çªï¼æ§ `/system/menu-permissions` è·¯ç±å页é¢å·²å é¤ã |
| | | - éæ± 22ï¼ç³»ç»ç®¡çæ°å¢âç³»ç»é
ç½® / æéé
ç½® / æ¥å¿ç®¡çâä¸ä¸ªäºçº§ç®å½ï¼ç»ç»æºæãæ¿ä¿®åä½ã产åç»ææ ã设å¤åºç¡åºãå·¥ç¨ä¸ä¸ãTéä¸ä¸ãä¸åæ§å¶å½å
¥ç³»ç»é
ç½®ï¼ç¨æ·ç®¡çãè§è²ç®¡çãèå管çå½å
¥æéé
ç½®ï¼ç»å½æ¥å¿ãæä½æ¥å¿ãé误æ¥å¿å½å
¥æ¥å¿ç®¡çã |
| | | - é误æ¥å¿ä¸ºæ°å¢åè½ï¼`GlobalExceptionHandler` æè·æªå¤ççéä¸å¡å¼å¸¸å¹¶å¼æ¥åå
¥ `LX.T_SYS_ERROR_LOG`ï¼ä¸å¡æ ¡éªå¼å¸¸ä¸è®°å½ãåè¡¨æ¯ææ¨¡åãå¼å¸¸ç±»åãæ¶é´çéï¼è¯¦æ
å±ç¤ºå®æ´å æ ã |
| | | - æ°åºåå§ååæ§åºè¿ç§»åä¼å 餿§èåå¹¶è¡¥é½äºçº§ç®å½çç¶åèåå
³ç³»ï¼æéæ ç»§ç»ä¿çèåèç¹åæé®èç¹ï¼è§è²åæ¢åå¤éæ¡å¯åæ¾ã |
| | | |
| | | **ä¸»è¦æä»¶** |
| | | - å端ï¼`emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java`ã`.../mapper/SysErrorLogMapper.java`ã`.../log/ErrorLogRecorder.java`ã`.../common/GlobalExceptionHandler.java`ã`.../bootstrap/DataInitializer.java`ã`.../bootstrap/DatabaseBootstrap.java`ã`.../system/controller/SystemController.java`ã`.../system/service/SystemService.java`ã`.../system/service/ButtonPermissionCatalog.java`ã |
| | | - å端ï¼`emergency-repair-web/src/views/system/RoleManage.vue`ã`RolePermissionDialog.vue`ã`ErrorLogManage.vue`ã`emergency-repair-web/src/router/index.js`ã`MenuManage.vue`ï¼`MenuPermission.vue` å·²å é¤ã |
| | | |
| | | **éªè¯ç»æ** |
| | | - å端 `mvn -B -DskipTests clean package` éè¿ï¼å端 `npm run build` éè¿ï¼ä»
ææ¢æå
ä½ç§¯åè¦ã |
| | | - `node .codex-run/verify-req21-22.js` éè¿ï¼ä¸ä¸ªäºçº§ç®å½åé误æ¥å¿è忣叏æ¾ç¤ºï¼æ§èåä¸åå¨ï¼è§è²ç®¡çâæé设置âå¼¹çªæé®èç¹æ°ä¸º 78 ä¸åæ¾æ£å¸¸ã |
| | | - 人为访é®éæ³é误æ¥å¿è¯¦æ
ID åï¼è®°å½èªå¨è½åºå¹¶å¯å¨é误æ¥å¿é¡µé¢æ¥çï¼éªè¯ç¨ä¸´æ¶è®°å½å·²ä»æ°æ®åºæ¸
çã |
| | | - æªå¾ï¼`.codex-run/role-permission-dialog-verified.png`ã`.codex-run/error-log-verified.png`ã |
| | | |
| | | **å½åè¿è¡ç¶æï¼2026-10-06ï¼** |
| | | - å端 `http://localhost:8082`ï¼å端 `http://127.0.0.1:8091`ï¼DM8 `127.0.0.1:5236`ï¼Edge CDP `9222`ã |
| | | - ç»å½ `admin / admin123`ï¼æ°æ®åº `SYSDBA/SYSDBA`ï¼ä¸å¡ schema `LX`ã |
| | | - å端æ¥å¿ï¼`.codex-run/backend-20261006-req21-22.out.log`ã`.codex-run/backend-20261006-req21-22.err.log`ã |
| | | |
| | | **æ¥ææ³¨æ** |
| | | - é误æ¥å¿å½åè¦çè¿å
¥ Spring MVC å
¨å±å¼å¸¸å¤ççå¼å¸¸ï¼å¯å¨é¶æ®µå¼å¸¸ã弿¥çº¿ç¨ä¸æªè¿å
¥è¯¥å¤çå¨çå¼å¸¸æä¸ä¼èªå¨è½åºã |
| | | - ä¸è¦æäº¤ `.codex-run`ãOffice éæä»¶æ `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt`ã |
| | | - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå
¬å¸ Git ä»åº `main` 忝ã |
| | |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | |
| | | @Component |
| | | @Order(2) |
| | |
| | | seedButtonMenus(); |
| | | ensureAdminRoleAssignment(); |
| | | ensureMajorConfigMenus(); |
| | | ensureRoleMenuAncestors(); |
| | | seedMajors(); |
| | | seedContractors(); |
| | | seedTrees(); |
| | |
| | | createMenu("report-trend", reports, "æ
éæ°/å¤åå¤ä»¶æ¶è年度è¶å¿", "/reports/trend", null, 1, 50); |
| | | |
| | | Long system = createMenu("system", null, "ç³»ç»ç®¡ç", null, "el-icon-setting", 0, 50); |
| | | createMenu("organization", system, "ç»ç»æºæ", "/system/organization", null, 1, 5); |
| | | createMenu("contractors", system, "æ¿ä¿®åä½ç®¡ç", "/system/contractors", null, 1, 10); |
| | | createMenu("product-tree", system, "产åç»ææ 管ç", "/system/product-tree", null, 1, 20); |
| | | createMenu("base-equipment", system, "设å¤åºç¡åºç®¡ç", "/system/base-equipment", null, 1, 30); |
| | | createMenu("engineering-majors", system, "å·¥ç¨ä¸ä¸é
ç½®", "/system/engineering-majors", null, 1, 35); |
| | | createMenu("team-majors", system, "rk_Téä¸ä¸é
ç½®", "/system/team-majors", null, 1, 36); |
| | | createMenu("roles", system, "è§è²ç®¡ç", "/system/roles", null, 1, 40); |
| | | createMenu("menus", system, "èå管ç", "/system/menus", null, 1, 50); |
| | | createMenu("menu-permissions", system, "èåæé管ç", "/system/menu-permissions", null, 1, 60); |
| | | createMenu("three-member", system, "ä¸åæ§å¶", "/system/three-member", null, 1, 70); |
| | | Long systemConfig = createMenu("system-config", system, "ç³»ç»é
ç½®", null, "el-icon-s-tools", 0, 10); |
| | | createMenu("organization", systemConfig, "ç»ç»æºæ", "/system/organization", null, 1, 10); |
| | | createMenu("contractors", systemConfig, "æ¿ä¿®åä½ç®¡ç", "/system/contractors", null, 1, 20); |
| | | createMenu("product-tree", systemConfig, "产åç»ææ 管ç", "/system/product-tree", null, 1, 30); |
| | | createMenu("base-equipment", systemConfig, "设å¤åºç¡åºç®¡ç", "/system/base-equipment", null, 1, 40); |
| | | createMenu("engineering-majors", systemConfig, "å·¥ç¨ä¸ä¸é
ç½®", "/system/engineering-majors", null, 1, 50); |
| | | createMenu("team-majors", systemConfig, "rk_Téä¸ä¸é
ç½®", "/system/team-majors", null, 1, 60); |
| | | createMenu("three-member", systemConfig, "ä¸åæ§å¶", "/system/three-member", null, 1, 70); |
| | | |
| | | Long permissionConfig = createMenu("permission-config", system, "æéé
ç½®", null, "el-icon-lock", 0, 20); |
| | | createMenu("users", permissionConfig, "ç¨æ·ç®¡ç", "/system/users", null, 1, 10); |
| | | createMenu("roles", permissionConfig, "è§è²ç®¡ç", "/system/roles", null, 1, 20); |
| | | createMenu("menus", permissionConfig, "èå管ç", "/system/menus", null, 1, 30); |
| | | |
| | | Long logConfig = createMenu("log-config", system, "æ¥å¿ç®¡ç", null, "el-icon-document", 0, 30); |
| | | createMenu("login-logs", logConfig, "ç»å½æ¥å¿", "/system/login-logs", null, 1, 10); |
| | | createMenu("operation-logs", logConfig, "æä½æ¥å¿", "/system/operation-logs", null, 1, 20); |
| | | createMenu("error-logs", logConfig, "é误æ¥å¿", "/system/error-logs", null, 1, 30); |
| | | } else { |
| | | for (SysMenu menu : menuMapper.selectList(null)) { |
| | | menuIds.put(menu.getUrl() == null ? "id-" + menu.getId() : menu.getUrl(), menu.getId()); |
| | |
| | | SysMenu system = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getName, "ç³»ç»ç®¡ç") |
| | | .last("LIMIT 1")); |
| | | Long pid = system == null ? 0L : system.getId(); |
| | | ensureMenu(pid, "å·¥ç¨ä¸ä¸é
ç½®", "/system/engineering-majors", 35); |
| | | ensureMenu(pid, "rk_Téä¸ä¸é
ç½®", "/system/team-majors", 36); |
| | | SysMenu config = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getName, "ç³»ç»é
ç½®") |
| | | .eq(SysMenu::getMenuType, 0) |
| | | .last("LIMIT 1")); |
| | | Long pid = config == null ? (system == null ? 0L : system.getId()) : config.getId(); |
| | | ensureMenu(pid, "å·¥ç¨ä¸ä¸é
ç½®", "/system/engineering-majors", 50); |
| | | ensureMenu(pid, "rk_Téä¸ä¸é
ç½®", "/system/team-majors", 60); |
| | | } |
| | | |
| | | private void ensureMenu(Long pid, String name, String url, int sort) { |
| | | if (menuMapper.selectCount(Wrappers.<SysMenu>lambdaQuery().eq(SysMenu::getUrl, url)) > 0) { |
| | | return; |
| | | private Long ensureGroupMenu(Long pid, String name, String icon, int sort) { |
| | | SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getPid, pid) |
| | | .eq(SysMenu::getName, name) |
| | | .eq(SysMenu::getMenuType, 0) |
| | | .last("LIMIT 1")); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (menu == null) { |
| | | menu = new SysMenu(); |
| | | menu.setPid(pid); |
| | | menu.setName(name); |
| | | menu.setIcon(icon); |
| | | menu.setMenuType(0); |
| | | menu.setSortNo(sort); |
| | | menu.setVisible(1); |
| | | menu.setCreateTime(now); |
| | | menu.setUpdateTime(now); |
| | | menuMapper.insert(menu); |
| | | return menu.getId(); |
| | | } |
| | | SysMenu menu = new SysMenu(); |
| | | menu.setPid(pid); |
| | | menu.setName(name); |
| | | menu.setUrl(url); |
| | | menu.setMenuType(1); |
| | | menu.setSortNo(sort); |
| | | menu.setVisible(1); |
| | | menu.setCreateTime(LocalDateTime.now()); |
| | | menu.setUpdateTime(LocalDateTime.now()); |
| | | menuMapper.insert(menu); |
| | | boolean changed = !java.util.Objects.equals(menu.getIcon(), icon) |
| | | || !java.util.Objects.equals(menu.getSortNo(), sort) |
| | | || !java.util.Objects.equals(menu.getVisible(), 1); |
| | | if (changed) { |
| | | menu.setIcon(icon); |
| | | menu.setSortNo(sort); |
| | | menu.setVisible(1); |
| | | menu.setUpdateTime(now); |
| | | menuMapper.updateById(menu); |
| | | } |
| | | return menu.getId(); |
| | | } |
| | | |
| | | private Long ensureMenu(Long pid, String name, String url, int sort) { |
| | | SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getUrl, url).last("LIMIT 1")); |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (menu == null) { |
| | | menu = new SysMenu(); |
| | | menu.setPid(pid); |
| | | menu.setName(name); |
| | | menu.setUrl(url); |
| | | menu.setMenuType(1); |
| | | menu.setSortNo(sort); |
| | | menu.setVisible(1); |
| | | menu.setCreateTime(now); |
| | | menu.setUpdateTime(now); |
| | | menuMapper.insert(menu); |
| | | return menu.getId(); |
| | | } |
| | | boolean changed = !java.util.Objects.equals(menu.getPid(), pid) |
| | | || !java.util.Objects.equals(menu.getName(), name) |
| | | || !java.util.Objects.equals(menu.getSortNo(), sort) |
| | | || !java.util.Objects.equals(menu.getMenuType(), 1) |
| | | || !java.util.Objects.equals(menu.getVisible(), 1); |
| | | if (changed) { |
| | | menu.setPid(pid); |
| | | menu.setName(name); |
| | | menu.setMenuType(1); |
| | | menu.setSortNo(sort); |
| | | menu.setVisible(1); |
| | | menu.setUpdateTime(now); |
| | | menuMapper.updateById(menu); |
| | | } |
| | | return menu.getId(); |
| | | } |
| | | |
| | | private void ensureSystemMenus() { |
| | | SysMenu system = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getName, "\u7cfb\u7edf\u7ba1\u7406").last("LIMIT 1")); |
| | | Long pid = system == null ? 0L : system.getId(); |
| | | ensureMenu(pid, "ç»ç»æºæ", "/system/organization", 5); |
| | | ensureMenu(pid, "\u7528\u6237\u7ba1\u7406", "/system/users", 37); |
| | | ensureMenu(pid, "\u767b\u5f55\u65e5\u5fd7", "/system/login-logs", 65); |
| | | ensureMenu(pid, "\u64cd\u4f5c\u65e5\u5fd7", "/system/operation-logs", 66); |
| | | grantAdminMenus("/system/organization", "/system/users", "/system/login-logs", "/system/operation-logs"); |
| | | Long systemConfig = ensureGroupMenu(pid, "ç³»ç»é
ç½®", "el-icon-s-tools", 10); |
| | | Long permissionConfig = ensureGroupMenu(pid, "æéé
ç½®", "el-icon-lock", 20); |
| | | Long logConfig = ensureGroupMenu(pid, "æ¥å¿ç®¡ç", "el-icon-document", 30); |
| | | |
| | | ensureMenu(systemConfig, "ç»ç»æºæ", "/system/organization", 10); |
| | | ensureMenu(systemConfig, "æ¿ä¿®åä½ç®¡ç", "/system/contractors", 20); |
| | | ensureMenu(systemConfig, "产åç»ææ 管ç", "/system/product-tree", 30); |
| | | ensureMenu(systemConfig, "设å¤åºç¡åºç®¡ç", "/system/base-equipment", 40); |
| | | ensureMenu(systemConfig, "å·¥ç¨ä¸ä¸é
ç½®", "/system/engineering-majors", 50); |
| | | ensureMenu(systemConfig, "rk_Téä¸ä¸é
ç½®", "/system/team-majors", 60); |
| | | ensureMenu(systemConfig, "ä¸åæ§å¶", "/system/three-member", 70); |
| | | |
| | | ensureMenu(permissionConfig, "ç¨æ·ç®¡ç", "/system/users", 10); |
| | | ensureMenu(permissionConfig, "è§è²ç®¡ç", "/system/roles", 20); |
| | | ensureMenu(permissionConfig, "èå管ç", "/system/menus", 30); |
| | | |
| | | ensureMenu(logConfig, "ç»å½æ¥å¿", "/system/login-logs", 10); |
| | | ensureMenu(logConfig, "æä½æ¥å¿", "/system/operation-logs", 20); |
| | | ensureMenu(logConfig, "é误æ¥å¿", "/system/error-logs", 30); |
| | | |
| | | removeMenuByUrl("/system/menu-permissions"); |
| | | grantAdminMenuIds(systemConfig, permissionConfig, logConfig); |
| | | grantAdminMenus("/system/organization", "/system/contractors", "/system/product-tree", |
| | | "/system/base-equipment", "/system/engineering-majors", "/system/team-majors", |
| | | "/system/three-member", "/system/users", "/system/roles", "/system/menus", |
| | | "/system/login-logs", "/system/operation-logs", "/system/error-logs"); |
| | | } |
| | | |
| | | private void grantAdminMenus(String... urls) { |
| | |
| | | } |
| | | } |
| | | |
| | | private void grantAdminMenuIds(Long... menuIds) { |
| | | SysRole adminRole = roleMapper.selectOne(Wrappers.<SysRole>lambdaQuery() |
| | | .eq(SysRole::getCode, "SYS_ADMIN").last("LIMIT 1")); |
| | | if (adminRole == null) { |
| | | return; |
| | | } |
| | | for (Long menuId : menuIds) { |
| | | ensureRoleMenu(adminRole.getId(), menuId); |
| | | ensureTenantMenu(1L, menuId); |
| | | } |
| | | } |
| | | |
| | | private void removeMenuByUrl(String url) { |
| | | SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getUrl, url).last("LIMIT 1")); |
| | | if (menu == null) { |
| | | return; |
| | | } |
| | | List<SysMenu> children = menuMapper.selectList(Wrappers.<SysMenu>lambdaQuery() |
| | | .eq(SysMenu::getPid, menu.getId())); |
| | | for (SysMenu child : children) { |
| | | removeMenuRelations(child.getId()); |
| | | menuMapper.deleteById(child.getId()); |
| | | } |
| | | removeMenuRelations(menu.getId()); |
| | | menuMapper.deleteById(menu.getId()); |
| | | } |
| | | |
| | | private void removeMenuRelations(Long menuId) { |
| | | roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getMenuId, menuId)); |
| | | tenantMenuMapper.delete(Wrappers.<SysTenantMenu>lambdaQuery().eq(SysTenantMenu::getMenuId, menuId)); |
| | | } |
| | | |
| | | private void ensureRoleMenuAncestors() { |
| | | Map<Long, SysMenu> menuById = menuMapper.selectList(null).stream() |
| | | .collect(java.util.stream.Collectors.toMap(SysMenu::getId, item -> item)); |
| | | List<SysRoleMenu> relations = new ArrayList<>(roleMenuMapper.selectList(null)); |
| | | Set<String> existing = relations.stream() |
| | | .map(item -> item.getRoleId() + "-" + item.getMenuId()) |
| | | .collect(java.util.stream.Collectors.toSet()); |
| | | for (SysRoleMenu relation : relations) { |
| | | SysMenu menu = menuById.get(relation.getMenuId()); |
| | | Long cursor = menu == null ? null : menu.getPid(); |
| | | int guard = 0; |
| | | while (cursor != null && cursor != 0L && guard++ < 32) { |
| | | String key = relation.getRoleId() + "-" + cursor; |
| | | if (existing.add(key)) { |
| | | ensureRoleMenu(relation.getRoleId(), cursor); |
| | | } |
| | | SysMenu parent = menuById.get(cursor); |
| | | cursor = parent == null ? null : parent.getPid(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Materializes the declarative button catalog as MENU_TYPE=2 rows. |
| | | * Existing rows are left untouched so manual menu edits are preserved. |
| | |
| | | "T_FAULT", "T_ENGINEERING_MAJOR", "T_TEAM_MAJOR", "T_CONTRACTOR", |
| | | "T_BASE_EQUIPMENT", "T_PRODUCT_TREE", "T_WORK_ORDER", "T_COMPLETION_INFO", |
| | | "T_ACCEPTANCE", "T_SYS_MENU", "T_SYS_ROLE", "T_SYS_ROLE_MENU", |
| | | "T_SYS_TENANT_MENU", "T_SYS_ROLE_USER", "T_SYS_LOGIN_LOG", "T_SYS_OPERATION_LOG" |
| | | "T_SYS_TENANT_MENU", "T_SYS_ROLE_USER", "T_SYS_LOGIN_LOG", "T_SYS_OPERATION_LOG", |
| | | "T_SYS_ERROR_LOG" |
| | | }; |
| | | private final JdbcTemplate jdbcTemplate; |
| | | |
| | |
| | | ensureIndex("IX_T_COMPLETION_WORK_ORDER", "CREATE INDEX LX.IX_T_COMPLETION_WORK_ORDER ON LX.T_COMPLETION_INFO(WORK_ORDER_ID)"); |
| | | ensureIndex("IX_T_SYS_LOGIN_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_LOGIN_LOG_TIME ON LX.T_SYS_LOGIN_LOG(LOGIN_TIME)"); |
| | | ensureIndex("IX_T_SYS_OPERATION_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_OPERATION_LOG_TIME ON LX.T_SYS_OPERATION_LOG(CREATE_TIME)"); |
| | | ensureIndex("IX_T_SYS_ERROR_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_ERROR_LOG_TIME ON LX.T_SYS_ERROR_LOG(ERROR_TIME)"); |
| | | ensureIndex("IX_T_SYS_ROLE_USER_USER", "CREATE INDEX LX.IX_T_SYS_ROLE_USER_USER ON LX.T_SYS_ROLE_USER(USER_ID)"); |
| | | } |
| | | |
| | |
| | | ddl.put("T_SYS_ROLE_USER", "CREATE TABLE LX.T_SYS_ROLE_USER (ID BIGINT NOT NULL PRIMARY KEY, ROLE_ID BIGINT, USER_ID BIGINT, CREATE_TIME TIMESTAMP)"); |
| | | ddl.put("T_SYS_LOGIN_LOG", "CREATE TABLE LX.T_SYS_LOGIN_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), DEPT_NAME VARCHAR(120), LOGIN_IP VARCHAR(200), STATUS VARCHAR(20), MESSAGE VARCHAR(500), LOGIN_TIME TIMESTAMP)"); |
| | | ddl.put("T_SYS_OPERATION_LOG", "CREATE TABLE LX.T_SYS_OPERATION_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), OPERATION_TYPE VARCHAR(40), TARGET_TYPE VARCHAR(100), TARGET_ID VARCHAR(80), DESCRIPTION VARCHAR(500), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(300), RESULT VARCHAR(20), CREATE_TIME TIMESTAMP)"); |
| | | ddl.put("T_SYS_ERROR_LOG", "CREATE TABLE LX.T_SYS_ERROR_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(500), EXCEPTION_TYPE VARCHAR(300), MESSAGE VARCHAR(2000), STACK_TRACE CLOB, ERROR_TIME TIMESTAMP)"); |
| | | return ddl; |
| | | } |
| | | } |
| | |
| | | package com.emergencyrepair.common; |
| | | |
| | | import com.emergencyrepair.system.log.ErrorLogRecorder; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.web.bind.annotation.ExceptionHandler; |
| | | import org.springframework.web.bind.annotation.RestControllerAdvice; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | |
| | | @RestControllerAdvice |
| | | @RequiredArgsConstructor |
| | | public class GlobalExceptionHandler { |
| | | private final ErrorLogRecorder errorLogRecorder; |
| | | |
| | | @ExceptionHandler(BusinessException.class) |
| | | public Result<Void> handleBusiness(BusinessException exception) { |
| | | return Result.error(exception.getMessage()); |
| | | } |
| | | |
| | | @ExceptionHandler(Exception.class) |
| | | public Result<Void> handleOther(Exception exception) { |
| | | public Result<Void> handleOther(Exception exception, HttpServletRequest request) { |
| | | errorLogRecorder.record(exception, request); |
| | | exception.printStackTrace(); |
| | | return Result.error(exception.getMessage() == null ? "ç³»ç»å¼å¸¸" : exception.getMessage()); |
| | | } |
| | |
| | | import com.emergencyrepair.business.entity.TeamMajor; |
| | | import com.emergencyrepair.common.Result; |
| | | import com.emergencyrepair.system.entity.SysMenu; |
| | | import com.emergencyrepair.system.entity.SysErrorLog; |
| | | import com.emergencyrepair.system.entity.SysLoginLog; |
| | | import com.emergencyrepair.system.entity.SysOperationLog; |
| | | import com.emergencyrepair.system.entity.SysRole; |
| | |
| | | return Result.ok(systemService.operationLogPage(current, size, keyword, module, operationType, startDate, endDate)); |
| | | } |
| | | |
| | | @GetMapping("/error-logs") |
| | | public Result<Page<SysErrorLog>> errorLogs(@RequestParam(defaultValue = "1") int current, |
| | | @RequestParam(defaultValue = "20") int size, |
| | | @RequestParam(required = false) String keyword, |
| | | @RequestParam(required = false) String module, |
| | | @RequestParam(required = false) String exceptionType, |
| | | @RequestParam(required = false) String startDate, |
| | | @RequestParam(required = false) String endDate) { |
| | | return Result.ok(systemService.errorLogPage(current, size, keyword, module, exceptionType, startDate, endDate)); |
| | | } |
| | | |
| | | @GetMapping("/error-logs/{id}") |
| | | public Result<SysErrorLog> errorLog(@PathVariable Long id) { |
| | | return Result.ok(systemService.errorLog(id)); |
| | | } |
| | | |
| | | @PostMapping("/menu") |
| | | public Result<SysMenu> createMenu(@RequestBody SysMenu menu) { |
| | | menu.setId(null); |
| New file |
| | |
| | | package com.emergencyrepair.system.entity; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.emergencyrepair.common.entity.BaseAuditEntity; |
| | | import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
| | | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("LX.T_SYS_ERROR_LOG") |
| | | public class SysErrorLog extends BaseAuditEntity { |
| | | @TableId(value = "ID", type = IdType.ASSIGN_ID) |
| | | @JsonSerialize(using = ToStringSerializer.class) |
| | | private Long id; |
| | | @JsonSerialize(using = ToStringSerializer.class) |
| | | @TableField("USER_ID") private Long userId; |
| | | @TableField("USER_NAME") private String userName; |
| | | @TableField("NICK_NAME") private String nickName; |
| | | @TableField("MODULE") private String module; |
| | | @TableField("REQUEST_METHOD") private String requestMethod; |
| | | @TableField("REQUEST_URI") private String requestUri; |
| | | @TableField("EXCEPTION_TYPE") private String exceptionType; |
| | | @TableField("MESSAGE") private String message; |
| | | @TableField("STACK_TRACE") private String stackTrace; |
| | | @TableField("ERROR_TIME") private LocalDateTime errorTime; |
| | | } |
| New file |
| | |
| | | package com.emergencyrepair.system.log; |
| | | |
| | | import com.emergencyrepair.common.audit.CurrentUserContext; |
| | | import com.emergencyrepair.system.entity.SysErrorLog; |
| | | import com.emergencyrepair.system.mapper.SysErrorLogMapper; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.jdbc.core.JdbcTemplate; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.io.PrintWriter; |
| | | import java.io.StringWriter; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Persists unhandled server exceptions so administrators can inspect runtime bugs. |
| | | */ |
| | | @Component |
| | | @RequiredArgsConstructor |
| | | public class ErrorLogRecorder { |
| | | private static final int MAX_MESSAGE_LENGTH = 2000; |
| | | private static final int MAX_STACK_LENGTH = 60000; |
| | | |
| | | private final SysErrorLogMapper errorLogMapper; |
| | | private final JdbcTemplate jdbcTemplate; |
| | | |
| | | public void record(Exception exception, HttpServletRequest request) { |
| | | if (exception == null) { |
| | | return; |
| | | } |
| | | try { |
| | | String username = CurrentUserContext.get(); |
| | | Long userId = resolveUserId(username); |
| | | SysErrorLog log = new SysErrorLog(); |
| | | log.setUserId(userId); |
| | | log.setUserName(username); |
| | | log.setNickName(resolveNickName(userId)); |
| | | log.setModule(resolveModule(request)); |
| | | log.setRequestMethod(request == null ? null : limit(request.getMethod(), 10)); |
| | | log.setRequestUri(request == null ? null : limit(request.getRequestURI(), 500)); |
| | | log.setExceptionType(limit(exception.getClass().getName(), 300)); |
| | | log.setMessage(limit(resolveMessage(exception), MAX_MESSAGE_LENGTH)); |
| | | log.setStackTrace(limit(stackTrace(exception), MAX_STACK_LENGTH)); |
| | | log.setErrorTime(LocalDateTime.now()); |
| | | errorLogMapper.insert(log); |
| | | } catch (Exception ignored) { |
| | | // Error logging must never interfere with the original error response. |
| | | } |
| | | } |
| | | |
| | | private Long resolveUserId(String username) { |
| | | if (!StringUtils.hasText(username)) { |
| | | return null; |
| | | } |
| | | List<Long> ids = jdbcTemplate.query( |
| | | "SELECT USER_ID FROM CSICZB_ZB.SYS_USER WHERE USER_NAME = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)", |
| | | new Object[]{username}, (rs, rowNum) -> rs.getLong(1)); |
| | | return ids.isEmpty() ? null : ids.get(0); |
| | | } |
| | | |
| | | private String resolveNickName(Long userId) { |
| | | if (userId == null) { |
| | | return null; |
| | | } |
| | | List<String> names = jdbcTemplate.query( |
| | | "SELECT NICK_NAME FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ?", |
| | | new Object[]{userId}, (rs, rowNum) -> rs.getString(1)); |
| | | return names.isEmpty() ? null : names.get(0); |
| | | } |
| | | |
| | | private String resolveMessage(Exception exception) { |
| | | String message = exception.getMessage(); |
| | | return StringUtils.hasText(message) ? message : exception.getClass().getSimpleName(); |
| | | } |
| | | |
| | | private String stackTrace(Exception exception) { |
| | | StringWriter writer = new StringWriter(); |
| | | exception.printStackTrace(new PrintWriter(writer)); |
| | | return writer.toString(); |
| | | } |
| | | |
| | | private String resolveModule(HttpServletRequest request) { |
| | | if (request == null || !StringUtils.hasText(request.getRequestURI())) { |
| | | return "å
¶ä»"; |
| | | } |
| | | String uri = request.getRequestURI(); |
| | | if (uri.startsWith("/api/faults") || uri.startsWith("/api/fault")) return "æ
é管ç"; |
| | | if (uri.startsWith("/api/work-orders") || uri.startsWith("/api/completion")) return "å®å·¥ç®¡ç"; |
| | | if (uri.startsWith("/api/acceptances")) return "å®å·¥éªæ¶"; |
| | | if (uri.startsWith("/api/reports")) return "ç»è®¡æ¥è¡¨"; |
| | | if (uri.startsWith("/api/system/users")) return "ç¨æ·ç®¡ç"; |
| | | if (uri.startsWith("/api/system/roles")) return "è§è²ç®¡ç"; |
| | | if (uri.startsWith("/api/system/menu")) return "èå管ç"; |
| | | if (uri.startsWith("/api/system/contractors")) return "æ¿ä¿®åä½ç®¡ç"; |
| | | if (uri.startsWith("/api/system")) return "ç³»ç»ç®¡ç"; |
| | | return "å
¶ä»"; |
| | | } |
| | | |
| | | private String limit(String value, int maxLength) { |
| | | if (value == null || value.length() <= maxLength) { |
| | | return value; |
| | | } |
| | | return value.substring(0, maxLength); |
| | | } |
| | | } |
| New file |
| | |
| | | package com.emergencyrepair.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.emergencyrepair.system.entity.SysErrorLog; |
| | | |
| | | public interface SysErrorLogMapper extends BaseMapper<SysErrorLog> { |
| | | } |
| | |
| | | put("/system/engineering-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664")); |
| | | put("/system/team-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664")); |
| | | put("/system/users", def("system:users:query", "\u67e5\u8be2"), def("system:users:assign", "\u89d2\u8272\u6388\u6743")); |
| | | put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664")); |
| | | put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"), def("system:roles:assign", "\u7528\u6237\u8bbe\u7f6e")); |
| | | put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"), def("system:roles:assign", "\u7528\u6237\u8bbe\u7f6e"), def("system:roles:permission", "\u6743\u9650\u8bbe\u7f6e")); |
| | | put("/system/menus", def("system:menus:add", "\u65b0\u589e"), def("system:menus:edit", "\u7f16\u8f91"), def("system:menus:delete", "\u5220\u9664"), def("system:menus:buttons", "\u83dc\u5355\u6309\u94ae\u540c\u6b65")); |
| | | put("/system/menu-permissions", def("system:menu-permissions:save", "\u4fdd\u5b58\u6388\u6743")); |
| | | put("/system/login-logs", def("system:login-logs:query", "\u67e5\u8be2"), def("system:login-logs:export", "\u5bfc\u51fa")); |
| | | put("/system/operation-logs", def("system:operation-logs:query", "\u67e5\u8be2"), def("system:operation-logs:export", "\u5bfc\u51fa")); |
| | | put("/system/error-logs", def("system:error-logs:query", "\u67e5\u8be2"), def("system:error-logs:detail", "\u8be6\u60c5")); |
| | | } |
| | | |
| | | private static void put(String url, ButtonDefinition... definitions) { |
| | |
| | | import com.emergencyrepair.business.mapper.TeamMajorMapper; |
| | | import com.emergencyrepair.common.BusinessException; |
| | | import com.emergencyrepair.system.entity.SysMenu; |
| | | import com.emergencyrepair.system.entity.SysErrorLog; |
| | | import com.emergencyrepair.system.entity.SysLoginLog; |
| | | import com.emergencyrepair.system.entity.SysOperationLog; |
| | | import com.emergencyrepair.system.entity.SysRole; |
| | |
| | | import com.emergencyrepair.system.entity.SysRoleUser; |
| | | import com.emergencyrepair.system.entity.SysTenantMenu; |
| | | import com.emergencyrepair.system.mapper.SysMenuMapper; |
| | | import com.emergencyrepair.system.mapper.SysErrorLogMapper; |
| | | import com.emergencyrepair.system.mapper.SysLoginLogMapper; |
| | | import com.emergencyrepair.system.mapper.SysOperationLogMapper; |
| | | import com.emergencyrepair.system.mapper.SysRoleMapper; |
| | |
| | | private final SysRoleUserMapper roleUserMapper; |
| | | private final SysLoginLogMapper loginLogMapper; |
| | | private final SysOperationLogMapper operationLogMapper; |
| | | private final SysErrorLogMapper errorLogMapper; |
| | | private final ButtonPermissionCatalog buttonPermissionCatalog; |
| | | private final JdbcTemplate jdbcTemplate; |
| | | private final EngineeringMajorMapper engineeringMajorMapper; |
| | |
| | | } |
| | | } |
| | | final Set<Long> allowedIds = allowed; |
| | | Set<Long> visibleIds = new LinkedHashSet<>(expandWithAncestors(new ArrayList<>(allowed))); |
| | | List<SysMenu> navigable = listMenus().stream() |
| | | .filter(menu -> menu.getMenuType() == null || menu.getMenuType() != 2) |
| | | .filter(menu -> allowedIds.contains(menu.getId())) |
| | | .filter(menu -> visibleIds.contains(menu.getId())) |
| | | .collect(Collectors.toList()); |
| | | return buildTree(navigable); |
| | | } |
| | |
| | | return operationLogMapper.selectPage(page, wrapper); |
| | | } |
| | | |
| | | public Page<SysErrorLog> errorLogPage(int current, int size, String keyword, String module, String exceptionType, String startDate, String endDate) { |
| | | Page<SysErrorLog> page = new Page<>(current <= 0 ? 1 : current, size <= 0 ? 20 : size); |
| | | LambdaQueryWrapper<SysErrorLog> wrapper = Wrappers.lambdaQuery(); |
| | | wrapper.select(SysErrorLog::getId, SysErrorLog::getUserId, SysErrorLog::getUserName, |
| | | SysErrorLog::getNickName, SysErrorLog::getModule, SysErrorLog::getRequestMethod, |
| | | SysErrorLog::getRequestUri, SysErrorLog::getExceptionType, SysErrorLog::getMessage, |
| | | SysErrorLog::getErrorTime); |
| | | if (StringUtils.hasText(keyword)) { |
| | | String value = keyword.trim(); |
| | | wrapper.and(item -> item.like(SysErrorLog::getUserName, value) |
| | | .or().like(SysErrorLog::getNickName, value) |
| | | .or().like(SysErrorLog::getRequestUri, value) |
| | | .or().like(SysErrorLog::getExceptionType, value) |
| | | .or().like(SysErrorLog::getMessage, value)); |
| | | } |
| | | if (StringUtils.hasText(module)) { |
| | | wrapper.eq(SysErrorLog::getModule, module.trim()); |
| | | } |
| | | if (StringUtils.hasText(exceptionType)) { |
| | | wrapper.like(SysErrorLog::getExceptionType, exceptionType.trim()); |
| | | } |
| | | LocalDateTime start = parseStart(startDate); |
| | | LocalDateTime end = parseEnd(endDate); |
| | | if (start != null) { |
| | | wrapper.ge(SysErrorLog::getErrorTime, start); |
| | | } |
| | | if (end != null) { |
| | | wrapper.le(SysErrorLog::getErrorTime, end); |
| | | } |
| | | wrapper.orderByDesc(SysErrorLog::getErrorTime); |
| | | return errorLogMapper.selectPage(page, wrapper); |
| | | } |
| | | |
| | | public SysErrorLog errorLog(Long id) { |
| | | SysErrorLog log = errorLogMapper.selectById(id); |
| | | if (log == null) { |
| | | throw new BusinessException("é误æ¥å¿ä¸åå¨"); |
| | | } |
| | | return log; |
| | | } |
| | | |
| | | public Long resolveUserId(String username) { |
| | | if (!StringUtils.hasText(username)) { |
| | | return null; |
| | |
| | | { path: 'system/roles', component: () => import('@/views/system/RoleManage.vue'), meta: { title: 'è§è²ç®¡ç', cacheName: 'RoleManage' } }, |
| | | { path: 'system/login-logs', component: () => import('@/views/system/LoginLogManage.vue'), meta: { title: 'ç»å½æ¥å¿', cacheName: 'LoginLogManage' } }, |
| | | { path: 'system/operation-logs', component: () => import('@/views/system/OperationLogManage.vue'), meta: { title: 'æä½æ¥å¿', cacheName: 'OperationLogManage' } }, |
| | | { path: 'system/error-logs', component: () => import('@/views/system/ErrorLogManage.vue'), meta: { title: 'é误æ¥å¿', cacheName: 'ErrorLogManage' } }, |
| | | { path: 'system/menus', component: () => import('@/views/system/MenuManage.vue'), meta: { title: 'èå管ç', cacheName: 'MenuManage' } }, |
| | | { path: 'system/menu-permissions', component: () => import('@/views/system/MenuPermission.vue'), meta: { title: 'èåæé', cacheName: 'MenuPermission' } }, |
| | | { path: 'system/three-member', component: () => import('@/views/system/ThreeMember.vue'), meta: { title: 'ä¸å管ç', cacheName: 'ThreeMember' } } |
| | | ] |
| | | } |
| New file |
| | |
| | | <template> |
| | | <div class="page"> |
| | | <div class="page-title"> |
| | | <div><h2>é误æ¥å¿</h2><p>è®°å½ç¨åºè¿è¡ä¸çæªå¤çå¼å¸¸ï¼ä¾¿äºå®ä½åä¿®å¤ç¼ºé·</p></div> |
| | | </div> |
| | | <div class="table-panel"> |
| | | <div class="toolbar"> |
| | | <el-input v-model="filters.keyword" clearable placeholder="è´¦æ·å / å§å / 请æ±å°å / å¼å¸¸ä¿¡æ¯" style="width:280px" @keyup.enter.native="search"></el-input> |
| | | <el-select v-model="filters.module" clearable placeholder="模å" style="width:150px"> |
| | | <el-option v-for="item in modules" :key="item" :label="item" :value="item"></el-option> |
| | | </el-select> |
| | | <el-input v-model="filters.exceptionType" clearable placeholder="å¼å¸¸ç±»å" style="width:220px" @keyup.enter.native="search"></el-input> |
| | | <el-date-picker |
| | | v-model="filters.dates" |
| | | type="daterange" |
| | | value-format="yyyy-MM-dd" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | style="width:260px" |
| | | ></el-date-picker> |
| | | <el-button type="primary" icon="el-icon-search" v-permission="'system:error-logs:query'" @click="search">æ¥è¯¢</el-button> |
| | | <el-button icon="el-icon-refresh-left" @click="reset">éç½®</el-button> |
| | | </div> |
| | | <el-table v-loading="loading" :data="rows" border stripe height="calc(100vh - 250px)"> |
| | | <el-table-column prop="errorTime" label="åçæ¶é´" width="180"></el-table-column> |
| | | <el-table-column prop="userName" label="è´¦æ·å" width="120"></el-table-column> |
| | | <el-table-column prop="nickName" label="å§å" width="110"></el-table-column> |
| | | <el-table-column prop="module" label="模å" width="120"></el-table-column> |
| | | <el-table-column prop="requestMethod" label="æ¹å¼" width="75" align="center"></el-table-column> |
| | | <el-table-column prop="requestUri" label="请æ±å°å" min-width="230" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="exceptionType" label="å¼å¸¸ç±»å" min-width="220" show-overflow-tooltip></el-table-column> |
| | | <el-table-column prop="message" label="å¼å¸¸ä¿¡æ¯" min-width="280" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="æä½" width="80" fixed="right"> |
| | | <template slot-scope="{ row }"> |
| | | <el-button type="text" v-permission="'system:error-logs:detail'" @click="openDetail(row)">详æ
</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <el-pagination |
| | | class="pager" |
| | | background |
| | | layout="total, sizes, prev, pager, next" |
| | | :total="total" |
| | | :current-page="current" |
| | | :page-size="size" |
| | | :page-sizes="[20, 50, 100]" |
| | | @current-change="handleCurrentChange" |
| | | @size-change="handleSizeChange" |
| | | ></el-pagination> |
| | | </div> |
| | | |
| | | <el-dialog title="é误详æ
" :visible.sync="detailVisible" width="900px" append-to-body> |
| | | <div v-loading="detailLoading"> |
| | | <div class="detail-meta"> |
| | | <span><b>åçæ¶é´ï¼</b>{{ detail.errorTime }}</span> |
| | | <span><b>æä½äººï¼</b>{{ detail.userName || '-' }}{{ detail.nickName ? ' / ' + detail.nickName : '' }}</span> |
| | | <span><b>模åï¼</b>{{ detail.module || '-' }}</span> |
| | | </div> |
| | | <div class="detail-meta"> |
| | | <span><b>请æ±ï¼</b>{{ (detail.requestMethod || '') + ' ' + (detail.requestUri || '') }}</span> |
| | | </div> |
| | | <div class="detail-meta"> |
| | | <span><b>å¼å¸¸ç±»åï¼</b>{{ detail.exceptionType }}</span> |
| | | </div> |
| | | <div class="detail-meta"> |
| | | <span><b>å¼å¸¸ä¿¡æ¯ï¼</b>{{ detail.message }}</span> |
| | | </div> |
| | | <pre class="stack-trace">{{ detail.stackTrace || 'æ å æ ä¿¡æ¯' }}</pre> |
| | | </div> |
| | | <template slot="footer"><el-button @click="detailVisible = false">å
³é</el-button></template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import api from '@/api' |
| | | export default { |
| | | name: 'ErrorLogManage', |
| | | data() { |
| | | return { |
| | | rows: [], |
| | | total: 0, |
| | | current: 1, |
| | | size: 20, |
| | | loading: false, |
| | | detailVisible: false, |
| | | detailLoading: false, |
| | | detail: {}, |
| | | modules: ['æ
é管ç', 'å®å·¥ç®¡ç', 'å®å·¥éªæ¶', 'ç»è®¡æ¥è¡¨', 'ç¨æ·ç®¡ç', 'è§è²ç®¡ç', 'èå管ç', 'æ¿ä¿®åä½ç®¡ç', 'ç³»ç»ç®¡ç', 'å
¶ä»'], |
| | | filters: { keyword: '', module: '', exceptionType: '', dates: [] } |
| | | } |
| | | }, |
| | | created() { this.load() }, |
| | | methods: { |
| | | load() { |
| | | this.loading = true |
| | | const dates = this.filters.dates || [] |
| | | api.get('/system/error-logs', { |
| | | params: { |
| | | keyword: this.filters.keyword || undefined, |
| | | module: this.filters.module || undefined, |
| | | exceptionType: this.filters.exceptionType || undefined, |
| | | startDate: dates[0] || undefined, |
| | | endDate: dates[1] || undefined, |
| | | current: this.current, |
| | | size: this.size |
| | | } |
| | | }).then(res => { |
| | | const data = res.data || {} |
| | | this.rows = data.records || [] |
| | | this.total = data.total || 0 |
| | | }).finally(() => { this.loading = false }) |
| | | }, |
| | | search() { this.current = 1; this.load() }, |
| | | reset() { this.filters = { keyword: '', module: '', exceptionType: '', dates: [] }; this.search() }, |
| | | openDetail(row) { |
| | | this.detailLoading = true |
| | | this.detail = {} |
| | | this.detailVisible = true |
| | | api.get('/system/error-logs/' + row.id).then(res => { |
| | | this.detail = res.data || {} |
| | | }).finally(() => { this.detailLoading = false }) |
| | | }, |
| | | handleCurrentChange(page) { this.current = page; this.load() }, |
| | | handleSizeChange(size) { this.size = size; this.current = 1; this.load() } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; } |
| | | .pager { margin-top: 12px; text-align: right; } |
| | | .detail-meta { margin-bottom: 8px; color: #42566b; line-height: 22px; word-break: break-all; } |
| | | .detail-meta b { color: #26394d; } |
| | | .stack-trace { |
| | | max-height: 520px; |
| | | margin: 12px 0 0; |
| | | padding: 12px; |
| | | border: 1px solid #dce4ec; |
| | | border-radius: 4px; |
| | | color: #30445a; |
| | | background: #f7f9fb; |
| | | font-family: Consolas, "Courier New", monospace; |
| | | font-size: 12px; |
| | | line-height: 18px; |
| | | white-space: pre-wrap; |
| | | word-break: break-all; |
| | | overflow: auto; |
| | | } |
| | | </style> |
| | |
| | | </el-dialog> |
| | | |
| | | <el-dialog :title="'页颿é®é
ç½® - ' + (buttonMenu.name || '')" :visible.sync="buttonDialogVisible" width="620px" append-to-body> |
| | | <p class="dialog-tip">ä¸åæé®æ¥èªå端æé®ç®å½ï¼å¾éåèªå¨çææé®ç±»åèåï¼å¯å¨èåæé管ç䏿æç»è§è²ã</p> |
| | | <p class="dialog-tip">ä¸åæé®æ¥èªå端æé®ç®å½ï¼å¾éåèªå¨çææé®ç±»åèåï¼å¯å¨è§è²ç®¡ççæéè®¾ç½®ä¸ææç»è§è²ã</p> |
| | | <el-checkbox-group v-model="buttonSelected" class="button-list"> |
| | | <el-checkbox v-for="item in buttonAvailable" :key="item.code" :label="item.code" class="button-item"> |
| | | <span class="button-name">{{ item.name }}</span> |
| | |
| | | <template> |
| | | <div class="page"> |
| | | <div class="page-title"> |
| | | <div><h2>è§è²ç®¡ç</h2><p>ç»´æ¤è§è²ä¿¡æ¯ï¼å¹¶å¯æè§è²æ¹é设置æå±ç¨æ·</p></div> |
| | | <div><h2>è§è²ç®¡ç</h2><p>ç»´æ¤è§è²ä¿¡æ¯ï¼å¹¶å¯æè§è²è®¾ç½®æå±ç¨æ·åèåæé®æé</p></div> |
| | | <el-button type="primary" icon="el-icon-plus" v-permission="'system:roles:add'" @click="openCreate">æ°å¢è§è²</el-button> |
| | | </div> |
| | | <div class="table-panel"> |
| | |
| | | <el-table-column prop="code" label="è§è²ç¼ç " width="170"></el-table-column> |
| | | <el-table-column prop="roleType" label="è§è²ç±»å" width="170"></el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" min-width="220" show-overflow-tooltip></el-table-column> |
| | | <el-table-column label="æä½" width="220" fixed="right"> |
| | | <el-table-column label="æä½" width="300" fixed="right"> |
| | | <template slot-scope="{ row }"> |
| | | <el-button type="text" v-permission="'system:roles:assign'" @click="openUserSetting(row)">ç¨æ·è®¾ç½®</el-button> |
| | | <el-button type="text" v-permission="'system:roles:permission'" @click="openPermissionSetting(row)">æé设置</el-button> |
| | | <el-button type="text" v-permission="'system:roles:edit'" @click="openEdit(row)">ç¼è¾</el-button> |
| | | <el-button type="text" class="danger-link" v-permission="'system:roles:delete'" @click="remove(row)">å é¤</el-button> |
| | | </template> |
| | |
| | | <el-button type="primary" :loading="userSaving" v-permission="'system:roles:assign'" @click="saveRoleUsers">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <role-permission-dialog :visible.sync="permissionDialogVisible" :role="currentRole"></role-permission-dialog> |
| | | </div> |
| | | </template> |
| | | <script> |
| | | import api from '@/api' |
| | | import RolePermissionDialog from './RolePermissionDialog.vue' |
| | | export default { |
| | | name: 'RoleManage', |
| | | components: { RolePermissionDialog }, |
| | | data() { |
| | | return { |
| | | rows: [], |
| | | dialogVisible: false, |
| | | permissionDialogVisible: false, |
| | | saving: false, |
| | | userDialogVisible: false, |
| | | userLoading: false, |
| | |
| | | this.$nextTick(this.restoreUserSelection) |
| | | }).finally(() => { this.userLoading = false }) |
| | | }, |
| | | openPermissionSetting(row) { |
| | | this.currentRole = { ...row } |
| | | this.permissionDialogVisible = true |
| | | }, |
| | | restoreUserSelection() { |
| | | const table = this.$refs.roleUserTable |
| | | if (!table) return |
| New file |
| | |
| | | <template> |
| | | <el-dialog |
| | | title="è§è²æé设置" |
| | | :visible.sync="dialogVisible" |
| | | width="860px" |
| | | append-to-body |
| | | :close-on-click-modal="false" |
| | | > |
| | | <div class="permission-head"> |
| | | <div> |
| | | <span class="role-label">å½åè§è²</span> |
| | | <strong>{{ role.name }}</strong> |
| | | <span class="role-code">{{ role.code }}</span> |
| | | </div> |
| | | <span class="selected-total">å·²éæé {{ selectedRoleIds.length }} 个</span> |
| | | </div> |
| | | <div class="permission-toolbar"> |
| | | <el-checkbox v-model="checkAll" @change="toggleAll">å
¨é</el-checkbox> |
| | | <span class="permission-tip">èååæé®æéåå¯å¾éï¼ä¿åå忢è§è²ä¼èªå¨åæ¾ã</span> |
| | | </div> |
| | | <div v-loading="loading" class="permission-tree"> |
| | | <el-tree |
| | | ref="permissionTree" |
| | | :data="menus" |
| | | node-key="id" |
| | | show-checkbox |
| | | default-expand-all |
| | | :render-content="renderContent" |
| | | :props="{ label: 'name', children: 'children' }" |
| | | @check="syncCheckAllState" |
| | | ></el-tree> |
| | | </div> |
| | | <template slot="footer"> |
| | | <el-button @click="dialogVisible = false">åæ¶</el-button> |
| | | <el-button type="primary" :loading="saving" v-permission="'system:roles:permission'" @click="save">ä¿å</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script> |
| | | import api from '@/api' |
| | | export default { |
| | | name: 'RolePermissionDialog', |
| | | props: { |
| | | visible: { type: Boolean, default: false }, |
| | | role: { type: Object, default: () => ({}) } |
| | | }, |
| | | data() { |
| | | return { |
| | | menus: [], |
| | | flatMenus: [], |
| | | loading: false, |
| | | saving: false, |
| | | checkAll: false, |
| | | selectedRoleIds: [] |
| | | } |
| | | }, |
| | | computed: { |
| | | dialogVisible: { |
| | | get() { return this.visible }, |
| | | set(value) { this.$emit('update:visible', value) } |
| | | }, |
| | | allMenuIds() { |
| | | return this.flatMenus.map(item => String(item.id)) |
| | | } |
| | | }, |
| | | watch: { |
| | | visible(value) { |
| | | if (value) this.open() |
| | | }, |
| | | }, |
| | | methods: { |
| | | open() { |
| | | if (!this.role || !this.role.id) return |
| | | this.loading = true |
| | | this.checkAll = false |
| | | this.selectedRoleIds = [] |
| | | Promise.all([ |
| | | api.get('/system/menu/tree'), |
| | | api.get('/system/menu/list'), |
| | | api.get('/system/menu-permissions', { params: { roleId: this.role.id } }) |
| | | ]).then(([tree, flat, permissions]) => { |
| | | this.menus = tree.data || [] |
| | | this.flatMenus = flat.data || [] |
| | | const data = permissions.data || {} |
| | | this.selectedRoleIds = (data.roleMenuIds || []).map(String) |
| | | this.$nextTick(() => { |
| | | if (!this.$refs.permissionTree) return |
| | | this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds)) |
| | | this.syncCheckAllState() |
| | | }) |
| | | }).finally(() => { this.loading = false }) |
| | | }, |
| | | renderContent(h, { data }) { |
| | | const label = data.menuType === 2 ? data.name + ' [æé® ' + (data.permissions || '') + ']' : data.name |
| | | return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label) |
| | | }, |
| | | displayCheckedKeys(ids) { |
| | | const selected = new Set((ids || []).map(String)) |
| | | const children = {} |
| | | const menuIds = new Set(this.flatMenus.map(item => String(item.id))) |
| | | this.flatMenus.forEach(item => { |
| | | const id = String(item.id) |
| | | const pid = item.pid == null ? '0' : String(item.pid) |
| | | if (!children[pid]) children[pid] = [] |
| | | children[pid].push(id) |
| | | }) |
| | | const result = [] |
| | | const collectFullySelected = id => { |
| | | if (!selected.has(id)) return false |
| | | const childIds = children[id] || [] |
| | | if (!childIds.length) { |
| | | result.push(id) |
| | | return true |
| | | } |
| | | const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean) |
| | | if (allChildrenSelected) result.push(id) |
| | | return allChildrenSelected |
| | | } |
| | | this.flatMenus |
| | | .filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid))) |
| | | .forEach(item => collectFullySelected(String(item.id))) |
| | | return result |
| | | }, |
| | | checkedMenuIds() { |
| | | const tree = this.$refs.permissionTree |
| | | if (!tree) return [] |
| | | return [...new Set([...(tree.getCheckedKeys(false) || []), ...(tree.getHalfCheckedKeys() || [])].map(String))] |
| | | }, |
| | | syncCheckAllState() { |
| | | this.checkAll = this.checkedMenuIds().length === this.allMenuIds.length |
| | | }, |
| | | toggleAll() { |
| | | if (!this.$refs.permissionTree) return |
| | | this.$refs.permissionTree.setCheckedKeys(this.checkAll ? this.allMenuIds : []) |
| | | this.$nextTick(this.syncCheckAllState) |
| | | }, |
| | | save() { |
| | | const roleMenuIds = this.checkedMenuIds() |
| | | this.saving = true |
| | | api.post('/system/menu-permissions', { roleId: this.role.id, roleMenuIds }).then(() => { |
| | | this.selectedRoleIds = roleMenuIds |
| | | this.$message.success('è§è²æéå·²ä¿å') |
| | | this.$emit('saved') |
| | | this.dialogVisible = false |
| | | }).finally(() => { this.saving = false }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .permission-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | color: #31465d; |
| | | } |
| | | .role-label { |
| | | margin-right: 10px; |
| | | color: #7b8b9c; |
| | | font-size: 13px; |
| | | } |
| | | .role-code { |
| | | margin-left: 8px; |
| | | color: #93a2b1; |
| | | font-size: 12px; |
| | | } |
| | | .selected-total { |
| | | color: #2f658f; |
| | | font-size: 13px; |
| | | } |
| | | .permission-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 10px; |
| | | } |
| | | .permission-tip { |
| | | color: #7b8a9a; |
| | | font-size: 12px; |
| | | } |
| | | .permission-tree { |
| | | height: 460px; |
| | | padding: 8px 10px; |
| | | border: 1px solid #dce4ec; |
| | | border-radius: 4px; |
| | | overflow: auto; |
| | | } |
| | | ::v-deep .perm-button-node { |
| | | color: #b8791f; |
| | | } |
| | | </style> |
| | |
| | | |
| | | - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå
¬å¸ Git ä»åº `main` 忝ã |
| | | - `.codex-run`ãOffice éæä»¶å `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt` ä»ä¸ºæ¬å°ä¸´æ¶æä»¶ï¼ä¸åºæäº¤ã |
| | | |
| | | ## åä¸ã2026-10-06 èè°æ¶å°¾ï¼éæ± 21-22ï¼ |
| | | |
| | | | åºå· | éæ± | ç¶æ | å®ç°ä¸éªè¯ | |
| | | | --- | --- | --- | --- | |
| | | | 21 | èåæéç®¡çæ¹ä¸ºè§è²ç®¡ççâæé设置âå
¥å£ | 已宿 | å é¤ç¬ç«èåãè·¯ç±å页é¢ï¼è§è²å表æ°å¢âæé设置âï¼å¼¹çªä¿çèååæé®èç¹å¹¶æ£å¸¸åæ¾ | |
| | | | 22 | ç³»ç»ç®¡çæ°å¢äºçº§èåç»ï¼æ°å¢é误æ¥å¿ | 已宿 | æ°å¢âç³»ç»é
ç½® / æéé
ç½® / æ¥å¿ç®¡çâç®å½å¹¶è¿ç§»èåï¼é误æ¥å¿è®°å½éä¸å¡å¼å¸¸ï¼æ¯æçéãå页åå æ 详æ
| |
| | | |
| | | ### 13.1 主è¦åæ´æä»¶ |
| | | |
| | | åç«¯ï¼ |
| | | |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java` |
| | | - `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java` |
| | | |
| | | åç«¯ï¼ |
| | | |
| | | - `emergency-repair-web/src/views/system/RoleManage.vue` |
| | | - `emergency-repair-web/src/views/system/RolePermissionDialog.vue` |
| | | - `emergency-repair-web/src/views/system/ErrorLogManage.vue` |
| | | - `emergency-repair-web/src/views/system/MenuManage.vue` |
| | | - `emergency-repair-web/src/router/index.js` |
| | | - å é¤ `emergency-repair-web/src/views/system/MenuPermission.vue` |
| | | |
| | | ### 13.2 éªè¯ç»æ |
| | | |
| | | - å端 `mvn -B -DskipTests clean package` éè¿ï¼å端 `npm run build` éè¿ï¼ä»
ææ¢æå
ä½ç§¯åè¦ã |
| | | - `node .codex-run/verify-req21-22.js` éè¿ï¼äºçº§ç®å½åé误æ¥å¿èåæ¾ç¤ºæ£å¸¸ï¼æ§âèåæé管çâä¸åå¨ï¼è§è²æéå¼¹çªæé®èç¹æ° 78ï¼æéåæ¾æ£å¸¸ã |
| | | - é误æ¥å¿å表ãå¼å¸¸ç±»åçéåå æ 详æ
页é¢éªè¯éè¿ï¼äººä¸ºè§¦åéæ³æ¥å£åæ°åæåè½åºï¼ä¸´æ¶éªè¯è®°å½å·²æ¸
çã |
| | | - æªå¾ï¼`.codex-run/role-permission-dialog-verified.png`ã`.codex-run/error-log-verified.png`ã |
| | | |
| | | ### 13.3 æ¥ææ³¨æ |
| | | |
| | | - é误æ¥å¿ç®åè¦çè¿å
¥ Spring MVC å
¨å±å¼å¸¸å¤ççå¼å¸¸ï¼å¯å¨é¶æ®µå¼å¸¸ã弿¥çº¿ç¨å¼å¸¸è¥ç»è¿è¯¥å¤çå¨ï¼æä¸ä¼èªå¨è®°å½ã |
| | | - æ¬è½®ä»£ç ä¸äº¤æ¥è®°å½ä¸å¹¶æäº¤å¹¶æ¨éå
¬å¸ Git ä»åº `main` 忝ã |
| | | - `.codex-run`ãOffice éæä»¶å `emergency-repair-web/public/æ°å»º ææ¬ææ¡£.txt` ä»ä¸ºæ¬å°ä¸´æ¶æä»¶ï¼ä¸åºæäº¤ã |