From 0fb7a4ffb282f771e34f2285fffaccb0c4ab789a Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Sat, 27 Dec 2025 17:34:19 +0800 Subject: [PATCH] =?UTF-8?q?https=20=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/sql/initDataPermission.sql | 10 +- .../docker/urbanlifeline/docker-compose.yml | 22 +-- .../auth/service/impl/AuthServiceImpl.java | 164 ++++++++++++++---- .../src/main/resources/application-dev.yml | 4 +- .../layouts/SidebarLayout/SidebarLayout.vue | 2 +- .../packages/bidding/vite.config.ts | 11 +- .../AdminSidebarLayout/AdminSidebarLayout.vue | 2 +- .../layouts/SidebarLayout/SidebarLayout.vue | 2 +- .../packages/platform/src/router/index.ts | 6 +- .../packages/platform/vite.config.ts | 8 +- .../packages/shared/vite.config.ts | 18 +- .../workcase/localSharedImportMap.js | 37 +--- .../layouts/SidebarLayout/SidebarLayout.vue | 2 +- .../packages/workcase/src/router/index.ts | 25 ++- .../public/JitsiMeeting/JitsiMeetingView.vue | 99 ++--------- .../packages/workcase/vite.config.ts | 13 +- .../pages/chatRoom/chatRoom/chatRoom.uvue | 6 +- .../packages/workcase_wechat/pnpm-lock.yaml | 95 ---------- 18 files changed, 233 insertions(+), 293 deletions(-) diff --git a/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql b/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql index c84ce017..2afabc29 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/initDataPermission.sql @@ -285,12 +285,7 @@ INSERT INTO sys.tb_sys_view ( 'route', NULL, 'workcase', NULL, 162, '工单操作日志', 'system', now(), false), ('VIEW-W109', 'view_workcase_admin_log_system', '系统日志', 'view_workcase_admin_log', '/admin/log/system', 'admin/log/systemLog/SystemLogView.vue', 'Settings', 1, - 'route', NULL, 'workcase', NULL, 163, '系统运行日志', 'system', now(), false), - --- Jitsi视频会议独立页面(支持URL参数token认证,用于小程序和外部链接访问) -('VIEW-W003', 'view_workcase_jitsi_meeting', 'Jitsi视频会议', NULL, '/meeting', 'public/JitsiMeeting/JitsiMeetingView.vue', 'Video', 1, - 'route', NULL, 'workcase', 'BlankLayout', 25, 'Jitsi视频会议独立页面,支持URL参数token认证', 'system', now(), false); - + 'route', NULL, 'workcase', NULL, 163, '系统运行日志', 'system', now(), false); -- ============================= -- 6. 角色权限关联(超级管理员拥有所有权限) -- ============================= @@ -432,9 +427,6 @@ INSERT INTO sys.tb_sys_view_permission ( ('VP-W108', 'view_workcase_admin_log_workcase', 'perm_workcase_log', 'system', NULL, now(), false), ('VP-W109', 'view_workcase_admin_log_system', 'perm_workcase_log', 'system', NULL, now(), false), --- Jitsi视频会议页面关联会议权限 -('VP-W003', 'view_workcase_jitsi_meeting', 'perm_meeting_join', 'system', NULL, now(), false); - -- -- 用户管理视图关联用户权限(已注释,因为view_user被注释掉了) -- -- ('VP-0001', 'view_user', 'perm_user_view', 'system', NULL, now(), false), -- -- ('VP-0002', 'view_user', 'perm_user_create', 'system', NULL, now(), false), diff --git a/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml b/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml index 800c6c4d..a0cb4e24 100644 --- a/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml +++ b/urbanLifelineServ/.bin/docker/urbanlifeline/docker-compose.yml @@ -105,15 +105,15 @@ services: networks: - urban-lifeline ports: - - "8280:80" # 保留原 HTTP 端口 - - "8443:443" # 保留原 HTTPS 端口(仅保留映射,实际禁用 HTTPS) + - "8280:80" # HTTP 端口(通过 Nginx 反向代理) + - "8443:443" # HTTPS 端口 environment: - # 基础配置(局域网访问) + # 基础配置(Jitsi容器使用HTTP,由Nginx提供HTTPS) TZ: Asia/Shanghai - # 关键:使用 http:// 协议的完整 URL - PUBLIC_URL: 192.168.0.253:8280 - - # 关键:禁用 HTTPS,让容器生成 ws:// 而不是 wss:// + # PUBLIC_URL设置为独立子域名 + PUBLIC_URL: https://org.xyzh.yslg.jitsi + + # 禁用容器内部HTTPS(由Nginx统一处理HTTPS) ENABLE_HTTPS: 0 ENABLE_HTTP_REDIRECT: 0 DISABLE_HTTPS: 1 @@ -142,7 +142,7 @@ services: JWT_APP_SECRET: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256 JWT_ACCEPTED_ISSUERS: urbanLifeline JWT_ACCEPTED_AUDIENCES: jitsi - JWT_ASAP_KEYSERVER: https://192.168.0.253:8280/ + JWT_ASAP_KEYSERVER: https://org.xyzh.yslg.jitsi/ JWT_ALLOW_EMPTY: 0 JWT_AUTH_TYPE: token JWT_TOKEN_AUTH_MODULE: token_verification @@ -154,9 +154,9 @@ services: ENABLE_XMPP_WEBSOCKET: 1 ENABLE_SCTP: 1 - # 日志/HTTPS 配置 + # 不使用Let's Encrypt(使用mkcert证书) ENABLE_LETSENCRYPT: 0 - LETSENCRYPT_DOMAIN: 192.168.0.253 + LETSENCRYPT_DOMAIN: org.xyzh.yslg.jitsi volumes: - ../../../.data/docker/jitsi/web:/config @@ -217,7 +217,7 @@ services: LOG_LEVEL: info # 公共URL(局域网访问) - PUBLIC_URL: 192.168.0.253:8280 + PUBLIC_URL: https://org.xyzh.yslg.jitsi volumes: - ../../../.data/docker/jitsi/prosody/config:/config diff --git a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java index 342ee985..89ef6b80 100644 --- a/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java +++ b/urbanLifelineServ/auth/src/main/java/org/xyzh/auth/service/impl/AuthServiceImpl.java @@ -5,9 +5,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.xyzh.api.auth.service.AuthService; import org.xyzh.api.system.service.SysUserService; import org.xyzh.api.system.service.ModulePermissionService; +import org.xyzh.api.system.service.GuestService; import org.xyzh.api.system.vo.SysUserVO; import org.xyzh.api.system.vo.PermissionVO; import org.xyzh.api.system.vo.UserDeptRoleVO; +import org.xyzh.common.dto.sys.TbGuestDTO; import org.xyzh.auth.enums.UserStatus; import org.xyzh.auth.strategy.LoginStrategyFactory; import org.xyzh.auth.strategy.LoginStrategy; @@ -64,6 +66,9 @@ public class AuthServiceImpl implements AuthService{ @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0) private ModulePermissionService modulePermissionService; + @DubboReference(version = "1.0.0", group = "system", timeout = 5000, check = false, retries = 0) + private GuestService guestService; + @Autowired private LoginStrategyFactory loginStrategyFactory; @@ -412,63 +417,164 @@ public class AuthServiceImpl implements AuthService{ if (token == null || token.trim().isEmpty()) { return ResultDomain.failure("Token不能为空"); } - + // 1. 验证当前Token是否有效 if (jwtTokenUtil.isTokenExpired(token)) { return ResultDomain.failure("Token已过期"); } - - // 2. 从Redis获取登录信息 + + // 2. 从JWT中提取用户ID和登录类型 + String userId = jwtTokenUtil.getUserIdFromToken(token); + String loginType = jwtTokenUtil.getClaimFromToken(token, claims -> claims.get("loginType", String.class)); + + if (userId == null || userId.trim().isEmpty()) { + return ResultDomain.failure("Token信息不完整"); + } + + logger.info("Token刷新请求:userId={}, loginType={}", userId, loginType); + + // 3. 尝试从Redis获取登录信息(优先使用缓存) String loginKey = "login:token:" + token; String loginJson = redisService.get(loginKey, String.class); - if (loginJson == null) { - return ResultDomain.failure("登录信息已失效"); + LoginDomain oldLoginDomain = null; + + if (loginJson != null) { + oldLoginDomain = JSON.parseObject(loginJson, LoginDomain.class); + if (loginType == null && oldLoginDomain != null) { + loginType = oldLoginDomain.getLoginType(); + } } - - LoginDomain oldLoginDomain = JSON.parseObject(loginJson, LoginDomain.class); - - // 3. 获取用户最新信息 - ResultDomain userInfoResult = userService.getUserInfo(oldLoginDomain.getUser().getUserId()); - if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) { - return ResultDomain.failure("获取用户信息失败"); + + // 4. 根据登录类型获取最新用户信息 + LoginDomain newLoginDomain; + + if ("wechat_miniprogram".equals(loginType)) { + // 来客(小程序)用户:从guest表获取信息 + TbGuestDTO guestDTO = new TbGuestDTO(); + guestDTO.setUserId(userId); + ResultDomain guestResult = guestService.selectGuestOne(guestDTO); + if (!guestResult.getSuccess() || guestResult.getData() == null) { + return ResultDomain.failure("获取来客信息失败"); + } + + TbGuestDTO guest = guestResult.getData(); + newLoginDomain = buildGuestLoginDomain(guest, loginType); + + } else { + // 普通用户:从sys_user表获取信息 + ResultDomain userInfoResult = userService.getUserInfo(userId); + if (!userInfoResult.getSuccess() || userInfoResult.getData() == null) { + return ResultDomain.failure("获取用户信息失败"); + } + + SysUserVO userInfo = userInfoResult.getData(); + String effectiveLoginType = (loginType != null) ? loginType : "password"; + newLoginDomain = buildLoginDomain(userInfo, effectiveLoginType, clientIp); } - - SysUserVO userInfo = userInfoResult.getData(); - - // 4. 重新构建LoginDomain - LoginDomain newLoginDomain = buildLoginDomain(userInfo, oldLoginDomain.getLoginType(), clientIp); + if (newLoginDomain == null) { return ResultDomain.failure("构建登录信息失败"); } - + // 5. 生成新Token String newToken = jwtTokenUtil.generateToken(newLoginDomain); newLoginDomain.setToken(newToken); - - // 6. 删除旧的Token信息 - redisService.delete(loginKey); - + + // 6. 删除旧的Token信息(如果存在) + if (loginJson != null) { + redisService.delete(loginKey); + } + // 7. 存储新的登录信息 String newLoginKey = "login:token:" + newToken; redisService.set(newLoginKey, JSON.toJSONString(newLoginDomain), 24, TimeUnit.HOURS); - + // 8. 更新用户登录状态 - String userLoginKey = "login:user:" + userInfo.getUserId(); + String userLoginKey = "login:user:" + userId; redisService.set(userLoginKey, newToken, 24, TimeUnit.HOURS); - - logger.info("Token刷新成功:userId={}, oldToken={}, newToken={}", - userInfo.getUserId(), + + logger.info("Token刷新成功:userId={}, loginType={}, oldToken={}, newToken={}", + userId, + loginType, token.substring(0, Math.min(10, token.length())) + "...", newToken.substring(0, Math.min(10, newToken.length())) + "..."); - + return ResultDomain.success("Token刷新成功", newLoginDomain); - + } catch (Exception e) { logger.error("Token刷新失败", e); return ResultDomain.failure("Token刷新失败: " + e.getMessage()); } } + /** + * 从来客信息构造LoginDomain(用于token刷新) + */ + private LoginDomain buildGuestLoginDomain(TbGuestDTO guest, String loginType) { + try { + LoginDomain loginDomain = new LoginDomain(); + + // 构造TbSysUserDTO,status设为guest + TbSysUserDTO userDTO = new TbSysUserDTO(); + userDTO.setUserId(guest.getUserId()); + userDTO.setPhone(guest.getPhone()); + userDTO.setEmail(guest.getEmail()); + userDTO.setWechatId(guest.getWechatId()); + userDTO.setStatus("guest"); // 来客特殊状态 + loginDomain.setUser(userDTO); + + // 构造TbSysUserInfoDTO + TbSysUserInfoDTO userInfoDTO = new TbSysUserInfoDTO(); + userInfoDTO.setUserId(guest.getUserId()); + userInfoDTO.setUsername(guest.getName() != null ? 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(loginType != null ? loginType : "wechat_miniprogram"); + + logger.info("构建来客LoginDomain成功:userId={}, name={}", guest.getUserId(), guest.getName()); + + return loginDomain; + + } catch (Exception e) { + logger.error("构建来客LoginDomain失败:userId={}", guest.getUserId(), e); + return null; + } + } + @Override public ResultDomain logout(String token) { try { diff --git a/urbanLifelineServ/workcase/src/main/resources/application-dev.yml b/urbanLifelineServ/workcase/src/main/resources/application-dev.yml index dc96a2ed..f10a3546 100644 --- a/urbanLifelineServ/workcase/src/main/resources/application-dev.yml +++ b/urbanLifelineServ/workcase/src/main/resources/application-dev.yml @@ -104,8 +104,8 @@ jitsi: # 注意:HS256算法要求密钥长度至少32字节(256 bits) secret: urbanLifeline-jitsi-secret-key-2025-production-safe-hs256 server: - # Jitsi Meet服务器地址(Docker部署在本地8280端口) - url: http://localhost:8280 + # Jitsi Meet服务器地址(独立子域名) + url: https://org.xyzh.yslg.jitsi token: # JWT Token有效期(毫秒)- 默认2小时 expiration: 7200000 \ No newline at end of file diff --git a/urbanLifelineWeb/packages/bidding/src/layouts/SidebarLayout/SidebarLayout.vue b/urbanLifelineWeb/packages/bidding/src/layouts/SidebarLayout/SidebarLayout.vue index 000bb15c..fd579891 100644 --- a/urbanLifelineWeb/packages/bidding/src/layouts/SidebarLayout/SidebarLayout.vue +++ b/urbanLifelineWeb/packages/bidding/src/layouts/SidebarLayout/SidebarLayout.vue @@ -219,7 +219,7 @@ const handleUserCommand = (command: string) => { case 'logout': localStorage.clear() ElMessage.success('退出成功') - router.push('/login') + router.push('/platform/login') break } } diff --git a/urbanLifelineWeb/packages/bidding/vite.config.ts b/urbanLifelineWeb/packages/bidding/vite.config.ts index ff110c70..da3ee2ec 100644 --- a/urbanLifelineWeb/packages/bidding/vite.config.ts +++ b/urbanLifelineWeb/packages/bidding/vite.config.ts @@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' +import fs from 'fs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -38,16 +39,16 @@ export default defineConfig(({ mode }) => ({ host: true, cors: true, open: '/bidding/', // 开发时自动打开到 /bidding/ 路径 + // HTTPS 配置(使用 mkcert 生成的本地开发证书) + https: { + key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'), + cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem') + }, proxy: { '/api': { target: 'http://localhost:8180', changeOrigin: true, rewrite: (path: string) => path.replace(/^\/api/, '') - }, - // 代理共享模块请求到 shared 服务 - '/shared': { - target: 'http://localhost:7000', - changeOrigin: true } } }, diff --git a/urbanLifelineWeb/packages/platform/src/layouts/AdminSidebarLayout/AdminSidebarLayout.vue b/urbanLifelineWeb/packages/platform/src/layouts/AdminSidebarLayout/AdminSidebarLayout.vue index 4ea8e16f..da220064 100644 --- a/urbanLifelineWeb/packages/platform/src/layouts/AdminSidebarLayout/AdminSidebarLayout.vue +++ b/urbanLifelineWeb/packages/platform/src/layouts/AdminSidebarLayout/AdminSidebarLayout.vue @@ -225,7 +225,7 @@ const handleUserCommand = (command: string) => { case 'logout': localStorage.clear() ElMessage.success('退出成功') - router.push('/login') + router.push('/platform/login') break } } diff --git a/urbanLifelineWeb/packages/platform/src/layouts/SidebarLayout/SidebarLayout.vue b/urbanLifelineWeb/packages/platform/src/layouts/SidebarLayout/SidebarLayout.vue index 78c45670..0ef4cbf7 100644 --- a/urbanLifelineWeb/packages/platform/src/layouts/SidebarLayout/SidebarLayout.vue +++ b/urbanLifelineWeb/packages/platform/src/layouts/SidebarLayout/SidebarLayout.vue @@ -243,7 +243,7 @@ const handleUserCommand = (command: string) => { case 'logout': localStorage.clear() ElMessage.success('退出成功') - router.push('/login') + router.push('/platform/login') break } } diff --git a/urbanLifelineWeb/packages/platform/src/router/index.ts b/urbanLifelineWeb/packages/platform/src/router/index.ts index 407ab466..af76e120 100644 --- a/urbanLifelineWeb/packages/platform/src/router/index.ts +++ b/urbanLifelineWeb/packages/platform/src/router/index.ts @@ -5,7 +5,7 @@ import { loadRoutesFromStorage } from './dynamicRoute' // platform应用的动态路由会根据layout字段自动添加,不需要预定义Root布局 const routes: RouteRecordRaw[] = [ { - path: '/login', + path: '/platform/login', name: 'Login', component: () => import('@/views/public/Login/Login.vue'), meta: { @@ -37,13 +37,13 @@ router.beforeEach((to, from, next) => { if (requiresAuth && !hasToken) { // 需要登录但未登录,跳转到登录页 next({ - path: '/login', + path: '/platform/login', query: { redirect: to.fullPath } // 保存原始路径 }) return } - if (to.path === '/login' && hasToken) { + if (to.path === '/platform/login' && hasToken) { // 已登录但访问登录页,跳转到首页 next('/') return diff --git a/urbanLifelineWeb/packages/platform/vite.config.ts b/urbanLifelineWeb/packages/platform/vite.config.ts index 6f5dcfff..7eecb8e8 100644 --- a/urbanLifelineWeb/packages/platform/vite.config.ts +++ b/urbanLifelineWeb/packages/platform/vite.config.ts @@ -4,6 +4,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { federation } from '@module-federation/vite' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' +import fs from 'fs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -26,7 +27,7 @@ export default defineConfig({ shared: { type: 'module', name: 'shared', - entry: 'http://localhost:7000/remoteEntry.js' + entry: 'https://org.xyzh.yslg/shared/remoteEntry.js' } }, shared: { @@ -55,6 +56,11 @@ export default defineConfig({ host: true, cors: true, open: '/', // 开发时自动打开到根路径 + // HTTPS 配置(使用 mkcert 生成的本地开发证书) + https: { + key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'), + cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem') + }, hmr: { // 修复 base 路径导致的 WebSocket 连接问题 path: '/@vite/client', diff --git a/urbanLifelineWeb/packages/shared/vite.config.ts b/urbanLifelineWeb/packages/shared/vite.config.ts index 33fb1d1c..57d40ec1 100644 --- a/urbanLifelineWeb/packages/shared/vite.config.ts +++ b/urbanLifelineWeb/packages/shared/vite.config.ts @@ -4,6 +4,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { federation } from '@module-federation/vite' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' +import fs from 'fs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -11,7 +12,7 @@ const __dirname = dirname(__filename) /** * Module Federation 构建配置(@module-federation/vite) * 官方维护版本,支持 Vite 6 + 开发模式热更新 - * + * * 优势: * - ✅ 完整支持 Vite 开发模式 * - ✅ dev 模式能生成 remoteEntry.js @@ -19,6 +20,9 @@ const __dirname = dirname(__filename) * - ✅ 真正的生产可用版本 */ export default defineConfig({ + // Shared 模块的基础路径(通过 Nginx 代理访问) + base: '/shared/', + plugins: [ vue({ script: { @@ -109,17 +113,27 @@ export default defineConfig({ strictPort: true, host: true, cors: true, + // HTTPS 配置(使用 mkcert 生成的本地开发证书) + https: { + key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'), + cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem') + }, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' } }, - + preview: { port: 7000, host: true, cors: true, + // HTTPS 配置(使用 mkcert 生成的本地开发证书) + https: { + key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'), + cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem') + }, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', diff --git a/urbanLifelineWeb/packages/workcase/.__mf__temp/workcase/localSharedImportMap.js b/urbanLifelineWeb/packages/workcase/.__mf__temp/workcase/localSharedImportMap.js index 8ddf622b..adb405c6 100644 --- a/urbanLifelineWeb/packages/workcase/.__mf__temp/workcase/localSharedImportMap.js +++ b/urbanLifelineWeb/packages/workcase/.__mf__temp/workcase/localSharedImportMap.js @@ -4,11 +4,6 @@ import {loadShare} from "@module-federation/runtime"; const importMap = { - "axios": async () => { - let pkg = await import("__mf__virtual/workcase__prebuild__axios__prebuild__.js"); - return pkg; - } - , "element-plus": async () => { let pkg = await import("__mf__virtual/workcase__prebuild__element_mf_2_plus__prebuild__.js"); return pkg; @@ -27,36 +22,6 @@ } const usedShared = { - "axios": { - name: "axios", - version: "1.13.2", - scope: ["default"], - loaded: false, - from: "workcase", - async get () { - if (false) { - throw new Error(`Shared module '${"axios"}' must be provided by host`); - } - usedShared["axios"].loaded = true - const {"axios": pkgDynamicImport} = importMap - const res = await pkgDynamicImport() - const exportModule = {...res} - // All npm packages pre-built by vite will be converted to esm - Object.defineProperty(exportModule, "__esModule", { - value: true, - enumerable: false - }) - return function () { - return exportModule - } - }, - shareConfig: { - singleton: false, - requiredVersion: "^1.13.2", - - } - } - , "element-plus": { name: "element-plus", version: "2.12.0", @@ -153,7 +118,7 @@ entryGlobalName: "shared", name: "shared", type: "module", - entry: "http://localhost:7000/remoteEntry.js", + entry: "https://org.xyzh.yslg/shared/remoteEntry.js", shareScope: "default", } diff --git a/urbanLifelineWeb/packages/workcase/src/layouts/SidebarLayout/SidebarLayout.vue b/urbanLifelineWeb/packages/workcase/src/layouts/SidebarLayout/SidebarLayout.vue index 77a84f5d..b22467f6 100644 --- a/urbanLifelineWeb/packages/workcase/src/layouts/SidebarLayout/SidebarLayout.vue +++ b/urbanLifelineWeb/packages/workcase/src/layouts/SidebarLayout/SidebarLayout.vue @@ -188,7 +188,7 @@ const handleUserCommand = (command: string) => { case 'logout': localStorage.clear() ElMessage.success('退出成功') - router.push('/login') + router.push('/platform/login') break } } diff --git a/urbanLifelineWeb/packages/workcase/src/router/index.ts b/urbanLifelineWeb/packages/workcase/src/router/index.ts index 4dd8b458..7729243a 100644 --- a/urbanLifelineWeb/packages/workcase/src/router/index.ts +++ b/urbanLifelineWeb/packages/workcase/src/router/index.ts @@ -6,8 +6,18 @@ import { APP_CONFIG } from 'shared/config' // @ts-ignore import { loadRoutesFromStorage } from './dynamicRoute' -// workcase应用的动态路由会根据layout字段自动添加,不需要预定义Root布局 -const routes: RouteRecordRaw[] = [] +// 公开路由(不需要登录认证) +const routes: RouteRecordRaw[] = [ + { + path: '/meeting', + name: 'Meeting', + component: () => import('@/views/public/JitsiMeeting/JitsiMeetingView.vue'), + meta: { + title: '视频会议', + requiresAuth: false // 不需要登录认证,允许通过token访问 + } + } +] const router = createRouter({ history: createWebHistory('/workcase'), // 与nginx保持一致,使用/workcase前缀 @@ -34,9 +44,10 @@ router.beforeEach(async (to, from, next) => { // 检查URL参数中是否有token(用于外部链接和小程序访问) const tokenParam = to.query.token as string | undefined - // 如果URL中有token,但localStorage中没有loginDomain,使用refresh接口验证 - if (tokenParam && !localStorage.getItem('loginDomain')) { - console.log('[Workcase Router] 检测到token参数,尝试验证登录状态...') + // 如果URL中有token参数,使用refresh接口验证并刷新登录状态 + // 这样可以用新token覆盖旧的登录状态(如果有的话) + if (tokenParam) { + console.log('[Workcase Router] 检测到token参数,尝试验证并刷新登录状态...') try { const response = await fetch('/api/urban-lifeline/auth/refresh', { method: 'POST', @@ -52,12 +63,12 @@ router.beforeEach(async (to, from, next) => { const loginDomain = result.data const newToken = loginDomain.token - // 保存到localStorage + // 保存到localStorage(覆盖旧的登录状态) localStorage.setItem('token', newToken) localStorage.setItem('loginDomain', JSON.stringify(loginDomain)) TokenManager.setToken(newToken) - console.log('[Workcase Router] Token验证成功,登录状态已保存') + console.log('[Workcase Router] Token验证成功,登录状态已刷新') } else { console.warn('[Workcase Router] Token验证失败:', result.message) } diff --git a/urbanLifelineWeb/packages/workcase/src/views/public/JitsiMeeting/JitsiMeetingView.vue b/urbanLifelineWeb/packages/workcase/src/views/public/JitsiMeeting/JitsiMeetingView.vue index c2cb8686..21a8fce4 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/public/JitsiMeeting/JitsiMeetingView.vue +++ b/urbanLifelineWeb/packages/workcase/src/views/public/JitsiMeeting/JitsiMeetingView.vue @@ -25,7 +25,6 @@ import { useRoute, useRouter } from 'vue-router' import { workcaseChatAPI } from '@/api/workcase' // @ts-ignore import { TokenManager } from 'shared/api' -import axios from 'axios' const route = useRoute() const router = useRouter() @@ -53,45 +52,6 @@ const getMeetingParams = () => { return { meetingId: meetingId.value, roomId: roomId.value, token: tokenParam } } -// 使用token刷新登录状态 -const refreshLoginWithToken = async (token: string) => { - try { - console.log('[JitsiMeetingView] 使用token刷新登录状态...') - - const response = await axios.post( - '/api/urban-lifeline/auth/refresh', - {}, - { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - } - ) - - if (response.data?.success && response.data?.data) { - const loginDomain = response.data.data - const newToken = loginDomain.token - - // 保存到localStorage - localStorage.setItem('token', newToken) - localStorage.setItem('loginDomain', JSON.stringify(loginDomain)) - - // 使用TokenManager设置token - TokenManager.setToken(newToken) - - console.log('[JitsiMeetingView] 登录状态刷新成功') - return true - } else { - console.error('[JitsiMeetingView] 刷新登录失败:', response.data?.message) - return false - } - } catch (err: any) { - console.error('[JitsiMeetingView] 刷新登录异常:', err) - return false - } -} - // 加载 Jitsi External API 脚本 const loadJitsiScript = (): Promise => { return new Promise((resolve, reject) => { @@ -102,7 +62,8 @@ const loadJitsiScript = (): Promise => { } const script = document.createElement('script') - script.src = 'http://localhost:8280/external_api.js' + // 从 Jitsi 子域名加载 External API + script.src = 'https://org.xyzh.yslg.jitsi/external_api.js' script.async = true script.onload = () => { console.log('[JitsiMeetingView] Jitsi External API 脚本加载成功') @@ -129,29 +90,27 @@ const initJitsiMeet = async (jitsiServerUrl: string, roomName: string, jwt: stri const JitsiMeetExternalAPI = (window as any).JitsiMeetExternalAPI - // 解析 URL 获取协议和域名 + // 解析 URL 获取协议和域名(子域名模式,无需路径处理) const urlObj = new URL(jitsiServerUrl) - const domain = urlObj.host // 获取 host (localhost:8280) + const domain = urlObj.host // org.xyzh.yslg.jitsi const useHttps = urlObj.protocol === 'https:' console.log('[JitsiMeetingView] 解析服务器配置:', { domain, protocol: urlObj.protocol, - useHttps + useHttps, + roomName }) - // 配置选项 - 关键!指定是否使用 HTTPS - // 👇 替换你原有的 options 全部代码,保留原有逻辑,仅修改配置项 + // 配置选项 - 子域名模式,直接使用域名和房间名 const options: any = { - roomName: roomName, + roomName: roomName, // 直接使用房间名,无需路径前缀 width: '100%', height: '100%', parentNode: jitsiContainer.value, jwt: jwt, - // ✅ 修复1:核心!正确的顶层配置项是 useHTTPS(不是 https),强制关闭HTTPS - useHTTPS: false, - // ✅ 修复2:禁用WebSocket的HTTPS,彻底阻止wss://请求(局域网必加) - useWebSocket: false, + // 使用 HTTPS 协议 + https: useHttps, configOverwrite: { startWithAudioMuted: false, startWithVideoMuted: false, @@ -159,13 +118,7 @@ const initJitsiMeet = async (jitsiServerUrl: string, roomName: string, jwt: stri prejoinPageEnabled: false, disableDeepLinking: true, enableChat: true, - enableScreenSharing: true, - // ✅ 修复3:叠加禁用,彻底阻断API内部的HTTPS强制逻辑(局域网核心) - useHTTPS: false, - // ✅ 修复4:禁用第三方HTTPS资源请求,避免混合内容报错 - disableThirdPartyRequests: false, - // ✅ 修复5:关闭服务端的HTTPS重定向检测 - disableHttpsRedirect: true + enableScreenSharing: true }, interfaceConfigOverwrite: { SHOW_JITSI_WATERMARK: false, @@ -177,7 +130,12 @@ const initJitsiMeet = async (jitsiServerUrl: string, roomName: string, jwt: stri } } - console.log('[JitsiMeetingView] 创建 JitsiMeetExternalAPI 实例,https=' + useHttps) + console.log('[JitsiMeetingView] 创建 JitsiMeetExternalAPI 实例:', { + domain, + https: useHttps, + roomName, + 预期URL: `${useHttps ? 'https' : 'http'}://${domain}/${roomName}` + }) jitsiApi = new JitsiMeetExternalAPI(domain, options) // 监听会议准备就绪事件 @@ -219,32 +177,13 @@ const joinMeeting = async () => { return } - // 检查是否有loginDomain - const hasLoginDomain = !!localStorage.getItem('loginDomain') - const hasToken = !!localStorage.getItem('token') || !!token - - console.log('[JitsiMeetingView] 登录状态检查:', { - hasLoginDomain, - hasToken - }) - - // 如果没有loginDomain但有token(小程序或外部链接访问),先刷新登录 - if (!hasLoginDomain && token) { - const refreshed = await refreshLoginWithToken(token) - if (!refreshed) { - error.value = '登录验证失败,请重新获取会议链接' - loading.value = false - return - } - } - - // 检查登录状态 + // 检查登录状态(路由守卫已处理token刷新) if (!TokenManager.hasToken()) { error.value = '未登录,请先登录' loading.value = false // 重定向到登录页 const currentUrl = window.location.href - window.location.href = `/login?redirect=${encodeURIComponent(currentUrl)}` + window.location.href = `/platform/login?redirect=${encodeURIComponent(currentUrl)}` return } diff --git a/urbanLifelineWeb/packages/workcase/vite.config.ts b/urbanLifelineWeb/packages/workcase/vite.config.ts index 23228113..0b4f85e0 100644 --- a/urbanLifelineWeb/packages/workcase/vite.config.ts +++ b/urbanLifelineWeb/packages/workcase/vite.config.ts @@ -4,6 +4,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx' import { federation } from '@module-federation/vite' import { resolve, dirname } from 'path' import { fileURLToPath } from 'url' +import fs from 'fs' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -26,7 +27,7 @@ export default defineConfig(({ mode }) => ({ shared: { type: 'module', name: 'shared', - entry: 'http://localhost:7000/remoteEntry.js' + entry: 'https://org.xyzh.yslg/shared/remoteEntry.js' } }, shared: { @@ -56,6 +57,11 @@ export default defineConfig(({ mode }) => ({ host: true, cors: true, open: '/workcase/', // 开发时自动打开到 /workcase/ 路径 + // HTTPS 配置(使用 mkcert 生成的本地开发证书) + https: { + key: fs.readFileSync('C:/Users/FK05/443/localhost+3-key.pem'), + cert: fs.readFileSync('C:/Users/FK05/443/localhost+3.pem') + }, hmr: { // 修复 base 路径导致的 WebSocket 连接问题 path: '/@vite/client', @@ -67,11 +73,6 @@ export default defineConfig(({ mode }) => ({ changeOrigin: true, ws: true, // 启用 WebSocket 代理 rewrite: (path: string) => path.replace(/^\/api/, '') - }, - // 代理共享模块请求到 shared 服务 - '/shared': { - target: 'http://localhost:7000', - changeOrigin: true } } }, diff --git a/urbanLifelineWeb/packages/workcase_wechat/pages/chatRoom/chatRoom/chatRoom.uvue b/urbanLifelineWeb/packages/workcase_wechat/pages/chatRoom/chatRoom/chatRoom.uvue index 71cd9c2f..ab2b76f7 100644 --- a/urbanLifelineWeb/packages/workcase_wechat/pages/chatRoom/chatRoom/chatRoom.uvue +++ b/urbanLifelineWeb/packages/workcase_wechat/pages/chatRoom/chatRoom/chatRoom.uvue @@ -685,9 +685,9 @@ async function handleJoinMeeting(meetingId: string) { const meetingName = meetingData.meetingName || '视频会议' console.log('[handleJoinMeeting] 获取到会议页面URL:', meetingPageUrl, '会议名称:', meetingName) - // 构建完整的会议URL(包含域名和workcase路径) - const protocol = window.location.protocol - const host = window.location.host + // 小程序环境:直接使用固定的HTTPS域名 + const protocol = 'https:' + const host = 'org.xyzh.yslg' // 如果meetingPageUrl不包含/workcase,需要加上 const fullPath = meetingPageUrl.startsWith('/workcase') ? meetingPageUrl diff --git a/urbanLifelineWeb/packages/workcase_wechat/pnpm-lock.yaml b/urbanLifelineWeb/packages/workcase_wechat/pnpm-lock.yaml index f03487e8..2b9f1883 100644 --- a/urbanLifelineWeb/packages/workcase_wechat/pnpm-lock.yaml +++ b/urbanLifelineWeb/packages/workcase_wechat/pnpm-lock.yaml @@ -3,98 +3,3 @@ lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false - -dependencies: - '@stomp/stompjs': - specifier: ^7.2.1 - version: 7.2.1 - sockjs-client: - specifier: ^1.6.1 - version: 1.6.1 - -packages: - - /@stomp/stompjs@7.2.1: - resolution: {integrity: sha512-DLd/WeicnHS5SsWWSk3x6/pcivqchNaEvg9UEGVqAcfYEBVmS9D6980ckXjTtfpXLjdLDsd96M7IuX4w7nzq5g==} - 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 - - /eventsource@2.0.2: - resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} - engines: {node: '>=12.0.0'} - 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 - - /http-parser-js@0.5.10: - resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} - dev: false - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: false - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: false - - /querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: false - - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: false - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: false - - /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 - - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: false - - /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