From b023bec2616d8e24135534f1709ac65d95a2031d Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Mon, 22 Dec 2025 17:03:37 +0800 Subject: [PATCH] =?UTF-8?q?gateway=20tomcat=E5=8E=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/sql/createTableWorkcase.sql | 2 +- .../postgres/sql/initDataPermission.sql | 63 +++- .../postgres/sql/initDataWorkcase.sql | 6 + urbanLifelineServ/ai/pom.xml | 5 + urbanLifelineServ/apis/pom.xml | 29 ++ urbanLifelineServ/auth/pom.xml | 18 + .../common/auth/config/GatewayAuthConfig.java | 31 +- .../auth/filter/JwtAuthenticationFilter.java | 2 +- urbanLifelineServ/common/common-dto/pom.xml | 8 + .../xyzh/common/dto/sys/TbSysUserRoleDTO.java | 6 + urbanLifelineServ/common/common-utils/pom.xml | 19 ++ .../utils/config/FastJsonConfiguration.java | 3 +- urbanLifelineServ/common/pom.xml | 20 ++ urbanLifelineServ/file/pom.xml | 6 + urbanLifelineServ/gateway/pom.xml | 168 +++++++++- .../org/xyzh/gateway/GatewayApplication.java | 14 +- .../xyzh/gateway/filter/AuthGlobalFilter.java | 33 +- .../src/main/resources/application.yml | 8 + urbanLifelineServ/log/pom.xml | 5 + urbanLifelineServ/message/pom.xml | 16 + urbanLifelineServ/pom.xml | 30 +- urbanLifelineServ/system/pom.xml | 18 + .../system/controller/GuestController.java | 66 ++++ .../system/service/impl/GuestServiceImpl.java | 16 + .../src/main/resources/application-dev.yml | 51 ++- .../system/src/main/resources/application.yml | 50 +-- urbanLifelineServ/workcase/pom.xml | 6 + .../config/WebSocketAuthInterceptor.java | 110 +++++++ .../xyzh/workcase/config/WebSocketConfig.java | 18 +- .../controller/WorkcaseChatContorller.java | 261 +++++++++++++-- .../controller/WorkcaseController.java | 17 + .../src/main/resources/application.yml | 2 +- urbanLifelineWeb/.vscode/launch.json | 18 +- .../packages/bidding/src/types/shared.d.ts | 21 +- .../packages/platform/pnpm-lock.yaml | 11 + .../packages/platform/src/types/shared.d.ts | 21 +- urbanLifelineWeb/packages/shared/package.json | 2 + .../packages/shared/pnpm-lock.yaml | 90 +++++ .../packages/shared/src/api/workcase/index.ts | 1 + .../shared/src/api/workcase/workcaseChat.ts | 301 +++++++++++++++++ .../components/chatRoom/chatRoom/ChatRoom.vue | 2 +- .../shared/src/components/chatRoom/index.ts | 2 +- .../shared/src/types/workcase/chatRoom.ts | 61 +++- .../packages/workcase/package.json | 3 + .../packages/workcase/pnpm-lock.yaml | 106 +++++- .../packages/workcase/src/types/shared.d.ts | 15 +- .../views/public/ChatRoom/ChatRoomView.vue | 311 ++++++++++++------ .../packages/workcase/vite.config.ts | 4 +- .../packages/workcase_wechat/package.json | 6 + .../packages/workcase_wechat/pnpm-lock.yaml | 100 ++++++ .../58fece2f5498ef30e0e385846df90df38196ba82 | 1 + ... a71acfafa708e3cf031a5dfbb5150ae026174408} | 0 .../c94f6ecc8c2e943a1daefff4333af748c5da5830 | 1 + .../e7dda72aabb7327ace1257371777ab9cb044277e | 1 - .../f261594d19a040c129a8fd4ce88589e0f7988433 | 1 - 55 files changed, 1926 insertions(+), 260 deletions(-) create mode 100644 urbanLifelineServ/.bin/database/postgres/sql/initDataWorkcase.sql create mode 100644 urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketAuthInterceptor.java create mode 100644 urbanLifelineWeb/packages/shared/src/api/workcase/workcaseChat.ts create mode 100644 urbanLifelineWeb/packages/workcase_wechat/package.json create mode 100644 urbanLifelineWeb/packages/workcase_wechat/pnpm-lock.yaml create mode 100644 urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/58fece2f5498ef30e0e385846df90df38196ba82 rename urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/{c17110e7e9205cbffe9484ef36c48da353a3c2e6 => a71acfafa708e3cf031a5dfbb5150ae026174408} (100%) create mode 100644 urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/c94f6ecc8c2e943a1daefff4333af748c5da5830 delete mode 100644 urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/e7dda72aabb7327ace1257371777ab9cb044277e delete mode 100644 urbanLifelineWeb/packages/workcase_wechat/unpackage/cache/.mp-weixin/.uts2js/cache/uts_9b1b54d07a7a4d66ee84f54872fe1ae86df34bb1/code/cache/f261594d19a040c129a8fd4ce88589e0f7988433 diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql index dbd95f1e..127f732f 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableWorkcase.sql @@ -63,7 +63,7 @@ CREATE TABLE workcase.tb_chat_room_member( member_id VARCHAR(50) NOT NULL, -- 成员记录ID room_id VARCHAR(50) NOT NULL, -- 聊天室ID user_id VARCHAR(50) NOT NULL, -- 用户ID(来客ID或员工ID) - user_type VARCHAR(20) NOT NULL, -- 用户类型:guest-来客 stuff-客服 ai-AI助手 + user_type VARCHAR(20) NOT NULL, -- 用户类型:guest-来客 staff-客服 ai-AI助手 user_name VARCHAR(100) NOT NULL, -- 用户名称 status VARCHAR(20) NOT NULL DEFAULT 'active', -- 状态:active-活跃 left-已离开 removed-被移除 unread_count INTEGER NOT NULL DEFAULT 0, -- 该成员的未读消息数 diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql b/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql index 3c17047f..f2b62e7a 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql @@ -30,8 +30,8 @@ INSERT INTO sys.tb_sys_role ( ('ROLE-0003', 'role_user', '普通用户', '系统普通用户角色', 'global', NULL, true, 'system', NULL, now(), false), --- 访客(全局) -('ROLE-0004', 'role_guest', '访客', '系统访客角色,仅限查看基础信息', +-- 访客(全局)- 注册用户默认角色,具备客服聊天和工单的所有接口权限 +('ROLE-0004', 'role_guest', '访客', '访客角色,具备客服聊天和工单的所有接口权限', 'global', NULL, true, 'system', NULL, now(), false); -- ============================= @@ -138,7 +138,30 @@ INSERT INTO sys.tb_sys_permission ( ('PERM-0624', 'perm_workcase_tickets', '工单管理', 'workcase:tickets:view', '访问工单管理', 'module_workcase', true, 'system', NULL, now(), false), ('PERM-0625', 'perm_workcase_conversation', '对话数据', 'workcase:conversation:view', '访问对话数据管理', 'module_workcase', true, 'system', NULL, now(), false), ('PERM-0626', 'perm_workcase_agent', '智能体管理', 'workcase:agent:view', '访问智能体管理', 'module_workcase', true, 'system', NULL, now(), false), -('PERM-0627', 'perm_workcase_log', '日志管理', 'workcase:log:view', '访问日志管理', 'module_workcase', true, 'system', NULL, now(), false); +('PERM-0627', 'perm_workcase_log', '日志管理', 'workcase:log:view', '访问日志管理', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0628', 'perm_workcase_chatroom', '聊天室控制台', 'workcase:chatroom:view', '访问聊天室控制台', 'module_workcase', true, 'system', NULL, now(), false), + +-- Workcase 接口权限(访客用户可用) +-- AI对话接口权限 +('PERM-0701', 'perm_workcase_chat_create', 'AI对话创建', 'workcase:chat:create', '创建对话', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0702', 'perm_workcase_chat_update', 'AI对话更新', 'workcase:chat:update', '更新对话', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0703', 'perm_workcase_chat_list', 'AI对话查询', 'workcase:chat:list', '查询对话列表', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0704', 'perm_workcase_chat_message', 'AI对话消息', 'workcase:chat:message', '获取对话消息列表', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0705', 'perm_workcase_chat_stream', 'AI流式对话', 'workcase:chat:stream', '流式对话接口', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0706', 'perm_workcase_chat_analyze', 'AI对话分析', 'workcase:chat:analyze', '分析对话生成工单信息', 'module_workcase', true, 'system', NULL, now(), false), +-- 聊天室接口权限 +('PERM-0711', 'perm_workcase_room_create', '聊天室创建', 'workcase:room:create', '创建聊天室', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0712', 'perm_workcase_room_update', '聊天室更新', 'workcase:room:update', '更新聊天室', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0713', 'perm_workcase_room_close', '聊天室关闭', 'workcase:room:close', '关闭聊天室', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0714', 'perm_workcase_room_view', '聊天室查看', 'workcase:room:view', '查看聊天室', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0715', 'perm_workcase_room_member', '聊天室成员', 'workcase:room:member', '管理聊天室成员', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0716', 'perm_workcase_room_message', '聊天室消息', 'workcase:room:message', '发送和查看聊天室消息', 'module_workcase', true, 'system', NULL, now(), false), +-- 工单接口权限 +('PERM-0721', 'perm_workcase_ticket_create', '工单创建', 'workcase:ticket:create', '创建工单', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0722', 'perm_workcase_ticket_update', '工单更新', 'workcase:ticket:update', '更新工单', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0723', 'perm_workcase_ticket_view', '工单查看', 'workcase:ticket:view', '查看工单详情和列表', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0724', 'perm_workcase_ticket_process', '工单处理', 'workcase:ticket:process', '工单处理过程管理', 'module_workcase', true, 'system', NULL, now(), false), +('PERM-0725', 'perm_workcase_ticket_device', '工单设备', 'workcase:ticket:device', '工单设备管理', 'module_workcase', true, 'system', NULL, now(), false); -- ============================= -- 5. 初始化视图(菜单) -- ============================= @@ -224,6 +247,9 @@ INSERT INTO sys.tb_sys_view ( ('VIEW-W001', 'view_workcase_home', '智能客服', NULL, '/aichat', 'public/AIChat/AIChatView.vue', 'Home', 1, 'route', NULL, 'workcase', 'SubSidebarLayout', 10, '智能客服首页', 'system', now(), false), +('VIEW-W002', 'view_workcase_chatroom', '聊天室控制台', NULL, '/chatroom', 'public/ChatRoom/ChatRoomView.vue', 'MessageSquare', 1, + 'route', NULL, 'workcase', 'SubSidebarLayout', 20, '实时聊天室控制台', 'system', now(), false), + -- 管理端视图(使用 SubSidebarLayout 布局) ('VIEW-W101', 'view_workcase_admin_overview', '数据概览', NULL, '/admin/overview', 'admin/overview/OverviewView.vue', 'BarChart3', 1, 'route', NULL, 'workcase', 'SubSidebarLayout', 110, '泰豪小电数据概览', 'system', now(), false), @@ -308,17 +334,39 @@ INSERT INTO sys.tb_sys_role_permission ( ('RP-U-0015', 'role_user', 'perm_message_view', 'system', NULL, now(), false), ('RP-U-0016', 'role_user', 'perm_config_view', 'system', NULL, now(), false); --- 访客权限(仅查看 + 基础菜单访问) +-- 访客权限(基础菜单 + workcase聊天和工单全部接口权限) INSERT INTO sys.tb_sys_role_permission ( optsn, role_id, permission_id, creator, dept_path, create_time, deleted ) VALUES -- 平台基础菜单访问权限 ('RP-G-0001', 'role_guest', 'perm_platform_home', 'system', NULL, now(), false), ('RP-G-0002', 'role_guest', 'perm_platform_chat', 'system', NULL, now(), false), +('RP-G-0003', 'role_guest', 'perm_platform_workcase', 'system', NULL, now(), false), -- 系统功能权限(仅查看) ('RP-G-0011', 'role_guest', 'perm_user_view', 'system', NULL, now(), false), ('RP-G-0012', 'role_guest', 'perm_file_view', 'system', NULL, now(), false), -('RP-G-0013', 'role_guest', 'perm_message_view', 'system', NULL, now(), false); +('RP-G-0013', 'role_guest', 'perm_message_view', 'system', NULL, now(), false), +-- Workcase AI对话接口权限 +('RP-G-0021', 'role_guest', 'perm_workcase_chat_create', 'system', NULL, now(), false), +('RP-G-0022', 'role_guest', 'perm_workcase_chat_update', 'system', NULL, now(), false), +('RP-G-0023', 'role_guest', 'perm_workcase_chat_list', 'system', NULL, now(), false), +('RP-G-0024', 'role_guest', 'perm_workcase_chat_message', 'system', NULL, now(), false), +('RP-G-0025', 'role_guest', 'perm_workcase_chat_stream', 'system', NULL, now(), false), +('RP-G-0026', 'role_guest', 'perm_workcase_chat_analyze', 'system', NULL, now(), false), +-- Workcase 聊天室接口权限 +('RP-G-0031', 'role_guest', 'perm_workcase_room_create', 'system', NULL, now(), false), +('RP-G-0032', 'role_guest', 'perm_workcase_room_update', 'system', NULL, now(), false), +('RP-G-0033', 'role_guest', 'perm_workcase_room_close', 'system', NULL, now(), false), +('RP-G-0034', 'role_guest', 'perm_workcase_room_view', 'system', NULL, now(), false), +('RP-G-0035', 'role_guest', 'perm_workcase_room_member', 'system', NULL, now(), false), +('RP-G-0036', 'role_guest', 'perm_workcase_room_message', 'system', NULL, now(), false), +('RP-G-0037', 'role_guest', 'perm_workcase_chatroom', 'system', NULL, now(), false), +-- Workcase 工单接口权限 +('RP-G-0041', 'role_guest', 'perm_workcase_ticket_create', 'system', NULL, now(), false), +('RP-G-0042', 'role_guest', 'perm_workcase_ticket_update', 'system', NULL, now(), false), +('RP-G-0043', 'role_guest', 'perm_workcase_ticket_view', 'system', NULL, now(), false), +('RP-G-0044', 'role_guest', 'perm_workcase_ticket_process', 'system', NULL, now(), false), +('RP-G-0045', 'role_guest', 'perm_workcase_ticket_device', 'system', NULL, now(), false); -- ============================= -- 7. 视图权限关联 @@ -345,10 +393,9 @@ INSERT INTO sys.tb_sys_view_permission ( ('VP-P203', 'view_platform_admin_knowledge', 'perm_platform_admin_knowledge', 'system', NULL, now(), false), ('VP-P204', 'view_platform_admin_config', 'perm_platform_admin_config', 'system', NULL, now(), false), --- Workcase服务用户端视图关联(使用同一个workcase访问权限) +-- Workcase服务用户端视图关联 ('VP-W001', 'view_workcase_home', 'perm_platform_workcase', 'system', NULL, now(), false), -('VP-W002', 'view_workcase_list', 'perm_platform_workcase', 'system', NULL, now(), false), -('VP-W003', 'view_workcase_detail', 'perm_platform_workcase', 'system', NULL, now(), false), +('VP-W002', 'view_workcase_chatroom', 'perm_workcase_chatroom', 'system', NULL, now(), false), -- Workcase服务管理端视图关联 ('VP-W101', 'view_workcase_admin_overview', 'perm_workcase_overview', 'system', NULL, now(), false), diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initDataWorkcase.sql b/urbanLifelineServ/.bin/database/postgres/sql/initDataWorkcase.sql new file mode 100644 index 00000000..cf786a91 --- /dev/null +++ b/urbanLifelineServ/.bin/database/postgres/sql/initDataWorkcase.sql @@ -0,0 +1,6 @@ +-- 初始化聊天室人员 +-- user_admin +INSERT INTO workcase.tb_chat_room_member( + optsn, member_id, room_id, user_id, user_type, user_name, status, unread_count, last_read_time, last_read_msg_id, join_time, leave_time, creator, create_time, update_time +) VALUES +('MEM-0001', 'member_admin', 'room_0001', 'user_admin', 'staff', '系统管理员', 'active', 0, null, null, now(), null, 'system', now(), null); \ No newline at end of file diff --git a/urbanLifelineServ/ai/pom.xml b/urbanLifelineServ/ai/pom.xml index d0fa3cc4..9cf8920b 100644 --- a/urbanLifelineServ/ai/pom.xml +++ b/urbanLifelineServ/ai/pom.xml @@ -104,6 +104,11 @@ com.squareup.okhttp3 okhttp-sse + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/apis/pom.xml b/urbanLifelineServ/apis/pom.xml index 9dc297d6..26444721 100644 --- a/urbanLifelineServ/apis/pom.xml +++ b/urbanLifelineServ/apis/pom.xml @@ -82,6 +82,35 @@ org.apache.dubbo dubbo + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + org.springframework + spring-web + provided + + + + + org.springframework + spring-webmvc + provided + + + + + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.36 + provided + \ No newline at end of file diff --git a/urbanLifelineServ/auth/pom.xml b/urbanLifelineServ/auth/pom.xml index 897eb535..e6cbbb59 100644 --- a/urbanLifelineServ/auth/pom.xml +++ b/urbanLifelineServ/auth/pom.xml @@ -59,6 +59,24 @@ + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/config/GatewayAuthConfig.java b/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/config/GatewayAuthConfig.java index eecfe516..d685a2e1 100644 --- a/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/config/GatewayAuthConfig.java +++ b/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/config/GatewayAuthConfig.java @@ -100,14 +100,13 @@ public class GatewayAuthConfig { List authorities = new ArrayList<>(); try { - String authHeader = request.getHeader("Authorization"); - logger.info("Authorization header: {}", authHeader != null ? "Bearer ***" : "null"); + String token = extractToken(request); + logger.debug("提取到Token: {}", token != null ? "***" : "null"); - if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) { - String token = authHeader.substring(BEARER_PREFIX.length()); + if (StringUtils.hasText(token)) { String cacheKey = LOGIN_TOKEN_PREFIX + token; LoginDomain login = redisService.get(cacheKey, LoginDomain.class); - logger.info("Redis key: {}, login: {}", cacheKey, login != null ? "loaded" : "null"); + logger.debug("Redis key: {}, login: {}", cacheKey, login != null ? "loaded" : "null"); if (login != null) { if (login.getUserPermissions() != null) { @@ -118,7 +117,7 @@ public class GatewayAuthConfig { } } } - logger.info("加载用户权限: {} 个", authorities.size()); + logger.debug("加载用户权限: {} 个", authorities.size()); } } } catch (Exception e) { @@ -127,5 +126,25 @@ public class GatewayAuthConfig { return authorities; } + + /** + * 从请求头或URL参数提取Token + */ + private String extractToken(HttpServletRequest request) { + // 1. 优先从请求头获取 + String authHeader = request.getHeader("Authorization"); + if (StringUtils.hasText(authHeader) && authHeader.startsWith(BEARER_PREFIX)) { + return authHeader.substring(BEARER_PREFIX.length()).trim(); + } + + // 2. 从URL参数获取(用于WebSocket连接) + String tokenParam = request.getParameter("token"); + if (StringUtils.hasText(tokenParam)) { + logger.debug("从URL参数获取Token"); + return tokenParam.trim(); + } + + return null; + } } } diff --git a/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/filter/JwtAuthenticationFilter.java b/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/filter/JwtAuthenticationFilter.java index 1ae5b9ac..43afd1f6 100644 --- a/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/filter/JwtAuthenticationFilter.java +++ b/urbanLifelineServ/common/common-auth/src/main/java/org/xyzh/common/auth/filter/JwtAuthenticationFilter.java @@ -127,7 +127,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { // 从Redis加载 LoginDomain,并将权限装配到 Spring Security 上下文 if (redisService != null) { - Object obj = redisService.get(REDIS_LOGIN_PREFIX + userId); + Object obj = redisService.get(REDIS_LOGIN_PREFIX + token); if (obj instanceof LoginDomain loginDomain) { // 组装权限码 authorities(已存在) List permAuthorities = null; diff --git a/urbanLifelineServ/common/common-dto/pom.xml b/urbanLifelineServ/common/common-dto/pom.xml index 4151742b..db5b34a0 100644 --- a/urbanLifelineServ/common/common-dto/pom.xml +++ b/urbanLifelineServ/common/common-dto/pom.xml @@ -35,6 +35,14 @@ common-utils ${urban-lifeline.version} + + + + io.swagger.core.v3 + swagger-annotations-jakarta + 2.2.36 + provided + \ No newline at end of file diff --git a/urbanLifelineServ/common/common-dto/src/main/java/org/xyzh/common/dto/sys/TbSysUserRoleDTO.java b/urbanLifelineServ/common/common-dto/src/main/java/org/xyzh/common/dto/sys/TbSysUserRoleDTO.java index 4cd31f4f..4f9bc1d4 100644 --- a/urbanLifelineServ/common/common-dto/src/main/java/org/xyzh/common/dto/sys/TbSysUserRoleDTO.java +++ b/urbanLifelineServ/common/common-dto/src/main/java/org/xyzh/common/dto/sys/TbSysUserRoleDTO.java @@ -23,4 +23,10 @@ public class TbSysUserRoleDTO extends BaseDTO { @Schema(description = "角色ID") private String roleId; + + @Schema(description = "部门ID") + private String deptId; + + @Schema(description = "部门全路径") + private String deptPath; } \ No newline at end of file diff --git a/urbanLifelineServ/common/common-utils/pom.xml b/urbanLifelineServ/common/common-utils/pom.xml index ad51b7d6..31b6b205 100644 --- a/urbanLifelineServ/common/common-utils/pom.xml +++ b/urbanLifelineServ/common/common-utils/pom.xml @@ -19,6 +19,25 @@ + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + org.springframework + spring-web + provided + + + org.springframework + spring-webmvc + provided + + org.apache.poi diff --git a/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/config/FastJsonConfiguration.java b/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/config/FastJsonConfiguration.java index 7d00be0c..c889bbae 100644 --- a/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/config/FastJsonConfiguration.java +++ b/urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/config/FastJsonConfiguration.java @@ -2,7 +2,7 @@ package org.xyzh.common.utils.config; import com.alibaba.fastjson2.support.config.FastJsonConfig; import com.alibaba.fastjson2.support.spring6.http.converter.FastJsonHttpMessageConverter; -import org.springframework.context.annotation.Bean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; @@ -20,6 +20,7 @@ import java.util.List; * @since 2025-11-28 */ @Configuration +@ConditionalOnClass(WebMvcConfigurer.class) public class FastJsonConfiguration implements WebMvcConfigurer { /** diff --git a/urbanLifelineServ/common/pom.xml b/urbanLifelineServ/common/pom.xml index e3e5d983..55eafe1b 100644 --- a/urbanLifelineServ/common/pom.xml +++ b/urbanLifelineServ/common/pom.xml @@ -79,4 +79,24 @@ + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + org.springframework + spring-web + provided + + + org.springframework + spring-webmvc + provided + + \ No newline at end of file diff --git a/urbanLifelineServ/file/pom.xml b/urbanLifelineServ/file/pom.xml index 2e93cd82..2c30c6c9 100644 --- a/urbanLifelineServ/file/pom.xml +++ b/urbanLifelineServ/file/pom.xml @@ -127,6 +127,12 @@ org.springframework.boot spring-boot-starter-data-redis + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + diff --git a/urbanLifelineServ/gateway/pom.xml b/urbanLifelineServ/gateway/pom.xml index cb587a66..0c8c2ed5 100644 --- a/urbanLifelineServ/gateway/pom.xml +++ b/urbanLifelineServ/gateway/pom.xml @@ -18,6 +18,89 @@ 21 + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + test + + + * + * + + + + + + org.springframework.boot + spring-boot-starter-security + ${spring-boot.version} + test + + + * + * + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + test + + + * + * + + + + + + org.springframework.boot + spring-boot-starter-tomcat + ${spring-boot.version} + test + + + * + * + + + + + org.apache.tomcat.embed + tomcat-embed-core + 10.1.48 + test + + + org.apache.tomcat.embed + tomcat-embed-el + 10.1.48 + test + + + org.apache.tomcat.embed + tomcat-embed-websocket + 10.1.48 + test + + + + org.springframework + spring-webmvc + ${spring-framework.version} + test + + + + @@ -81,24 +164,81 @@ org.xyzh.common common-auth + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.apache.tomcat.embed + tomcat-embed-core + + org.xyzh.common common-redis + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.apache.tomcat.embed + tomcat-embed-core + + - + org.xyzh.common common-core + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + org.apache.tomcat.embed + tomcat-embed-core + + @@ -138,6 +278,30 @@ lombok true + + + + org.springframework.security + spring-security-config + + + org.springframework.security + spring-security-web + + + + + jakarta.servlet + jakarta.servlet-api + provided + + + + + org.springframework + spring-webmvc + provided + diff --git a/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/GatewayApplication.java b/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/GatewayApplication.java index d4e13b72..742221fe 100644 --- a/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/GatewayApplication.java +++ b/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/GatewayApplication.java @@ -6,9 +6,6 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; -import org.xyzh.common.auth.config.SecurityConfig; -import org.xyzh.common.auth.config.WebMvcConfig; -import org.xyzh.common.auth.config.GatewayAuthConfig; /** * @description Gateway 网关启动类 @@ -25,11 +22,12 @@ import org.xyzh.common.auth.config.GatewayAuthConfig; "org.xyzh.common" // 公共模块(包括 common-auth) }, excludeFilters = { - // 排除 Spring MVC 相关配置,Gateway 使用 WebFlux - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = { - SecurityConfig.class, // Spring MVC Security配置 - WebMvcConfig.class, // Spring MVC配置 - GatewayAuthConfig.class // 微服务Gateway模式配置(使用Servlet Filter) + // 使用正则表达式排除 Spring MVC 相关配置,避免加载 WebMvcConfigurer 类 + @ComponentScan.Filter(type = FilterType.REGEX, pattern = { + "org\\.xyzh\\.common\\.auth\\.config\\.SecurityConfig", + "org\\.xyzh\\.common\\.auth\\.config\\.WebMvcConfig", + "org\\.xyzh\\.common\\.auth\\.config\\.GatewayAuthConfig", + "org\\.xyzh\\.common\\.utils\\.config\\.FastJsonConfiguration" }) } ) diff --git a/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/filter/AuthGlobalFilter.java b/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/filter/AuthGlobalFilter.java index b9f44c54..03606474 100644 --- a/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/filter/AuthGlobalFilter.java +++ b/urbanLifelineServ/gateway/src/main/java/org/xyzh/gateway/filter/AuthGlobalFilter.java @@ -131,27 +131,32 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered { } /** - * 从请求头中提取 Token + * 从请求头或URL参数中提取 Token */ private String extractToken(ServerHttpRequest request) { + // 1. 优先从请求头获取 List headers = request.getHeaders().get(authProperties.getTokenHeader()); - if (headers == null || headers.isEmpty()) { - return null; + if (headers != null && !headers.isEmpty()) { + String header = headers.get(0); + if (StringUtils.hasText(header)) { + // 支持 Bearer 前缀 + String prefix = authProperties.getTokenPrefix(); + if (StringUtils.hasText(prefix) && header.startsWith(prefix)) { + return header.substring(prefix.length()).trim(); + } + // 也支持直接传递 Token(不带前缀) + return header.trim(); + } } - String header = headers.get(0); - if (!StringUtils.hasText(header)) { - return null; + // 2. 从URL参数获取(用于WebSocket连接) + String tokenParam = request.getQueryParams().getFirst("token"); + if (StringUtils.hasText(tokenParam)) { + log.debug("从URL参数获取Token"); + return tokenParam.trim(); } - // 支持 Bearer 前缀 - String prefix = authProperties.getTokenPrefix(); - if (StringUtils.hasText(prefix) && header.startsWith(prefix)) { - return header.substring(prefix.length()).trim(); - } - - // 也支持直接传递 Token(不带前缀) - return header.trim(); + return null; } /** diff --git a/urbanLifelineServ/gateway/src/main/resources/application.yml b/urbanLifelineServ/gateway/src/main/resources/application.yml index 4a8d3770..5e25b021 100644 --- a/urbanLifelineServ/gateway/src/main/resources/application.yml +++ b/urbanLifelineServ/gateway/src/main/resources/application.yml @@ -92,6 +92,14 @@ spring: filters: - StripPrefix=1 + # ==================== 工单服务 WebSocket 路由 ==================== + - id: workcase-websocket + uri: lb:ws://workcase-service + predicates: + - Path=/urban-lifeline/workcase/ws/** + filters: + - StripPrefix=1 + # ==================== 工单服务路由 ==================== - id: workcase-service uri: lb://workcase-service diff --git a/urbanLifelineServ/log/pom.xml b/urbanLifelineServ/log/pom.xml index 3015b126..1113cfe6 100644 --- a/urbanLifelineServ/log/pom.xml +++ b/urbanLifelineServ/log/pom.xml @@ -101,6 +101,11 @@ mybatis-spring ${mybatis.spring.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/message/pom.xml b/urbanLifelineServ/message/pom.xml index 485ffaa4..c65f28dd 100644 --- a/urbanLifelineServ/message/pom.xml +++ b/urbanLifelineServ/message/pom.xml @@ -59,6 +59,22 @@ + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/pom.xml b/urbanLifelineServ/pom.xml index 0f68c454..0d85662e 100644 --- a/urbanLifelineServ/pom.xml +++ b/urbanLifelineServ/pom.xml @@ -434,41 +434,13 @@ - - - org.springframework.boot - spring-boot-starter-security - - - org.springframework.boot - spring-boot-starter-logging - - - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-logging - - - - + org.springframework.boot spring-boot-starter-log4j2 - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - diff --git a/urbanLifelineServ/system/pom.xml b/urbanLifelineServ/system/pom.xml index 62b193ec..af25b55b 100644 --- a/urbanLifelineServ/system/pom.xml +++ b/urbanLifelineServ/system/pom.xml @@ -85,6 +85,24 @@ mybatis-spring ${mybatis.spring.version} + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/system/src/main/java/org/xyzh/system/controller/GuestController.java b/urbanLifelineServ/system/src/main/java/org/xyzh/system/controller/GuestController.java index fb2ea225..aa6456c9 100644 --- a/urbanLifelineServ/system/src/main/java/org/xyzh/system/controller/GuestController.java +++ b/urbanLifelineServ/system/src/main/java/org/xyzh/system/controller/GuestController.java @@ -1,6 +1,11 @@ package org.xyzh.system.controller; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import com.alibaba.fastjson2.JSON; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.beans.factory.annotation.Autowired; @@ -15,15 +20,22 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.xyzh.api.auth.service.AuthService; import org.xyzh.api.system.service.GuestService; +import org.xyzh.api.system.service.ModulePermissionService; +import org.xyzh.api.system.vo.PermissionVO; import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.LoginParam; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.dto.sys.TbGuestDTO; +import org.xyzh.common.dto.sys.TbSysPermissionDTO; import org.xyzh.common.dto.sys.TbSysUserDTO; import org.xyzh.common.dto.sys.TbSysUserInfoDTO; +import org.xyzh.common.dto.sys.TbSysUserRoleDTO; +import org.xyzh.common.dto.sys.TbSysViewDTO; import org.xyzh.common.utils.id.IdUtil; import org.xyzh.common.utils.validation.ValidationUtils; +import org.xyzh.common.auth.utils.JwtTokenUtil; +import org.xyzh.common.redis.service.RedisService; import io.swagger.v3.oas.annotations.Operation; import jakarta.servlet.http.HttpServletRequest; @@ -48,6 +60,15 @@ public class GuestController { @DubboReference(version = "1.0.0", group = "auth", timeout = 5000, check = false, retries = 0) private AuthService authService; + @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0) + private ModulePermissionService modulePermissionService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private RedisService redisService; + @PostMapping public ResultDomain createGuest(TbGuestDTO guest) { @@ -191,8 +212,53 @@ public class GuestController { userInfoDTO.setUsername(guest.getName()); loginDomain.setUserInfo(userInfoDTO); + // 设置角色信息 + List userRoles = new ArrayList<>(); + TbSysUserRoleDTO userRole = new TbSysUserRoleDTO(); + userRole.setUserId(guest.getUserId()); + userRole.setRoleId("role_guest"); + userRole.setDeptId("dept_root"); + userRoles.add(userRole); + loginDomain.setUserRoles(userRoles); + + // 获取用户权限信息 + List userPermissions = new ArrayList<>(); + List userViews = new ArrayList<>(); + + ResultDomain permissionsResult = modulePermissionService.getUserPermissions(guest.getUserId()); + if (permissionsResult.getSuccess() && permissionsResult.getDataList() != null) { + for (PermissionVO permission : permissionsResult.getDataList()) { + if (permission.getPermissionId() != null) { + TbSysPermissionDTO permissionDTO = PermissionVO.toPermissionDTO(permission); + if (permissionDTO != null) { + userPermissions.add(permissionDTO); + } + } + if (permission.getViewId() != null) { + TbSysViewDTO viewDTO = PermissionVO.toViewDTO(permission); + if (viewDTO != null) { + userViews.add(viewDTO); + } + } + } + } + loginDomain.setUserPermissions(userPermissions); + loginDomain.setUserViews(userViews); + loginDomain.setLoginType("wechat_miniprogram"); + // 生成 JWT Token + String token = jwtTokenUtil.generateToken(loginDomain); + loginDomain.setToken(token); + + // 将登录信息存储到 Redis(24小时有效期) + String loginKey = "login:token:" + token; + redisService.set(loginKey, JSON.toJSONString(loginDomain), 24, TimeUnit.HOURS); + + // 存储用户登录状态 + String userLoginKey = "login:user:" + guest.getUserId(); + redisService.set(userLoginKey, token, 24, TimeUnit.HOURS); + return loginDomain; } } diff --git a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/GuestServiceImpl.java b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/GuestServiceImpl.java index 50fcf4f9..853c6979 100644 --- a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/GuestServiceImpl.java +++ b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/GuestServiceImpl.java @@ -12,7 +12,11 @@ import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.dto.sys.TbGuestDTO; +import org.xyzh.common.dto.sys.TbSysUserRoleDTO; import org.xyzh.system.mapper.user.TbGuestMapper; +import org.xyzh.system.mapper.user.TbSysUserRoleMapper; + +import java.util.Date; /** @@ -34,10 +38,22 @@ public class GuestServiceImpl implements GuestService{ @Autowired private TbGuestMapper guestMapper; + @Autowired + private TbSysUserRoleMapper userRoleMapper; + @Override @Transactional public ResultDomain createGuest(TbGuestDTO guest) { guestMapper.insertGuest(guest); + + // 绑定访客角色(role_guest) + TbSysUserRoleDTO userRole = new TbSysUserRoleDTO(); + userRole.setUserId(guest.getUserId()); + userRole.setRoleId("role_guest"); + userRole.setDeptId("dept_root"); + userRole.setCreateTime(new Date()); + userRoleMapper.insertUserRole(userRole); + return ResultDomain.success("创建成功",guest); } diff --git a/urbanLifelineServ/system/src/main/resources/application-dev.yml b/urbanLifelineServ/system/src/main/resources/application-dev.yml index 8daa9881..af62a4ba 100644 --- a/urbanLifelineServ/system/src/main/resources/application-dev.yml +++ b/urbanLifelineServ/system/src/main/resources/application-dev.yml @@ -4,35 +4,34 @@ server: servlet: context-path: /urban-lifeline/system # ================== Auth ==================== -urban-lifeline: - auth: - enabled: true +auth: + enabled: true + gateway-mode: true + # 认证接口:可以按服务自定义 + login-path: /urban-lifeline/auth/login + logout-path: /urban-lifeline/auth/logout + captcha-path: /urban-lifeline/auth/captcha + refresh-path: /urban-lifeline/auth/refresh - # 认证接口:可以按服务自定义 - login-path: /urban-lifeline/auth/login - logout-path: /urban-lifeline/auth/logout - captcha-path: /urban-lifeline/auth/captcha - refresh-path: /urban-lifeline/auth/refresh + # 通用白名单(非认证接口) + whitelist: + # Swagger/OpenAPI 文档相关(建议不带 context-path) + - /swagger-ui/** + - /swagger-ui.html + - /v3/api-docs/** + - /webjars/** - # 通用白名单(非认证接口) - whitelist: - # Swagger/OpenAPI 文档相关(建议不带 context-path) - - /swagger-ui/** - - /swagger-ui.html - - /v3/api-docs/** - - /webjars/** + # 静态资源 + - /favicon.ico + - /error - # 静态资源 - - /favicon.ico - - /error - - # 健康检查 - - /actuator/health - - /actuator/info - - # 其他需要放行的路径 - # - /public/** - # - /api/public/** + # 健康检查 + - /actuator/health + - /actuator/info + + # 其他需要放行的路径 + # - /public/** + # - /api/public/** # ================== Security ================== diff --git a/urbanLifelineServ/system/src/main/resources/application.yml b/urbanLifelineServ/system/src/main/resources/application.yml index 71e69873..29142845 100644 --- a/urbanLifelineServ/system/src/main/resources/application.yml +++ b/urbanLifelineServ/system/src/main/resources/application.yml @@ -4,35 +4,35 @@ server: # servlet: # context-path: /urban-lifeline/system # 微服务架构下,context-path由Gateway管理 # ================== Auth ==================== -urban-lifeline: - auth: - enabled: true - # 认证接口:可以按服务自定义 - login-path: /urban-lifeline/auth/login - logout-path: /urban-lifeline/auth/logout - captcha-path: /urban-lifeline/auth/captcha - refresh-path: /urban-lifeline/auth/refresh +auth: + enabled: true + gateway-mode: true + # 认证接口:可以按服务自定义 + login-path: /urban-lifeline/auth/login + logout-path: /urban-lifeline/auth/logout + captcha-path: /urban-lifeline/auth/captcha + refresh-path: /urban-lifeline/auth/refresh - # 通用白名单(非认证接口) - whitelist: - # Swagger/OpenAPI 文档相关(建议不带 context-path) - - /swagger-ui/** - - /swagger-ui.html - - /v3/api-docs/** - - /webjars/** + # 通用白名单(非认证接口) + whitelist: + # Swagger/OpenAPI 文档相关(建议不带 context-path) + - /swagger-ui/** + - /swagger-ui.html + - /v3/api-docs/** + - /webjars/** - # 静态资源 - - /favicon.ico - - /error + # 静态资源 + - /favicon.ico + - /error - # 健康检查 - - /actuator/health - - /actuator/info - - # 其他需要放行的路径 - # - /public/** - # - /api/public/** + # 健康检查 + - /actuator/health + - /actuator/info + + # 其他需要放行的路径 + # - /public/** + # - /api/public/** security: aes: diff --git a/urbanLifelineServ/workcase/pom.xml b/urbanLifelineServ/workcase/pom.xml index a81545d3..4f0f278e 100644 --- a/urbanLifelineServ/workcase/pom.xml +++ b/urbanLifelineServ/workcase/pom.xml @@ -116,6 +116,12 @@ org.springframework.boot spring-boot-starter-websocket + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + \ No newline at end of file diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketAuthInterceptor.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketAuthInterceptor.java new file mode 100644 index 00000000..54de0987 --- /dev/null +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketAuthInterceptor.java @@ -0,0 +1,110 @@ +package org.xyzh.workcase.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; +import org.xyzh.common.auth.contants.AuthContants; + +import java.util.Map; + +/** + * WebSocket握手拦截器 + * 从网关传递的请求头中获取已验证的用户信息 + */ +@Component +public class WebSocketAuthInterceptor implements HandshakeInterceptor { + + private static final Logger log = LoggerFactory.getLogger(WebSocketAuthInterceptor.class); + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Map attributes) throws Exception { + + log.info("WebSocket握手开始,URI: {}", request.getURI()); + + try { + // 从网关传递的请求头中获取用户ID(网关已完成token验证) + String userId = extractUserIdFromRequest(request); + + if (!StringUtils.hasText(userId)) { + log.warn("WebSocket握手失败:请求头中未找到用户ID,网关认证可能失败"); + return false; + } + + // 可选:也获取token,供后续使用 + String token = extractTokenFromRequest(request); + + // 将用户信息存储到WebSocket会话属性中,供后续使用 + attributes.put("userId", userId); + if (StringUtils.hasText(token)) { + attributes.put("token", token); + } + + log.info("WebSocket握手成功,userId: {}", userId); + return true; + + } catch (Exception e) { + log.error("WebSocket握手异常", e); + return false; + } + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Exception exception) { + if (exception != null) { + log.error("WebSocket握手后发生异常", exception); + } + } + + /** + * 从网关传递的请求头中提取用户ID + */ + private String extractUserIdFromRequest(ServerHttpRequest request) { + if (request instanceof ServletServerHttpRequest) { + ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; + + // 从网关传递的请求头获取(标准微服务架构方式) + String userId = servletRequest.getHeaders().getFirst(AuthContants.USER_ID_ATTRIBUTE); + if (StringUtils.hasText(userId)) { + log.debug("从网关请求头获取用户ID: {}", userId); + return userId; + } + } + + log.warn("未能从请求头中获取用户ID"); + return null; + } + + /** + * 从请求头中提取token(可选) + */ + private String extractTokenFromRequest(ServerHttpRequest request) { + if (request instanceof ServletServerHttpRequest) { + ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; + + // 从网关传递的请求头获取 + String token = servletRequest.getHeaders().getFirst(AuthContants.TOKEN_ATTRIBUTE); + if (StringUtils.hasText(token)) { + return token; + } + + // 也支持从Authorization头获取 + String authHeader = servletRequest.getHeaders().getFirst("Authorization"); + if (StringUtils.hasText(authHeader)) { + if (authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return authHeader; + } + } + + return null; + } +} diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketConfig.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketConfig.java index ba746463..3e61548e 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketConfig.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/config/WebSocketConfig.java @@ -1,5 +1,6 @@ package org.xyzh.workcase.config; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; @@ -14,6 +15,9 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + @Autowired + private WebSocketAuthInterceptor webSocketAuthInterceptor; + @Override public void configureMessageBroker(MessageBrokerRegistry config) { // 配置消息代理 @@ -29,9 +33,15 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { - // 注册STOMP端点 - registry.addEndpoint("/ws/chat") - .setAllowedOriginPatterns("*") // 允许跨域 - .withSockJS(); // 支持SockJS降级方案 + // 原生WebSocket端点(不使用SockJS) + registry.addEndpoint("/workcase/ws/chat") + .setAllowedOriginPatterns("*") + .addInterceptors(webSocketAuthInterceptor); + + // SockJS端点(用于不支持WebSocket的浏览器降级) + registry.addEndpoint("/workcase/ws/chat-sockjs") + .setAllowedOriginPatterns("*") + .addInterceptors(webSocketAuthInterceptor) + .withSockJS(); } } diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java index 5cb92846..e82941cf 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseChatContorller.java @@ -1,6 +1,8 @@ package org.xyzh.workcase.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -13,9 +15,18 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.xyzh.api.ai.dto.ChatPrepareData; import org.xyzh.api.ai.dto.TbChat; import org.xyzh.api.ai.dto.TbChatMessage; +import org.xyzh.api.workcase.dto.TbChatRoomDTO; +import org.xyzh.api.workcase.dto.TbChatRoomMemberDTO; +import org.xyzh.api.workcase.dto.TbChatRoomMessageDTO; +import org.xyzh.api.workcase.dto.TbCustomerServiceDTO; import org.xyzh.api.workcase.dto.TbWordCloudDTO; import org.xyzh.api.workcase.dto.TbWorkcaseDTO; +import org.xyzh.api.workcase.service.ChatRoomService; import org.xyzh.api.workcase.service.WorkcaseChatService; +import org.xyzh.api.workcase.vo.ChatMemberVO; +import org.xyzh.api.workcase.vo.ChatRoomMessageVO; +import org.xyzh.api.workcase.vo.ChatRoomVO; +import org.xyzh.api.workcase.vo.CustomerServiceVO; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.utils.validation.ValidationResult; @@ -28,7 +39,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; /** * @description 工单对话控制器 - * - AI对话管理 + * - AI对话管理(智能问答) + * - ChatRoom聊天室管理(实时IM) * - 微信客服消息接收 * - 词云管理 * @filename WorkcaseChatController.java @@ -44,9 +56,13 @@ public class WorkcaseChatContorller { @Autowired private WorkcaseChatService workcaseChatService; + @Autowired + private ChatRoomService chatRoomService; + // ========================= AI对话管理 ========================= @Operation(summary = "创建对话") + @PreAuthorize("hasAuthority('workcase:chat:create')") @PostMapping public ResultDomain createChat(@RequestBody TbChat chat) { ValidationResult vr = ValidationUtils.validate(chat, Arrays.asList( @@ -59,6 +75,7 @@ public class WorkcaseChatContorller { } @Operation(summary = "更新对话") + @PreAuthorize("hasAuthority('workcase:chat:update')") @PutMapping public ResultDomain updateChat(@RequestBody TbChat chat) { ValidationResult vr = ValidationUtils.validate(chat, Arrays.asList( @@ -71,12 +88,14 @@ public class WorkcaseChatContorller { } @Operation(summary = "查询对话列表") + @PreAuthorize("hasAuthority('workcase:chat:list')") @PostMapping("/list") public ResultDomain getChatList(@RequestBody TbChat filter) { return workcaseChatService.getChatList(filter); } @Operation(summary = "获取对话消息列表") + @PreAuthorize("hasAuthority('workcase:chat:message')") @PostMapping("/message/list") public ResultDomain getChatMessageList(@RequestBody TbChat filter) { ValidationResult vr = ValidationUtils.validate(filter, Arrays.asList( @@ -89,6 +108,7 @@ public class WorkcaseChatContorller { } @Operation(summary = "准备对话会话") + @PreAuthorize("hasAuthority('workcase:chat:stream')") @PostMapping("/prepare") public ResultDomain prepareChatMessageSession(@RequestBody ChatPrepareData prepareData) { ValidationResult vr = ValidationUtils.validate(prepareData, Arrays.asList( @@ -102,18 +122,21 @@ public class WorkcaseChatContorller { } @Operation(summary = "流式对话(SSE)") + @PreAuthorize("hasAuthority('workcase:chat:stream')") @GetMapping(value = "/stream/{sessionId}", produces = "text/event-stream") public SseEmitter streamChatMessage(@PathVariable String sessionId) { return workcaseChatService.streamChatMessageWithSse(sessionId); } @Operation(summary = "停止对话") + @PreAuthorize("hasAuthority('workcase:chat:stream')") @PostMapping("/stop/{taskId}") public ResultDomain stopChat(@RequestBody TbChat filter, @PathVariable String taskId) { return workcaseChatService.stopChatMessageByTaskId(filter, taskId); } @Operation(summary = "评论对话消息") + @PreAuthorize("hasAuthority('workcase:chat:message')") @PostMapping("/comment") public ResultDomain commentChatMessage(@RequestBody TbChat filter, @RequestParam String messageId, @RequestParam String comment) { @@ -123,43 +146,233 @@ public class WorkcaseChatContorller { // ========================= 对话分析 ========================= @Operation(summary = "分析对话(AI预填工单信息)") + @PreAuthorize("hasAuthority('workcase:chat:analyze')") @GetMapping("/analyze/{chatId}") public ResultDomain analyzeChat(@PathVariable String chatId) { return workcaseChatService.analyzeChat(chatId); } @Operation(summary = "总结对话") + @PreAuthorize("hasAuthority('workcase:chat:analyze')") @PostMapping("/summary/{chatId}") public ResultDomain summaryChat(@PathVariable String chatId) { return workcaseChatService.summaryChat(chatId); } + // ========================= ChatRoom聊天室管理(实时IM) ========================= + + @Operation(summary = "创建聊天室") + @PreAuthorize("hasAuthority('workcase:room:create')") + @PostMapping("/room") + public ResultDomain createChatRoom(@RequestBody TbChatRoomDTO chatRoom) { + ValidationResult vr = ValidationUtils.validate(chatRoom, Arrays.asList( + ValidationUtils.requiredString("workcaseId", "工单ID"), + ValidationUtils.requiredString("guestId", "来客ID") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.createChatRoom(chatRoom); + } + + @Operation(summary = "更新聊天室") + @PreAuthorize("hasAuthority('workcase:room:update')") + @PutMapping("/room") + public ResultDomain updateChatRoom(@RequestBody TbChatRoomDTO chatRoom) { + ValidationResult vr = ValidationUtils.validate(chatRoom, Arrays.asList( + ValidationUtils.requiredString("roomId", "聊天室ID") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.updateChatRoom(chatRoom); + } + + @Operation(summary = "关闭聊天室") + @PreAuthorize("hasAuthority('workcase:room:close')") + @PostMapping("/room/{roomId}/close") + public ResultDomain closeChatRoom(@PathVariable String roomId, @RequestParam String closedBy) { + return chatRoomService.closeChatRoom(roomId, closedBy); + } + + @Operation(summary = "获取聊天室详情") + @PreAuthorize("hasAuthority('workcase:room:view')") + @GetMapping("/room/{roomId}") + public ResultDomain getChatRoomById(@PathVariable String roomId) { + return chatRoomService.getChatRoomById(roomId); + } + + @Operation(summary = "分页查询聊天室") + @PreAuthorize("hasAuthority('workcase:room:view')") + @PostMapping("/room/page") + public ResultDomain getChatRoomPage(@RequestBody PageRequest pageRequest) { + ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( + ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null), + ValidationUtils.requiredNumber("pageParam.pageSize", "每页数量", 1, 100) + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.getChatRoomPage(pageRequest); + } + + // ========================= ChatRoom成员管理 ========================= + + @Operation(summary = "添加聊天室成员") + @PreAuthorize("hasAuthority('workcase:room:member')") + @PostMapping("/room/member") + public ResultDomain addChatRoomMember(@RequestBody TbChatRoomMemberDTO member) { + ValidationResult vr = ValidationUtils.validate(member, Arrays.asList( + ValidationUtils.requiredString("roomId", "聊天室ID"), + ValidationUtils.requiredString("userId", "用户ID") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.addChatRoomMember(member); + } + + @Operation(summary = "移除聊天室成员") + @PreAuthorize("hasAuthority('workcase:room:member')") + @DeleteMapping("/room/member/{memberId}") + public ResultDomain removeChatRoomMember(@PathVariable String memberId) { + return chatRoomService.removeChatRoomMember(memberId); + } + + @Operation(summary = "获取聊天室成员列表") + @PreAuthorize("hasAuthority('workcase:room:member')") + @GetMapping("/room/{roomId}/members") + public ResultDomain getChatRoomMemberList(@PathVariable String roomId) { + return chatRoomService.getChatRoomMemberList(roomId); + } + + // ========================= ChatRoom消息管理 ========================= + + @Operation(summary = "发送聊天室消息") + @PreAuthorize("hasAuthority('workcase:room:message')") + @PostMapping("/room/message") + public ResultDomain sendMessage(@RequestBody TbChatRoomMessageDTO message) { + ValidationResult vr = ValidationUtils.validate(message, Arrays.asList( + ValidationUtils.requiredString("roomId", "聊天室ID"), + ValidationUtils.requiredString("senderId", "发送者ID"), + ValidationUtils.requiredString("content", "消息内容") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.sendMessage(message); + } + + @Operation(summary = "分页查询聊天室消息") + @PreAuthorize("hasAuthority('workcase:room:message')") + @PostMapping("/room/message/page") + public ResultDomain getChatMessagePage(@RequestBody PageRequest pageRequest) { + ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( + ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null), + ValidationUtils.requiredNumber("pageParam.pageSize", "每页数量", 1, 100) + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.getChatMessagePage(pageRequest); + } + + @Operation(summary = "删除聊天室消息") + @PreAuthorize("hasAuthority('workcase:room:message')") + @DeleteMapping("/room/message/{messageId}") + public ResultDomain deleteRoomMessage(@PathVariable String messageId) { + return chatRoomService.deleteMessage(messageId); + } + + // ========================= 客服人员管理 ========================= + + @Operation(summary = "添加客服人员") + @PostMapping("/customer-service") + public ResultDomain addCustomerService(@RequestBody TbCustomerServiceDTO customerService) { + ValidationResult vr = ValidationUtils.validate(customerService, Arrays.asList( + ValidationUtils.requiredString("userId", "员工ID") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.addCustomerService(customerService); + } + + @Operation(summary = "更新客服人员") + @PutMapping("/customer-service") + public ResultDomain updateCustomerService(@RequestBody TbCustomerServiceDTO customerService) { + ValidationResult vr = ValidationUtils.validate(customerService, Arrays.asList( + ValidationUtils.requiredString("userId", "员工ID") + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.updateCustomerService(customerService); + } + + @Operation(summary = "删除客服人员") + @DeleteMapping("/customer-service/{userId}") + public ResultDomain deleteCustomerService(@PathVariable String userId) { + return chatRoomService.deleteCustomerService(userId); + } + + @Operation(summary = "分页查询客服人员") + @PostMapping("/customer-service/page") + public ResultDomain getCustomerServicePage(@RequestBody PageRequest pageRequest) { + ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( + ValidationUtils.requiredNumber("pageParam.page", "页码", 1, null), + ValidationUtils.requiredNumber("pageParam.pageSize", "每页数量", 1, 100) + )); + if (!vr.isValid()) { + return ResultDomain.failure(vr.getAllErrors()); + } + return chatRoomService.getCustomerServicePage(pageRequest); + } + + @Operation(summary = "更新客服在线状态") + @PostMapping("/customer-service/{userId}/status") + public ResultDomain updateCustomerServiceStatus(@PathVariable String userId, @RequestParam String status) { + return chatRoomService.updateCustomerServiceStatus(userId, status); + } + + @Operation(summary = "获取可接待客服列表") + @GetMapping("/customer-service/available") + public ResultDomain getAvailableCustomerServices() { + return chatRoomService.getAvailableCustomerServices(); + } + + @Operation(summary = "自动分配客服") + @PostMapping("/room/{roomId}/assign") + public ResultDomain assignCustomerService(@PathVariable String roomId) { + return chatRoomService.assignCustomerService(roomId); + } + // ========================= 微信客服消息回调 ========================= - @Operation(summary = "微信客服消息回调验证(GET)") - @GetMapping("/kefu/callback") - public String kefuCallbackVerify( - @RequestParam("msg_signature") String msgSignature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce, - @RequestParam("echostr") String echostr) { - // TODO: 验证签名并返回 echostr - // 实际应使用微信提供的加解密工具验证 - return echostr; - } + // @Operation(summary = "微信客服消息回调验证(GET)") + // @GetMapping("/kefu/callback") + // public String kefuCallbackVerify( + // @RequestParam("msg_signature") String msgSignature, + // @RequestParam("timestamp") String timestamp, + // @RequestParam("nonce") String nonce, + // @RequestParam("echostr") String echostr) { + // // TODO: 验证签名并返回 echostr + // // 实际应使用微信提供的加解密工具验证 + // return echostr; + // } - @Operation(summary = "微信客服消息回调(POST)") - @PostMapping("/kefu/callback") - public String kefuCallback( - @RequestParam("msg_signature") String msgSignature, - @RequestParam("timestamp") String timestamp, - @RequestParam("nonce") String nonce, - @RequestBody String xmlBody) { - // TODO: 解密消息,调用同步接口拉取消息 - // 收到回调后,应调用 kefuMessageService.syncMessages() 拉取新消息 - // 然后通过 processMessages() 处理消息 - return "success"; - } + // @Operation(summary = "微信客服消息回调(POST)") + // @PostMapping("/kefu/callback") + // public String kefuCallback( + // @RequestParam("msg_signature") String msgSignature, + // @RequestParam("timestamp") String timestamp, + // @RequestParam("nonce") String nonce, + // @RequestBody String xmlBody) { + // // TODO: 解密消息,调用同步接口拉取消息 + // // 收到回调后,应调用 kefuMessageService.syncMessages() 拉取新消息 + // // 然后通过 processMessages() 处理消息 + // return "success"; + // } // ========================= 词云管理 ========================= diff --git a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseController.java b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseController.java index a3fa01a6..12958bfc 100644 --- a/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseController.java +++ b/urbanLifelineServ/workcase/src/main/java/org/xyzh/workcase/controller/WorkcaseController.java @@ -1,6 +1,7 @@ package org.xyzh.workcase.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -42,6 +43,7 @@ public class WorkcaseController { // ========================= 工单管理 ========================= @Operation(summary = "创建工单") + @PreAuthorize("hasAuthority('workcase:ticket:create')") @PostMapping public ResultDomain createWorkcase(@RequestBody TbWorkcaseDTO workcase) { ValidationResult vr = ValidationUtils.validate(workcase, Arrays.asList( @@ -55,6 +57,7 @@ public class WorkcaseController { } @Operation(summary = "更新工单") + @PreAuthorize("hasAuthority('workcase:ticket:update')") @PutMapping public ResultDomain updateWorkcase(@RequestBody TbWorkcaseDTO workcase) { ValidationResult vr = ValidationUtils.validate(workcase, Arrays.asList( @@ -67,6 +70,7 @@ public class WorkcaseController { } @Operation(summary = "删除工单") + @PreAuthorize("hasAuthority('workcase:ticket:update')") @DeleteMapping("/{workcaseId}") public ResultDomain deleteWorkcase(@PathVariable String workcaseId) { TbWorkcaseDTO workcase = new TbWorkcaseDTO(); @@ -75,18 +79,21 @@ public class WorkcaseController { } @Operation(summary = "获取工单详情") + @PreAuthorize("hasAuthority('workcase:ticket:view')") @GetMapping("/{workcaseId}") public ResultDomain getWorkcaseById(@PathVariable String workcaseId) { return workcaseService.getWorkcaseById(workcaseId); } @Operation(summary = "查询工单列表") + @PreAuthorize("hasAuthority('workcase:ticket:view')") @PostMapping("/list") public ResultDomain getWorkcaseList(@RequestBody TbWorkcaseDTO filter) { return workcaseService.getWorkcaseList(filter); } @Operation(summary = "分页查询工单") + @PreAuthorize("hasAuthority('workcase:ticket:view')") @PostMapping("/page") public ResultDomain getWorkcasePage(@RequestBody PageRequest pageRequest) { ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( @@ -123,6 +130,7 @@ public class WorkcaseController { // ========================= 工单处理过程 ========================= @Operation(summary = "创建工单处理过程") + @PreAuthorize("hasAuthority('workcase:ticket:process')") @PostMapping("/process") public ResultDomain createWorkcaseProcess(@RequestBody TbWorkcaseProcessDTO process) { ValidationResult vr = ValidationUtils.validate(process, Arrays.asList( @@ -136,6 +144,7 @@ public class WorkcaseController { } @Operation(summary = "更新工单处理过程") + @PreAuthorize("hasAuthority('workcase:ticket:process')") @PutMapping("/process") public ResultDomain updateWorkcaseProcess(@RequestBody TbWorkcaseProcessDTO process) { ValidationResult vr = ValidationUtils.validate(process, Arrays.asList( @@ -148,6 +157,7 @@ public class WorkcaseController { } @Operation(summary = "删除工单处理过程") + @PreAuthorize("hasAuthority('workcase:ticket:process')") @DeleteMapping("/process/{processId}") public ResultDomain deleteWorkcaseProcess(@PathVariable String processId) { TbWorkcaseProcessDTO process = new TbWorkcaseProcessDTO(); @@ -156,12 +166,14 @@ public class WorkcaseController { } @Operation(summary = "查询工单处理过程列表") + @PreAuthorize("hasAuthority('workcase:ticket:process')") @PostMapping("/process/list") public ResultDomain getWorkcaseProcessList(@RequestBody TbWorkcaseProcessDTO filter) { return workcaseService.getWorkcaseProcessList(filter); } @Operation(summary = "分页查询工单处理过程") + @PreAuthorize("hasAuthority('workcase:ticket:process')") @PostMapping("/process/page") public ResultDomain getWorkcaseProcessPage(@RequestBody PageRequest pageRequest) { ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( @@ -177,6 +189,7 @@ public class WorkcaseController { // ========================= 工单设备管理 ========================= @Operation(summary = "创建工单设备") + @PreAuthorize("hasAuthority('workcase:ticket:device')") @PostMapping("/device") public ResultDomain createWorkcaseDevice(@RequestBody TbWorkcaseDeviceDTO device) { ValidationResult vr = ValidationUtils.validate(device, Arrays.asList( @@ -190,6 +203,7 @@ public class WorkcaseController { } @Operation(summary = "更新工单设备") + @PreAuthorize("hasAuthority('workcase:ticket:device')") @PutMapping("/device") public ResultDomain updateWorkcaseDevice(@RequestBody TbWorkcaseDeviceDTO device) { ValidationResult vr = ValidationUtils.validate(device, Arrays.asList( @@ -203,6 +217,7 @@ public class WorkcaseController { } @Operation(summary = "删除工单设备") + @PreAuthorize("hasAuthority('workcase:ticket:device')") @DeleteMapping("/device/{workcaseId}/{device}") public ResultDomain deleteWorkcaseDevice(@PathVariable String workcaseId, @PathVariable String device) { TbWorkcaseDeviceDTO deviceDTO = new TbWorkcaseDeviceDTO(); @@ -212,12 +227,14 @@ public class WorkcaseController { } @Operation(summary = "查询工单设备列表") + @PreAuthorize("hasAuthority('workcase:ticket:device')") @PostMapping("/device/list") public ResultDomain getWorkcaseDeviceList(@RequestBody TbWorkcaseDeviceDTO filter) { return workcaseService.getWorkcaseDeviceList(filter); } @Operation(summary = "分页查询工单设备") + @PreAuthorize("hasAuthority('workcase:ticket:device')") @PostMapping("/device/page") public ResultDomain getWorkcaseDevicePage(@RequestBody PageRequest pageRequest) { ValidationResult vr = ValidationUtils.validate(pageRequest, Arrays.asList( diff --git a/urbanLifelineServ/workcase/src/main/resources/application.yml b/urbanLifelineServ/workcase/src/main/resources/application.yml index 97538871..54306e66 100644 --- a/urbanLifelineServ/workcase/src/main/resources/application.yml +++ b/urbanLifelineServ/workcase/src/main/resources/application.yml @@ -7,7 +7,7 @@ server: # ================== Auth ==================== auth: enabled: true - gate-way: true + gateway-mode: true whitelist: - /swagger-ui/** - /swagger-ui.html diff --git a/urbanLifelineWeb/.vscode/launch.json b/urbanLifelineWeb/.vscode/launch.json index d5072dff..df833abc 100644 --- a/urbanLifelineWeb/.vscode/launch.json +++ b/urbanLifelineWeb/.vscode/launch.json @@ -31,6 +31,21 @@ "clear": false } }, + { + "name": "启动 workcase 开发服务器", + "type": "node-terminal", + "request": "launch", + "command": "npm run dev", + "cwd": "${workspaceFolder}/packages/workcase", + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "new", + "showReuseMessage": true, + "clear": false + } + }, { "name": "启动 Bidding 开发服务器", "type": "node-terminal", @@ -53,7 +68,8 @@ "configurations": [ "启动 Shared 开发服务器", "启动 Platform 开发服务器", - "启动 Bidding 开发服务器" + "启动 workcase 开发服务器", + // "启动 Bidding 开发服务器" ], "stopAll": true, "presentation": { diff --git a/urbanLifelineWeb/packages/bidding/src/types/shared.d.ts b/urbanLifelineWeb/packages/bidding/src/types/shared.d.ts index 5559c62b..488ce0f6 100644 --- a/urbanLifelineWeb/packages/bidding/src/types/shared.d.ts +++ b/urbanLifelineWeb/packages/bidding/src/types/shared.d.ts @@ -98,8 +98,10 @@ declare module 'shared/api/ai' { declare module 'shared/api/workcase' { export const workcaseAPI: any + export const workcaseChatAPI: any } + // ============ types模块 ================== declare module 'shared/types' { import type { BaseDTO } from '../../../shared/src/types/base' @@ -143,15 +145,30 @@ declare module 'shared/types' { TbWorkcaseDeviceDTO, // 聊天室相关 TbChatRoomDTO, - TbChatMessageDTO, + TbChatRoomMessageDTO, TbChatRoomMemberDTO, + TbVideoMeetingDTO, + TbMeetingParticipantDTO, + TbMeetingTranscriptionDTO, ChatRoomVO, ChatMessageVO, + ChatRoomMessageVO, ChatMemberVO, VideoMeetingVO, + MeetingParticipantVO, SendMessageParam, CreateMeetingParam, - MarkReadParam + MarkReadParam, + // 客服相关 + TbCustomerServiceDTO, + CustomerServiceVO, + // 词云 + TbWordCloudDTO, + // 来客相关 + TbGuestDTO, + GuestVO, + CustomerVO, + ConversationVO } from '../../../shared/src/types/workcase' // 重新导出 menu diff --git a/urbanLifelineWeb/packages/platform/pnpm-lock.yaml b/urbanLifelineWeb/packages/platform/pnpm-lock.yaml index 5a1c1253..bbe29ff3 100644 --- a/urbanLifelineWeb/packages/platform/pnpm-lock.yaml +++ b/urbanLifelineWeb/packages/platform/pnpm-lock.yaml @@ -17,6 +17,9 @@ dependencies: element-plus: specifier: ^2.12.0 version: 2.12.0(vue@3.5.25) + lucide-vue-next: + specifier: ^0.561.0 + version: 0.561.0(vue@3.5.25) pinia: specifier: ^2.2.8 version: 2.3.1(typescript@5.9.3)(vue@3.5.25) @@ -1464,6 +1467,14 @@ packages: yallist: 3.1.1 dev: true + /lucide-vue-next@0.561.0(vue@3.5.25): + resolution: {integrity: sha512-c5HUckO0qHklVSOf/0vaSR3pEb8fYImRDCRDLde56uqS9js0D/e3RAvq0/YFWjkmyOBKCb0/IdskdoHZQEkT5g==} + peerDependencies: + vue: '>=3.0.1' + dependencies: + vue: 3.5.25(typescript@5.9.3) + dev: false + /magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} dependencies: diff --git a/urbanLifelineWeb/packages/platform/src/types/shared.d.ts b/urbanLifelineWeb/packages/platform/src/types/shared.d.ts index 5559c62b..488ce0f6 100644 --- a/urbanLifelineWeb/packages/platform/src/types/shared.d.ts +++ b/urbanLifelineWeb/packages/platform/src/types/shared.d.ts @@ -98,8 +98,10 @@ declare module 'shared/api/ai' { declare module 'shared/api/workcase' { export const workcaseAPI: any + export const workcaseChatAPI: any } + // ============ types模块 ================== declare module 'shared/types' { import type { BaseDTO } from '../../../shared/src/types/base' @@ -143,15 +145,30 @@ declare module 'shared/types' { TbWorkcaseDeviceDTO, // 聊天室相关 TbChatRoomDTO, - TbChatMessageDTO, + TbChatRoomMessageDTO, TbChatRoomMemberDTO, + TbVideoMeetingDTO, + TbMeetingParticipantDTO, + TbMeetingTranscriptionDTO, ChatRoomVO, ChatMessageVO, + ChatRoomMessageVO, ChatMemberVO, VideoMeetingVO, + MeetingParticipantVO, SendMessageParam, CreateMeetingParam, - MarkReadParam + MarkReadParam, + // 客服相关 + TbCustomerServiceDTO, + CustomerServiceVO, + // 词云 + TbWordCloudDTO, + // 来客相关 + TbGuestDTO, + GuestVO, + CustomerVO, + ConversationVO } from '../../../shared/src/types/workcase' // 重新导出 menu diff --git a/urbanLifelineWeb/packages/shared/package.json b/urbanLifelineWeb/packages/shared/package.json index d91895ec..2558e938 100644 --- a/urbanLifelineWeb/packages/shared/package.json +++ b/urbanLifelineWeb/packages/shared/package.json @@ -11,11 +11,13 @@ }, "dependencies": { "@element-plus/icons-vue": "^2.3.2", + "@stomp/stompjs": "^7.2.1", "cors": "^2.8.5", "element-plus": "^2.12.0", "express": "^4.18.2", "lucide-vue-next": "^0.561.0", "ofetch": "^1.4.1", + "sockjs-client": "^1.6.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, diff --git a/urbanLifelineWeb/packages/shared/pnpm-lock.yaml b/urbanLifelineWeb/packages/shared/pnpm-lock.yaml index 2dba20e9..20c4b1cd 100644 --- a/urbanLifelineWeb/packages/shared/pnpm-lock.yaml +++ b/urbanLifelineWeb/packages/shared/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@element-plus/icons-vue': specifier: ^2.3.2 version: 2.3.2(vue@3.5.25) + '@stomp/stompjs': + specifier: ^7.2.1 + version: 7.2.1 cors: specifier: ^2.8.5 version: 2.8.5 @@ -17,9 +20,15 @@ dependencies: express: specifier: ^4.18.2 version: 4.22.1 + lucide-vue-next: + specifier: ^0.561.0 + version: 0.561.0(vue@3.5.25) ofetch: specifier: ^1.4.1 version: 1.5.1 + sockjs-client: + specifier: ^1.6.1 + version: 1.6.1 vue: specifier: ^3.5.13 version: 3.5.25(typescript@5.9.3) @@ -1000,6 +1009,10 @@ packages: dev: true optional: true + /@stomp/stompjs@7.2.1: + resolution: {integrity: sha512-DLd/WeicnHS5SsWWSk3x6/pcivqchNaEvg9UEGVqAcfYEBVmS9D6980ckXjTtfpXLjdLDsd96M7IuX4w7nzq5g==} + dev: false + /@sxzz/popperjs-es@2.11.7: resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} dev: false @@ -1464,6 +1477,17 @@ packages: ms: 2.0.0 dev: false + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + dev: false + /debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1729,6 +1753,11 @@ packages: engines: {node: '>= 0.6'} dev: false + /eventsource@2.0.2: + resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} + engines: {node: '>=12.0.0'} + dev: false + /express@4.22.1: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} @@ -1768,6 +1797,13 @@ packages: - supports-color dev: false + /faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + dev: false + /fdir@6.5.0(picomatch@4.0.3): resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1976,6 +2012,10 @@ packages: toidentifier: 1.0.1 dev: false + /http-parser-js@0.5.10: + resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + dev: false + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -2262,6 +2302,14 @@ packages: yallist: 3.1.1 dev: true + /lucide-vue-next@0.561.0(vue@3.5.25): + resolution: {integrity: sha512-c5HUckO0qHklVSOf/0vaSR3pEb8fYImRDCRDLde56uqS9js0D/e3RAvq0/YFWjkmyOBKCb0/IdskdoHZQEkT5g==} + peerDependencies: + vue: '>=3.0.1' + dependencies: + vue: 3.5.25(typescript@5.9.3) + dev: false + /magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} dependencies: @@ -2533,6 +2581,10 @@ packages: side-channel: 1.1.0 dev: false + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: false + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2588,6 +2640,10 @@ packages: set-function-name: 2.0.2 dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: false + /resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -3054,6 +3110,19 @@ packages: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + /sockjs-client@1.6.1: + resolution: {integrity: sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==} + engines: {node: '>=12'} + dependencies: + debug: 3.2.7 + eventsource: 2.0.2 + faye-websocket: 0.11.4 + inherits: 2.0.4 + url-parse: 1.5.10 + transitivePeerDependencies: + - supports-color + dev: false + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3294,6 +3363,13 @@ packages: picocolors: 1.1.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: false + /utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -3407,6 +3483,20 @@ packages: '@vue/shared': 3.5.25 typescript: 5.9.3 + /websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.10 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + dev: false + + /websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + dev: false + /which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} diff --git a/urbanLifelineWeb/packages/shared/src/api/workcase/index.ts b/urbanLifelineWeb/packages/shared/src/api/workcase/index.ts index 945f32a8..ff366b1e 100644 --- a/urbanLifelineWeb/packages/shared/src/api/workcase/index.ts +++ b/urbanLifelineWeb/packages/shared/src/api/workcase/index.ts @@ -1 +1,2 @@ export * from './workcase' +export * from './workcaseChat' \ No newline at end of file diff --git a/urbanLifelineWeb/packages/shared/src/api/workcase/workcaseChat.ts b/urbanLifelineWeb/packages/shared/src/api/workcase/workcaseChat.ts new file mode 100644 index 00000000..f4df96a5 --- /dev/null +++ b/urbanLifelineWeb/packages/shared/src/api/workcase/workcaseChat.ts @@ -0,0 +1,301 @@ +import { api } from '@/api/index' +import type { ResultDomain, PageRequest } from '@/types' +import type { TbChat, TbChatMessage, ChatPrepareData } from '@/types/ai' +import type { TbWorkcaseDTO } from '@/types/workcase/workcase' +import type { + TbChatRoomDTO, + TbChatRoomMemberDTO, + TbChatRoomMessageDTO, + TbCustomerServiceDTO, + TbWordCloudDTO, + ChatRoomVO, + ChatMemberVO, + ChatRoomMessageVO, + CustomerServiceVO +} from '@/types/workcase/chatRoom' + +/** + * @description 工单对话相关接口 + * @filename workcaseChat.ts + * @author cascade + * @copyright xyzh + * @since 2025-12-22 + */ +export const workcaseChatAPI = { + baseUrl: '/urban-lifeline/workcase/chat', + + // ====================== AI对话管理 ====================== + + /** + * 创建对话 + */ + async createChat(chat: TbChat): Promise> { + const response = await api.post(`${this.baseUrl}`, chat) + return response.data + }, + + /** + * 更新对话 + */ + async updateChat(chat: TbChat): Promise> { + const response = await api.put(`${this.baseUrl}`, chat) + return response.data + }, + + /** + * 查询对话列表 + */ + async getChatList(filter: TbChat): Promise> { + const response = await api.post(`${this.baseUrl}/list`, filter) + return response.data + }, + + /** + * 获取对话消息列表 + */ + async getChatMessageList(filter: TbChat): Promise> { + const response = await api.post(`${this.baseUrl}/message/list`, filter) + return response.data + }, + + /** + * 准备对话会话 + */ + async prepareChatMessageSession(prepareData: ChatPrepareData): Promise> { + const response = await api.post(`${this.baseUrl}/prepare`, prepareData) + return response.data + }, + + /** + * 流式对话(SSE)- 返回EventSource URL + */ + getStreamUrl(sessionId: string): string { + return `${this.baseUrl}/stream/${sessionId}` + }, + + /** + * 停止对话 + */ + async stopChat(filter: TbChat, taskId: string): Promise> { + const response = await api.post(`${this.baseUrl}/stop/${taskId}`, filter) + return response.data + }, + + /** + * 评论对话消息 + */ + async commentChatMessage(filter: TbChat, messageId: string, comment: string): Promise> { + const response = await api.post(`${this.baseUrl}/comment`, filter, { + params: { messageId, comment } + }) + return response.data + }, + + // ====================== 对话分析 ====================== + + /** + * 分析对话(AI预填工单信息) + */ + async analyzeChat(chatId: string): Promise> { + const response = await api.get(`${this.baseUrl}/analyze/${chatId}`) + return response.data + }, + + /** + * 总结对话 + */ + async summaryChat(chatId: string): Promise> { + const response = await api.post(`${this.baseUrl}/summary/${chatId}`) + return response.data + }, + + // ====================== ChatRoom聊天室管理 ====================== + + /** + * 创建聊天室 + */ + async createChatRoom(chatRoom: TbChatRoomDTO): Promise> { + const response = await api.post(`${this.baseUrl}/room`, chatRoom) + return response.data + }, + + /** + * 更新聊天室 + */ + async updateChatRoom(chatRoom: TbChatRoomDTO): Promise> { + const response = await api.put(`${this.baseUrl}/room`, chatRoom) + return response.data + }, + + /** + * 关闭聊天室 + */ + async closeChatRoom(roomId: string, closedBy: string): Promise> { + const response = await api.post(`${this.baseUrl}/room/${roomId}/close`, null, { + params: { closedBy } + }) + return response.data + }, + + /** + * 获取聊天室详情 + */ + async getChatRoomById(roomId: string): Promise> { + const response = await api.get(`${this.baseUrl}/room/${roomId}`) + return response.data + }, + + /** + * 分页查询聊天室 + */ + async getChatRoomPage(pageRequest: PageRequest): Promise> { + const response = await api.post(`${this.baseUrl}/room/page`, pageRequest) + return response.data + }, + + // ====================== ChatRoom成员管理 ====================== + + /** + * 添加聊天室成员 + */ + async addChatRoomMember(member: TbChatRoomMemberDTO): Promise> { + const response = await api.post(`${this.baseUrl}/room/member`, member) + return response.data + }, + + /** + * 移除聊天室成员 + */ + async removeChatRoomMember(memberId: string): Promise> { + const response = await api.delete(`${this.baseUrl}/room/member/${memberId}`) + return response.data + }, + + /** + * 获取聊天室成员列表 + */ + async getChatRoomMemberList(roomId: string): Promise> { + const response = await api.get(`${this.baseUrl}/room/${roomId}/members`) + return response.data + }, + + // ====================== ChatRoom消息管理 ====================== + + /** + * 发送聊天室消息 + */ + async sendMessage(message: TbChatRoomMessageDTO): Promise> { + const response = await api.post(`${this.baseUrl}/room/message`, message) + return response.data + }, + + /** + * 分页查询聊天室消息 + */ + async getChatMessagePage(pageRequest: PageRequest): Promise> { + const response = await api.post(`${this.baseUrl}/room/message/page`, pageRequest) + return response.data + }, + + /** + * 删除聊天室消息 + */ + async deleteMessage(messageId: string): Promise> { + const response = await api.delete(`${this.baseUrl}/room/message/${messageId}`) + return response.data + }, + + // ====================== 客服人员管理 ====================== + + /** + * 添加客服人员 + */ + async addCustomerService(customerService: TbCustomerServiceDTO): Promise> { + const response = await api.post(`${this.baseUrl}/customer-service`, customerService) + return response.data + }, + + /** + * 更新客服人员 + */ + async updateCustomerService(customerService: TbCustomerServiceDTO): Promise> { + const response = await api.put(`${this.baseUrl}/customer-service`, customerService) + return response.data + }, + + /** + * 删除客服人员 + */ + async deleteCustomerService(userId: string): Promise> { + const response = await api.delete(`${this.baseUrl}/customer-service/${userId}`) + return response.data + }, + + /** + * 分页查询客服人员 + */ + async getCustomerServicePage(pageRequest: PageRequest): Promise> { + const response = await api.post(`${this.baseUrl}/customer-service/page`, pageRequest) + return response.data + }, + + /** + * 更新客服在线状态 + */ + async updateCustomerServiceStatus(userId: string, status: string): Promise> { + const response = await api.post(`${this.baseUrl}/customer-service/${userId}/status`, null, { + params: { status } + }) + return response.data + }, + + /** + * 获取可接待客服列表 + */ + async getAvailableCustomerServices(): Promise> { + const response = await api.get(`${this.baseUrl}/customer-service/available`) + return response.data + }, + + /** + * 自动分配客服 + */ + async assignCustomerService(roomId: string): Promise> { + const response = await api.post(`${this.baseUrl}/room/${roomId}/assign`) + return response.data + }, + + // ====================== 词云管理 ====================== + + /** + * 添加词云 + */ + async addWordCloud(wordCloud: TbWordCloudDTO): Promise> { + const response = await api.post(`${this.baseUrl}/wordcloud`, wordCloud) + return response.data + }, + + /** + * 更新词云 + */ + async updateWordCloud(wordCloud: TbWordCloudDTO): Promise> { + const response = await api.put(`${this.baseUrl}/wordcloud`, wordCloud) + return response.data + }, + + /** + * 查询词云列表 + */ + async getWordCloudList(filter: TbWordCloudDTO): Promise> { + const response = await api.post(`${this.baseUrl}/wordcloud/list`, filter) + return response.data + }, + + /** + * 分页查询词云 + */ + async getWordCloudPage(pageRequest: PageRequest): Promise> { + const response = await api.post(`${this.baseUrl}/wordcloud/page`, pageRequest) + return response.data + } +} diff --git a/urbanLifelineWeb/packages/shared/src/components/chatRoom/chatRoom/ChatRoom.vue b/urbanLifelineWeb/packages/shared/src/components/chatRoom/chatRoom/ChatRoom.vue index 7f788e75..44138b7a 100644 --- a/urbanLifelineWeb/packages/shared/src/components/chatRoom/chatRoom/ChatRoom.vue +++ b/urbanLifelineWeb/packages/shared/src/components/chatRoom/chatRoom/ChatRoom.vue @@ -116,7 +116,7 @@