From c20df56dcd51e9c566f226bd4cb172b452eb0ac1 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sun, 31 Aug 2025 19:38:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 4 +- .../main/resources/application-prod-9900.yml | 44 +++ .../main/resources/application-prod-9901.yml | 44 +++ .../main/resources/application-prod-9902.yml | 44 +++ .../main/resources/application-prod-9903.yml | 44 +++ .../main/resources/application-prod-local.yml | 353 +++++++++++++++++ .../src/main/resources/application-prod.yml | 41 +- .../src/main/resources/application.yml | 3 + .../src/main/resources/logback-plus.xml | 5 + .../src/main/resources/application.yml | 2 +- .../main/resources/application-prod-local.yml | 40 ++ .../src/main/resources/application-prod.yml | 6 +- .../controller/VolunteerMemberController.java | 1 - .../SSE关闭接口406错误解决方案.md | 252 ++++++++++++ .../SSE生产环境配置问题解决方案.md | 256 ++++++++++++ .../nginx配置/hzdyjsweb.camasort.com.conf | 77 +++- .../docker-compose-multi.yml | 135 +++++++ docs/部署文档/杭州第一技师部署文档/nginx.conf | 116 ++++++ .../start-extend-services.sh | 368 ++++++++++++++++++ .../start-multi-instance.bat | 296 ++++++++++++++ .../start-multi-instance.sh | 284 ++++++++++++++ .../杭州第一技师部署文档/test-config-fix.sh | 66 ++++ .../多实例启动配置修复说明.md | 96 +++++ .../杭州第一技师部署文档/多实例部署说明.md | 293 ++++++++++++++ .../SnailJob命名空间配置修复方案.md | 96 +++++ .../xg/xsjcxx/studentMsg/studentMsg/index.vue | 2 +- 26 files changed, 2931 insertions(+), 37 deletions(-) create mode 100644 backend/ruoyi-admin/src/main/resources/application-prod-9900.yml create mode 100644 backend/ruoyi-admin/src/main/resources/application-prod-9901.yml create mode 100644 backend/ruoyi-admin/src/main/resources/application-prod-9902.yml create mode 100644 backend/ruoyi-admin/src/main/resources/application-prod-9903.yml create mode 100644 backend/ruoyi-admin/src/main/resources/application-prod-local.yml create mode 100644 backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod-local.yml delete mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/VolunteerMemberController.java create mode 100644 docs/技术问题解决记录/SSE关闭接口406错误解决方案.md create mode 100644 docs/技术问题解决记录/SSE生产环境配置问题解决方案.md create mode 100644 docs/部署文档/杭州第一技师部署文档/docker-compose-multi.yml create mode 100644 docs/部署文档/杭州第一技师部署文档/nginx.conf create mode 100644 docs/部署文档/杭州第一技师部署文档/start-extend-services.sh create mode 100644 docs/部署文档/杭州第一技师部署文档/start-multi-instance.bat create mode 100644 docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh create mode 100644 docs/部署文档/杭州第一技师部署文档/test-config-fix.sh create mode 100644 docs/部署文档/杭州第一技师部署文档/多实例启动配置修复说明.md create mode 100644 docs/部署文档/杭州第一技师部署文档/多实例部署说明.md create mode 100644 docs/问题解决方案/SnailJob命名空间配置修复方案.md diff --git a/backend/ruoyi-admin/src/main/resources/application-dev.yml b/backend/ruoyi-admin/src/main/resources/application-dev.yml index 187582ba..96b88da6 100644 --- a/backend/ruoyi-admin/src/main/resources/application-dev.yml +++ b/backend/ruoyi-admin/src/main/resources/application-dev.yml @@ -115,8 +115,8 @@ spring: idleTimeout: 300000 # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟(减少生命周期) maxLifetime: 900000 - # 多久检查一次连接的活性(增加间隔,减少检测频率) - keepaliveTime: 60000 + # 多久检查一次连接的活性(增加间隔,减少检测频率,减少日志输出) + keepaliveTime: 300000 # 连接测试查询(验证连接有效性) # connectionTestQuery: SELECT 11 diff --git a/backend/ruoyi-admin/src/main/resources/application-prod-9900.yml b/backend/ruoyi-admin/src/main/resources/application-prod-9900.yml new file mode 100644 index 00000000..a0648192 --- /dev/null +++ b/backend/ruoyi-admin/src/main/resources/application-prod-9900.yml @@ -0,0 +1,44 @@ +# 生产环境配置 - 9900端口实例 +server: + port: 9900 + servlet: + context-path: / + +# 实例标识 +spring: + application: + name: RuoYi-Vue-Plus-9900 + +# 日志文件配置 +logging: + file: + name: logs/ruoyi-admin-9900.log + pattern: + file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" + +# 监控中心配置 - 端口漂移 +spring.boot.admin.client: + enabled: true + url: http://172.16.0.67:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + instance.port: 9900 + instance.name: ruoyi-admin-9900 + username: @monitor.username@ + password: @monitor.password@ + +# SnailJob 配置 - 端口漂移 +snail-job: + enabled: true + group: "ruoyi_group_9900" + token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" + server: + host: 172.16.0.67 + port: 17888 + namespace: "prod" + port: 29900 + host: + rpc-type: grpc diff --git a/backend/ruoyi-admin/src/main/resources/application-prod-9901.yml b/backend/ruoyi-admin/src/main/resources/application-prod-9901.yml new file mode 100644 index 00000000..699e3d3c --- /dev/null +++ b/backend/ruoyi-admin/src/main/resources/application-prod-9901.yml @@ -0,0 +1,44 @@ +# 生产环境配置 - 9901端口实例 +server: + port: 9901 + servlet: + context-path: / + +# 实例标识 +spring: + application: + name: RuoYi-Vue-Plus-9901 + +# 日志文件配置 +logging: + file: + name: logs/ruoyi-admin-9901.log + pattern: + file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" + +# 监控中心配置 - 端口漂移 +spring.boot.admin.client: + enabled: true + url: http://172.16.0.67:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + instance.port: 9901 + instance.name: ruoyi-admin-9901 + username: @monitor.username@ + password: @monitor.password@ + +# SnailJob 配置 - 端口漂移 +snail-job: + enabled: true + group: "ruoyi_group_9901" + token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" + server: + host: 172.16.0.67 + port: 17888 + namespace: "prod" + port: 29901 + host: + rpc-type: grpc diff --git a/backend/ruoyi-admin/src/main/resources/application-prod-9902.yml b/backend/ruoyi-admin/src/main/resources/application-prod-9902.yml new file mode 100644 index 00000000..c50e9f94 --- /dev/null +++ b/backend/ruoyi-admin/src/main/resources/application-prod-9902.yml @@ -0,0 +1,44 @@ +# 生产环境配置 - 9902端口实例 +server: + port: 9902 + servlet: + context-path: / + +# 实例标识 +spring: + application: + name: RuoYi-Vue-Plus-9902 + +# 日志文件配置 +logging: + file: + name: logs/ruoyi-admin-9902.log + pattern: + file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" + +# 监控中心配置 - 端口漂移 +spring.boot.admin.client: + enabled: true + url: http://172.16.0.67:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + instance.port: 9902 + instance.name: ruoyi-admin-9902 + username: @monitor.username@ + password: @monitor.password@ + +# SnailJob 配置 - 端口漂移 +snail-job: + enabled: true + group: "ruoyi_group_9902" + token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" + server: + host: 172.16.0.67 + port: 17888 + namespace: "prod" + port: 29902 + host: + rpc-type: grpc diff --git a/backend/ruoyi-admin/src/main/resources/application-prod-9903.yml b/backend/ruoyi-admin/src/main/resources/application-prod-9903.yml new file mode 100644 index 00000000..209ae9a6 --- /dev/null +++ b/backend/ruoyi-admin/src/main/resources/application-prod-9903.yml @@ -0,0 +1,44 @@ +# 生产环境配置 - 9903端口实例 +server: + port: 9903 + servlet: + context-path: / + +# 实例标识 +spring: + application: + name: RuoYi-Vue-Plus-9903 + +# 日志文件配置 +logging: + file: + name: logs/ruoyi-admin-9903.log + pattern: + file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" + +# 监控中心配置 - 端口漂移 +spring.boot.admin.client: + enabled: true + url: http://172.16.0.67:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + instance.port: 9903 + instance.name: ruoyi-admin-9903 + username: @monitor.username@ + password: @monitor.password@ + +# SnailJob 配置 - 端口漂移 +snail-job: + enabled: true + group: "ruoyi_group_9903" + token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" + server: + host: 172.16.0.67 + port: 17888 + namespace: "prod" + port: 29903 + host: + rpc-type: grpc diff --git a/backend/ruoyi-admin/src/main/resources/application-prod-local.yml b/backend/ruoyi-admin/src/main/resources/application-prod-local.yml new file mode 100644 index 00000000..7ba1abbf --- /dev/null +++ b/backend/ruoyi-admin/src/main/resources/application-prod-local.yml @@ -0,0 +1,353 @@ +--- # 临时文件存储位置 避免临时文件被系统清理报错 +spring.servlet.multipart.location: /ruoyi/server/temp + +--- # 监控中心配置 +spring.boot.admin.client: + # 增加客户端开关 + enabled: true + url: http://47.121.125.41:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + username: @monitor.username@ + password: @monitor.password@ + +--- # snail-job 配置 +snail-job: + enabled: true + # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务 + group: "ruoyi_group" + # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表 + token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" + server: + host: 47.121.125.41 + port: 17888 + # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段 + namespace: ${spring.profiles.active} + # 随主应用端口漂移 + port: 2${server.port} + # 客户端ip指定 + host: + # RPC类型: netty, grpc + rpc-type: grpc + +--- # 数据源配置 +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content + dynamic: + # 性能分析插件(有性能损耗 不建议生产环境使用) + p6spy: false + # 设置默认的数据源或者数据源组,默认值即为 master + primary: master + # 严格模式 匹配不到数据源则报错 + strict: true + datasource: + # 主库数据源 + master: + type: ${spring.datasource.type} + driverClassName: com.mysql.cj.jdbc.Driver + # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 + # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) + url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: root + password: hn02le.34lkdLKD +# # 从库数据源 +# slave: +# lazy: true +# type: ${spring.datasource.type} +# driverClassName: com.mysql.cj.jdbc.Driver +# url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true +# username: +# password: +# oracle: +# type: ${spring.datasource.type} +# driverClassName: oracle.jdbc.OracleDriver +# url: jdbc:oracle:thin:@//localhost:1521/XE +# username: ROOT +# password: root +# postgres: +# type: ${spring.datasource.type} +# driverClassName: org.postgresql.Driver +# url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true +# username: root +# password: root +# sqlserver: +# type: ${spring.datasource.type} +# driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver +# url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true +# username: SA +# password: root + hikari: + # 最大连接池数量 + maxPoolSize: 30 + # 最小空闲线程数量 + minIdle: 10 + # 配置获取连接等待超时的时间 + connectionTimeout: 30000 + # 校验超时时间 + validationTimeout: 5000 + # 空闲连接存活最大时间,默认10分钟 + idleTimeout: 600000 + # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 + maxLifetime: 1800000 + # 多久检查一次连接的活性 + keepaliveTime: 30000 + +--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉) +spring.data: + redis: + # 地址 + host: 47.99.164.66 + # 端口,默认为6379 + port: 16379 + # 数据库索引 + database: 3 + # redis 密码必须配置 + password: Hzdyjs@123456 + # 连接超时时间 + timeout: 10s + # 是否开启ssl + ssl.enabled: false + +# redisson 配置 +redisson: + # redis key前缀 + keyPrefix: + # 线程池数量 + threads: 16 + # Netty线程池数量 + nettyThreads: 32 + # 单节点配置 + singleServerConfig: + # 客户端名称 不能用中文 + clientName: RuoYi-Vue-Plus + # 最小空闲连接数 + connectionMinimumIdleSize: 32 + # 连接池大小 + connectionPoolSize: 64 + # 连接空闲超时,单位:毫秒 + idleConnectionTimeout: 10000 + # 命令等待超时,单位:毫秒 + timeout: 3000 + # 发布和订阅连接池大小 + subscriptionConnectionPoolSize: 50 + +--- # mail 邮件发送 +mail: + enabled: false + host: smtp.163.com + port: 465 + # 是否需要用户名密码验证 + auth: true + # 发送方,遵循RFC-822标准 + from: xxx@163.com + # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) + user: xxx@163.com + # 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助) + pass: xxxxxxxxxx + # 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。 + starttlsEnable: true + # 使用SSL安全连接 + sslEnable: true + # SMTP超时时长,单位毫秒,缺省值不超时 + timeout: 0 + # Socket连接超时值,单位毫秒,缺省值不超时 + connectionTimeout: 0 + +--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商 +# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用 +sms: + # 配置源类型用于标定配置来源(interface,yaml) + config-type: yaml + # 用于标定yml中的配置是否开启短信拦截,接口配置不受此限制 + restricted: true + # 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效 + minute-max: 1 + # 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效 + account-max: 30 + # 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中 + blends: + # 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可 + # 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户 + config1: + # 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 + supplier: alibaba + # 有些称为accessKey有些称之为apiKey,也有称为sdkKey或者appId。 + access-key-id: 您的accessKey + # 称为accessSecret有些称之为apiSecret + access-key-secret: 您的accessKeySecret + signature: 您的短信签名 + sdk-app-id: 您的sdkAppId + config2: + # 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分 + supplier: tencent + access-key-id: 您的accessKey + access-key-secret: 您的accessKeySecret + signature: 您的短信签名 + sdk-app-id: 您的sdkAppId + +--- # 三方授权 +justauth: + # 前端外网访问地址 + address: http://localhost:80 + type: + maxkey: + # maxkey 服务器地址 + # 注意 如下均配置均不需要修改 maxkey 已经内置好了数据 + server-url: http://sso.maxkey.top + client-id: 876892492581044224 + client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8 + redirect-uri: ${justauth.address}/social-callback?source=maxkey + topiam: + # topiam 服务器地址 + server-url: http://127.0.0.1:1989/api/v1/authorize/y0q************spq***********8ol + client-id: 449c4*********937************759 + client-secret: ac7***********1e0************28d + redirect-uri: ${justauth.address}/social-callback?source=topiam + scopes: [ openid, email, phone, profile ] + qq: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=qq + union-id: false + weibo: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=weibo + gitee: + client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98 + client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac + redirect-uri: ${justauth.address}/social-callback?source=gitee + dingtalk: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=dingtalk + baidu: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=baidu + csdn: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=csdn + coding: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=coding + coding-group-name: xx + oschina: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=oschina + alipay_wallet: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet + alipay-public-key: MIIB**************DAQAB + wechat_open: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=wechat_open + wechat_mp: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=wechat_mp + wechat_enterprise: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise + agent-id: 1000002 + gitlab: + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=gitlab + gitea: + # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204 + # gitea 服务器地址 + server-url: https://demo.gitea.com + client-id: 10**********6 + client-secret: 1f7d08**********5b7**********29e + redirect-uri: ${justauth.address}/social-callback?source=gitea + +system: + file-cache: + # 是否启用文件缓存 + enabled: true + + # 最大缓存文件数量 + max-cache-size: 100 + + # 最大缓存大小(字节)- 500MB + max-cache-size-bytes: 524288000 + + # 缓存过期时间(毫秒)- 30分钟 + expire-time-ms: 1800000 + + # 最大缓存单个文件大小(字节)- 50MB + max-single-file-size: 52428800 + + # LRU淘汰比例(百分比) + evict-ratio: 20 + + # 是否启用缓存统计 + enable-stats: true + + # 缓存清理间隔(毫秒)- 5分钟 + cleanup-interval-ms: 300000 +# 配置说明: +# 1. enabled: 控制是否启用文件缓存功能 +# 2. max-cache-size: 缓存的最大文件数量 +# 3. max-cache-size-bytes: 缓存的最大总大小(字节) +# 4. expire-time-ms: 缓存条目的过期时间(毫秒) +# 5. max-single-file-size: 单个文件的最大缓存大小(字节) +# 6. evict-ratio: LRU淘汰时移除的条目比例(百分比) +# 7. enable-stats: 是否启用缓存统计功能 +# 8. cleanup-interval-ms: 自动清理过期条目的间隔(毫秒) + +# 性能优化建议: +# - 对于内存充足的环境,可以适当增加 max-cache-size-bytes +# - 对于频繁访问的小文件,可以增加 max-cache-size +# - 对于大文件较多的环境,可以调整 max-single-file-size +# - 对于访问频率较低的环境,可以适当减少 expire-time-ms + + +--- # SQL日志配置(用于调试班级名额查询问题) +logging: + level: + # 开启 MyBatis SQL 日志 + org.dromara.jcbt.mapper: DEBUG + org.dromara.jcbt.service: DEBUG + # 开启 MyBatis-Plus SQL 日志 + com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor: DEBUG + # 开启 HikariCP 连接池日志(可选) + com.zaxxer.hikari: DEBUG + # 开启 P6Spy SQL 日志(更详细的SQL执行日志) + p6spy: INFO + pattern: + # 控制台日志格式,增加线程名和方法名 + console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx" + +# MyBatis 配置 +mybatis-plus: + configuration: + # 开启 SQL 日志打印 + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + # 开启驼峰命名转换 + map-underscore-to-camel-case: true + # 开启缓存 + cache-enabled: true + # 开启懒加载 + lazy-loading-enabled: true + # 开启积极懒加载 + aggressive-lazy-loading: false + +--- # 高德地图配置 +amap: + # 高德地图API密钥(请替换为您的实际密钥) + key: 920750e421cd6357ddf60ee1e929e0dd + # 高德地图API基础URL + url: https://restapi.amap.com diff --git a/backend/ruoyi-admin/src/main/resources/application-prod.yml b/backend/ruoyi-admin/src/main/resources/application-prod.yml index 7ba1abbf..6ce93392 100644 --- a/backend/ruoyi-admin/src/main/resources/application-prod.yml +++ b/backend/ruoyi-admin/src/main/resources/application-prod.yml @@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp spring.boot.admin.client: # 增加客户端开关 enabled: true - url: http://47.121.125.41:9090/admin + url: http://172.16.0.67:9090/admin instance: service-host-type: IP metadata: @@ -22,7 +22,7 @@ snail-job: # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表 token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT" server: - host: 47.121.125.41 + host: 172.16.0.67 port: 17888 # 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段 namespace: ${spring.profiles.active} @@ -52,9 +52,9 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://172.16.0.67:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: hn02le.34lkdLKD + password: Hzdyjs@Root@123456 # # 从库数据源 # slave: # lazy: true @@ -83,7 +83,7 @@ spring: # password: root hikari: # 最大连接池数量 - maxPoolSize: 30 + maxPoolSize: 60 # 最小空闲线程数量 minIdle: 10 # 配置获取连接等待超时的时间 @@ -94,20 +94,20 @@ spring: idleTimeout: 600000 # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟 maxLifetime: 1800000 - # 多久检查一次连接的活性 - keepaliveTime: 30000 + # 多久检查一次连接的活性(增加间隔,减少检测频率,减少日志输出) + keepaliveTime: 300000 --- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉) spring.data: redis: # 地址 - host: 47.99.164.66 + host: 172.16.0.67 # 端口,默认为6379 port: 16379 # 数据库索引 database: 3 # redis 密码必须配置 - password: Hzdyjs@123456 + password: Hzdyjs@redis@123456 # 连接超时时间 timeout: 10s # 是否开启ssl @@ -318,15 +318,16 @@ system: --- # SQL日志配置(用于调试班级名额查询问题) logging: level: - # 开启 MyBatis SQL 日志 - org.dromara.jcbt.mapper: DEBUG - org.dromara.jcbt.service: DEBUG - # 开启 MyBatis-Plus SQL 日志 - com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor: DEBUG - # 开启 HikariCP 连接池日志(可选) - com.zaxxer.hikari: DEBUG - # 开启 P6Spy SQL 日志(更详细的SQL执行日志) - p6spy: INFO + # 生产环境关闭 MyBatis SQL 日志,避免性能影响和敏感信息泄露 + # org.dromara.jcbt.mapper: DEBUG + # org.dromara.jcbt.service: DEBUG + # 生产环境关闭 MyBatis-Plus SQL 日志 + # com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor: DEBUG + # 关闭 HikariCP 连接池DEBUG日志,减少keepalive检测日志 + com.zaxxer.hikari: WARN + com.zaxxer.hikari.pool.HikariPool: WARN + # 生产环境关闭 P6Spy SQL 日志,避免性能影响 + # p6spy: INFO pattern: # 控制台日志格式,增加线程名和方法名 console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx" @@ -334,7 +335,7 @@ logging: # MyBatis 配置 mybatis-plus: configuration: - # 开启 SQL 日志打印 + # 生产环境关闭 SQL 日志打印,避免性能影响和敏感信息泄露 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启驼峰命名转换 map-underscore-to-camel-case: true @@ -345,7 +346,7 @@ mybatis-plus: # 开启积极懒加载 aggressive-lazy-loading: false ---- # 高德地图配置 +--- # 高德地图配置 生产环境需要修改成学校的配置 amap: # 高德地图API密钥(请替换为您的实际密钥) key: 920750e421cd6357ddf60ee1e929e0dd diff --git a/backend/ruoyi-admin/src/main/resources/application.yml b/backend/ruoyi-admin/src/main/resources/application.yml index f66f8f50..86b03355 100644 --- a/backend/ruoyi-admin/src/main/resources/application.yml +++ b/backend/ruoyi-admin/src/main/resources/application.yml @@ -41,6 +41,9 @@ logging: org.springframework: warn org.mybatis.spring.mapper: error org.apache.fury: warn + # 关闭HikariCP连接池DEBUG日志,减少keepalive检测日志 + com.zaxxer.hikari: warn + com.zaxxer.hikari.pool.HikariPool: warn # 关闭SQL日志调试,减少SELECT 1心跳检测日志 # org.dromara.cw.mapper.CwApprovalRecordMapper: info # org.dromara.cw.service.impl.CwApprovalRecordServiceImpl: info diff --git a/backend/ruoyi-admin/src/main/resources/logback-plus.xml b/backend/ruoyi-admin/src/main/resources/logback-plus.xml index d6322759..3ed170b9 100644 --- a/backend/ruoyi-admin/src/main/resources/logback-plus.xml +++ b/backend/ruoyi-admin/src/main/resources/logback-plus.xml @@ -165,4 +165,9 @@ + + + + + diff --git a/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml index 86766d7a..108a203e 100644 --- a/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml +++ b/backend/ruoyi-extend/ruoyi-monitor-admin/src/main/resources/application.yml @@ -38,7 +38,7 @@ spring.boot.admin.client: # 增加客户端开关 enabled: true # 设置 Spring Boot Admin Server 地址 - url: http://127.0.0.1:9090/admin + url: http://172.16.0.67:9090/admin instance: service-host-type: IP metadata: diff --git a/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod-local.yml b/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod-local.yml new file mode 100644 index 00000000..0f731ab7 --- /dev/null +++ b/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod-local.yml @@ -0,0 +1,40 @@ +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: root + password: hn02le.34lkdLKD + hikari: + connection-timeout: 30000 + validation-timeout: 5000 + minimum-idle: 10 + maximum-pool-size: 20 + idle-timeout: 600000 + max-lifetime: 900000 + keepaliveTime: 30000 + +--- # snail-job 服务端配置 +snail-job: + # 拉取重试数据的每批次的大小 + retry-pull-page-size: 1000 + # 拉取重试数据的每批次的大小 + job-pull-page-size: 1000 + # 服务器端口 + server-port: 17888 + # 日志保存时间(单位: day) + log-storage: 7 + rpc-type: grpc + +--- # 监控中心配置 +spring.boot.admin.client: + # 增加客户端开关 + enabled: true + url: http://47.121.125.41:9090/admin + instance: + service-host-type: IP + metadata: + username: ${spring.boot.admin.client.username} + userpassword: ${spring.boot.admin.client.password} + username: @monitor.username@ + password: @monitor.password@ diff --git a/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml b/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml index 0f731ab7..a0059e7d 100644 --- a/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml +++ b/backend/ruoyi-extend/ruoyi-snailjob-server/src/main/resources/application-prod.yml @@ -2,9 +2,9 @@ spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + url: jdbc:mysql://172.16.0.67:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root - password: hn02le.34lkdLKD + password: Hzdyjs@Root@123456 hikari: connection-timeout: 30000 validation-timeout: 5000 @@ -30,7 +30,7 @@ snail-job: spring.boot.admin.client: # 增加客户端开关 enabled: true - url: http://47.121.125.41:9090/admin + url: http://172.16.0.67:9090/admin instance: service-host-type: IP metadata: diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/VolunteerMemberController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/VolunteerMemberController.java deleted file mode 100644 index 0519ecba..00000000 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/VolunteerMemberController.java +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/技术问题解决记录/SSE关闭接口406错误解决方案.md b/docs/技术问题解决记录/SSE关闭接口406错误解决方案.md new file mode 100644 index 00000000..c779157f --- /dev/null +++ b/docs/技术问题解决记录/SSE关闭接口406错误解决方案.md @@ -0,0 +1,252 @@ +# SSE关闭接口406错误解决方案 + +## 🚨 问题描述 + +用户在前台退出时,SSE关闭接口返回406 Not Acceptable错误: + +``` +Request URL: http://hzdyjsweb.camasort.com/prod-api/resource/sse/close +Request Method: GET +Status Code: 406 Not Acceptable +``` + +## 🔍 问题分析 + +### 1. 错误原因分析 + +**406 Not Acceptable错误**通常表示: +- 服务器无法根据请求的内容特性完成请求 +- 请求的资源的内容特性无法满足请求头中的条件 +- 用户认证信息缺失或无效 + +### 2. 根本原因 + +1. **认证时机问题**: + - 前端在logout时先调用SSE关闭接口,再调用logout接口 + - 此时token可能已经失效或即将失效 + - 后端`LoginHelper.getUserId()`可能返回null + +2. **后端接口设计问题**: + - SSE关闭接口使用了`@SaIgnore`注解,但仍然需要获取用户信息 + - 缺少对空值和异常的处理 + - 没有优雅降级机制 + +3. **nginx代理配置问题**: + - SSE关闭接口可能没有正确的代理配置 + - 缺少专门的错误处理 + +## 🛠️ 解决方案 + +### 方案一:修复后端SSE关闭接口(已实施) + +#### 1. 优化错误处理 + +```java +/** + * 关闭 SSE 连接 + */ +@SaIgnore +@GetMapping(value = "${sse.path}/close") +public R close() { + try { + // 尝试获取token和用户ID,如果获取失败则直接返回成功 + String tokenValue = null; + Long userId = null; + + try { + tokenValue = StpUtil.getTokenValue(); + userId = LoginHelper.getUserId(); + } catch (Exception e) { + // 如果获取用户信息失败,记录日志但不抛出异常 + log.warn("Failed to get user info for SSE close: {}", e.getMessage()); + return R.ok(); + } + + // 如果token或userId为空,直接返回成功 + if (tokenValue == null || userId == null) { + log.info("Token or userId is null, skipping SSE disconnect"); + return R.ok(); + } + + sseEmitterManager.disconnect(userId, tokenValue); + return R.ok(); + } catch (Exception e) { + log.error("Error during SSE close: {}", e.getMessage(), e); + // 即使出错也返回成功,避免影响用户退出流程 + return R.ok(); + } +} +``` + +#### 2. 关键改进点 + +- **添加异常捕获**:捕获所有可能的异常 +- **空值检查**:检查token和userId是否为空 +- **优雅降级**:即使出错也返回成功状态 +- **日志记录**:记录详细的错误信息用于调试 + +### 方案二:优化前端logout逻辑(已实施) + +#### 1. 添加错误处理 + +```typescript +/** + * 注销 + */ +export function logout() { + // 先尝试关闭SSE连接,但不阻塞logout流程 + if (import.meta.env.VITE_APP_SSE === 'true') { + request({ + url: '/resource/sse/close', + method: 'get' + }).catch((error) => { + // SSE关闭失败不影响用户退出,只记录日志 + console.warn('SSE close failed during logout:', error); + }); + } + + // 执行正常的logout流程 + return request({ + url: '/auth/logout', + method: 'post' + }); +} +``` + +#### 2. 关键改进点 + +- **非阻塞调用**:SSE关闭失败不影响logout流程 +- **错误捕获**:捕获SSE关闭接口的错误 +- **日志记录**:记录错误信息但不影响用户体验 + +### 方案三:更新nginx配置(已实施) + +#### 1. 添加SSE关闭接口专用配置 + +```nginx +# SSE关闭接口专用配置 +location ^~ /prod-api/resource/sse/close { + proxy_pass http://47.121.125.41:8080/resource/sse/close; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 普通API配置(不需要SSE特殊配置) + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + # CORS处理 + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid'; + add_header Access-Control-Allow-Credentials true; + + if ($request_method = 'OPTIONS') { + return 204; + } +} +``` + +#### 2. 配置说明 + +- **独立配置**:为SSE关闭接口提供独立的代理配置 +- **合理超时**:设置30秒超时,适合普通API调用 +- **CORS支持**:确保跨域请求正常工作 + +## 📋 部署步骤 + +### 1. 更新后端代码 + +```bash +# 重新编译后端项目 +mvn clean compile + +# 重启后端服务 +systemctl restart your-backend-service +``` + +### 2. 更新前端代码 + +```bash +# 重新构建前端项目 +npm run build + +# 部署到nginx目录 +cp -r dist/* /root/hzdyjsweb9091/dist/ +``` + +### 3. 更新nginx配置 + +```bash +# 测试nginx配置 +nginx -t + +# 重新加载配置 +nginx -s reload +``` + +## 🔧 验证方法 + +### 1. 功能验证 + +1. **正常登录**:确保用户能够正常登录 +2. **SSE连接**:检查SSE连接是否正常建立 +3. **正常退出**:测试用户退出流程 +4. **错误处理**:检查控制台是否有错误信息 + +### 2. 错误场景测试 + +1. **Token失效退出**:在token即将失效时退出 +2. **网络异常退出**:在网络不稳定的情况下退出 +3. **并发退出**:多个用户同时退出 + +### 3. 日志检查 + +```bash +# 检查后端日志 +tail -f /path/to/backend/logs/application.log | grep -i "sse" + +# 检查nginx错误日志 +tail -f /var/log/nginx/hzdyjsweb.camasort.com.error.log +``` + +## 🎯 预期效果 + +### 1. 问题解决 + +- ✅ 消除406错误 +- ✅ 用户退出流程顺畅 +- ✅ 不影响用户体验 + +### 2. 系统稳定性 + +- ✅ SSE连接正确关闭 +- ✅ 资源正确释放 +- ✅ 错误处理完善 + +### 3. 监控指标 + +- SSE关闭成功率:100% +- 用户退出成功率:100% +- 错误日志减少:显著减少 + +## 📚 相关文档 + +- [SSE生产环境配置问题解决方案](../SSE生产环境配置问题解决方案.md) +- [nginx配置文档](../../部署文档/nginx配置/) +- [前端环境变量配置建议](../../部署文档/nginx配置/前端环境变量配置建议.md) + +## 🔄 后续优化 + +1. **监控告警**:添加SSE关闭失败监控 +2. **性能优化**:优化SSE连接管理 +3. **用户体验**:添加退出进度提示 +4. **错误恢复**:实现自动重试机制 + +--- + +**更新时间**:2025-01-03 +**维护人员**:系统管理员 +**文档版本**:v1.0 \ No newline at end of file diff --git a/docs/技术问题解决记录/SSE生产环境配置问题解决方案.md b/docs/技术问题解决记录/SSE生产环境配置问题解决方案.md new file mode 100644 index 00000000..b13744af --- /dev/null +++ b/docs/技术问题解决记录/SSE生产环境配置问题解决方案.md @@ -0,0 +1,256 @@ +# SSE生产环境配置问题解决方案 + +## 🚨 问题描述 + +在生产环境中,通过nginx配置后,前端无法正常接收SSE(Server-Sent Events)消息推送。 + +## 🔍 问题分析 + +### 1. 技术架构分析 + +**后端SSE实现**: +- 使用Spring Boot的`SseEmitter`实现 +- SSE端点:`/resource/sse` +- 支持用户认证和Token验证 +- 使用Redis进行消息广播 + +**前端SSE实现**: +- 使用Vue 3的`useEventSource`组合式API +- 自动重连机制(10次重试,3秒间隔) +- 消息处理:显示通知和存储到状态管理 + +**nginx代理配置**: +- 当前配置缺少专门的SSE代理处理 +- 超时设置可能不够长 +- 缺少SSE特定的响应头配置 + +### 2. 根本原因 + +1. **nginx配置不完整**:缺少专门的SSE代理配置 +2. **超时设置不足**:SSE是长连接,需要更长的超时时间 +3. **响应头配置缺失**:SSE需要特定的Content-Type和缓存控制 +4. **代理优先级问题**:SSE路由可能被其他配置覆盖 + +## 🛠️ 解决方案 + +### 方案一:优化nginx配置(推荐) + +#### 1. 添加专门的SSE代理配置 + +```nginx +# SSE专用代理配置(重要:必须放在其他API配置之前) +location ^~ /prod-api/resource/sse { + proxy_pass http://47.121.125.41:8080/resource/sse; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE特殊配置 + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_cache off; + + # SSE需要更长的超时时间 + proxy_connect_timeout 60s; + proxy_send_timeout 86400s; # 24小时 + proxy_read_timeout 86400s; # 24小时 + + # SSE响应头设置 + proxy_set_header Accept "text/event-stream"; + proxy_set_header Cache-Control "no-cache"; + proxy_set_header Connection "keep-alive"; + + # CORS处理 + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid'; + add_header Access-Control-Allow-Credentials true; + + if ($request_method = 'OPTIONS') { + return 204; + } +} +``` + +#### 2. 关键配置说明 + +- **`proxy_buffering off`**:禁用nginx缓冲,确保SSE消息实时传输 +- **`proxy_cache off`**:禁用缓存,避免消息被缓存 +- **`proxy_read_timeout 86400s`**:设置24小时超时,支持长连接 +- **`proxy_set_header Connection ""`**:清除Connection头,避免代理问题 +- **`add_header Access-Control-Allow-Credentials true`**:支持跨域认证 + +### 方案二:修改前端配置 + +#### 1. 环境变量配置 + +```bash +# 推荐配置:通过nginx代理 +VITE_APP_BASE_API = '/prod-api' +VITE_APP_SSE = true +VITE_APP_WEBSOCKET = false +``` + +#### 2. 备选配置:直接访问后端 + +```bash +# 备选配置:直接访问后端 +VITE_APP_BASE_API = 'http://47.121.125.41:8080' +VITE_APP_SSE = true +VITE_APP_WEBSOCKET = false +``` + +## 📋 部署步骤 + +### 1. 更新nginx配置 + +```bash +# 备份原配置 +cp /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf.backup + +# 更新配置文件 +vim /etc/nginx/conf.d/hzdyjsweb.camasort.com.conf + +# 测试配置 +nginx -t + +# 重新加载配置 +nginx -s reload +``` + +### 2. 更新前端配置 + +```bash +# 进入前端项目目录 +cd /path/to/frontend + +# 更新环境变量 +vim .env.production + +# 重新构建 +npm run build + +# 部署到nginx目录 +cp -r dist/* /root/hzdyjsweb9091/dist/ +``` + +### 3. 重启服务 + +```bash +# 重启后端服务(如果需要) +systemctl restart your-backend-service + +# 检查nginx状态 +systemctl status nginx +``` + +## 🔧 故障排查 + +### 1. 检查nginx配置 + +```bash +# 测试nginx配置 +nginx -t + +# 查看nginx错误日志 +tail -f /var/log/nginx/error.log +tail -f /var/log/nginx/hzdyjsweb.camasort.com.error.log + +# 查看nginx访问日志 +tail -f /var/log/nginx/hzdyjsweb.camasort.com.access.log +``` + +### 2. 检查SSE连接 + +#### 浏览器开发者工具 +1. 打开F12开发者工具 +2. 切换到Network面板 +3. 筛选XHR/Fetch请求 +4. 查找SSE连接(通常显示为EventStream) +5. 检查连接状态和响应头 + +#### 控制台错误信息 +```javascript +// 检查SSE连接状态 +console.log('SSE连接状态:', EventSource.readyState); +// 0: CONNECTING, 1: OPEN, 2: CLOSED +``` + +### 3. 测试SSE端点 + +#### 直接测试后端 +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" \ + -H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \ + http://47.121.125.41:8080/resource/sse +``` + +#### 通过nginx测试 +```bash +curl -H "Authorization: Bearer YOUR_TOKEN" \ + -H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \ + http://hzdyjsweb.camasort.com/prod-api/resource/sse +``` + +### 4. 检查后端SSE服务 + +#### 查看后端日志 +```bash +# 查看应用日志 +tail -f /path/to/backend/logs/application.log + +# 查看SSE相关日志 +grep -i "sse" /path/to/backend/logs/application.log +``` + +#### 检查Redis连接 +```bash +# 连接Redis +redis-cli + +# 检查SSE主题订阅 +SUBSCRIBE global:sse +``` + +## 🎯 验证方法 + +### 1. 功能验证 + +1. **登录系统**:确保用户已登录 +2. **触发SSE消息**:通过工作流或其他功能触发消息推送 +3. **检查通知**:查看是否收到实时通知 +4. **检查控制台**:查看SSE连接状态和消息 + +### 2. 性能验证 + +1. **连接稳定性**:长时间保持连接,检查是否断开 +2. **消息延迟**:测试消息推送的实时性 +3. **并发连接**:测试多用户同时连接的情况 + +### 3. 监控指标 + +- SSE连接数 +- 消息推送成功率 +- 连接断开频率 +- 响应时间 + +## 📚 相关文档 + +- [nginx SSE代理配置官方文档](http://nginx.org/en/docs/http/ngx_http_proxy_module.html) +- [Spring Boot SSE官方文档](https://docs.spring.io/spring-framework/reference/web/webmvc/controller/ann-methods/sse.html) +- [Vue 3 EventSource使用指南](https://vueuse.org/core/useEventSource/) + +## 🔄 后续优化 + +1. **添加SSE连接监控**:监控连接状态和性能 +2. **实现连接池管理**:优化连接资源使用 +3. **添加消息重试机制**:提高消息推送可靠性 +4. **优化nginx配置**:根据实际使用情况调整超时和缓冲设置 + +--- + +**更新时间**:2025-01-03 +**维护人员**:系统管理员 +**文档版本**:v1.0 \ No newline at end of file diff --git a/docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf b/docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf index 62cdacc8..2993ebb8 100644 --- a/docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf +++ b/docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf @@ -41,36 +41,95 @@ server { add_header Pragma "no-cache"; } - # Warm-Flow工作流UI代理配置(重要:必须放在Vue路由配置之前,使用^~确保优先级) - location ^~ /prod-api/warm-flow-ui/ { - proxy_pass http://47.121.125.41:8080/warm-flow-ui/; + # SSE专用代理配置(重要:必须放在其他API配置之前) + location ^~ /prod-api/resource/sse { + proxy_pass http://47.121.125.41:8080/resource/sse; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # 支持WebSocket和SSE + # SSE特殊配置 proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Connection ""; proxy_buffering off; proxy_cache off; - # 超时设置 + # SSE需要更长的超时时间 proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 86400s; + proxy_send_timeout 86400s; # 24小时 + proxy_read_timeout 86400s; # 24小时 + + # SSE响应头设置 + proxy_set_header Accept "text/event-stream"; + proxy_set_header Cache-Control "no-cache"; + proxy_set_header Connection "keep-alive"; # CORS处理 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid'; + add_header Access-Control-Allow-Credentials true; if ($request_method = 'OPTIONS') { return 204; } } + # SSE关闭接口专用配置 + location ^~ /prod-api/resource/sse/close { + proxy_pass http://47.121.125.41:8080/resource/sse/close; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 普通API配置(不需要SSE特殊配置) + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + # CORS处理 + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid'; + add_header Access-Control-Allow-Credentials true; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + + # Warm-Flow工作流UI代理配置(重要:必须放在Vue路由配置之前) + location ^~ /prod-api/warm-flow-ui/ { + proxy_pass http://47.121.125.41:8080/warm-flow-ui/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 支持WebSocket和SSE + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_buffering off; + proxy_cache off; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 86400s; + + # CORS处理 + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS'; + add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,clientid'; + + if ($request_method = 'OPTIONS') { + return 204; + } + } + # API代理配置(使用^~确保优先级,避免被Vue路由配置干扰) location ^~ /prod-api/ { proxy_pass http://47.121.125.41:8080/; diff --git a/docs/部署文档/杭州第一技师部署文档/docker-compose-multi.yml b/docs/部署文档/杭州第一技师部署文档/docker-compose-multi.yml new file mode 100644 index 00000000..b6b3f869 --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/docker-compose-multi.yml @@ -0,0 +1,135 @@ +version: '3.8' + +services: + # 9900端口实例 + ruoyi-admin-9900: + image: openjdk:17-jre-slim + container_name: ruoyi-admin-9900 + ports: + - "9900:9900" + volumes: + - ./ruoyi-admin.jar:/app/ruoyi-admin.jar + - ./logs:/app/logs + - ./temp:/app/temp + working_dir: /app + command: > + java -Xms512m -Xmx1024m + -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m + -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai + -XX:+UseG1GC -XX:MaxGCPauseMillis=200 + -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ + -Dspring.profiles.active=prod-9900 + -Dserver.port=9900 + -jar ruoyi-admin.jar + environment: + - SPRING_PROFILES_ACTIVE=prod-9900 + - SERVER_PORT=9900 + restart: unless-stopped + networks: + - ruoyi-network + + # 9901端口实例 + ruoyi-admin-9901: + image: openjdk:17-jre-slim + container_name: ruoyi-admin-9901 + ports: + - "9901:9901" + volumes: + - ./ruoyi-admin.jar:/app/ruoyi-admin.jar + - ./logs:/app/logs + - ./temp:/app/temp + working_dir: /app + command: > + java -Xms512m -Xmx1024m + -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m + -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai + -XX:+UseG1GC -XX:MaxGCPauseMillis=200 + -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ + -Dspring.profiles.active=prod-9901 + -Dserver.port=9901 + -jar ruoyi-admin.jar + environment: + - SPRING_PROFILES_ACTIVE=prod-9901 + - SERVER_PORT=9901 + restart: unless-stopped + networks: + - ruoyi-network + + # 9902端口实例 + ruoyi-admin-9902: + image: openjdk:17-jre-slim + container_name: ruoyi-admin-9902 + ports: + - "9902:9902" + volumes: + - ./ruoyi-admin.jar:/app/ruoyi-admin.jar + - ./logs:/app/logs + - ./temp:/app/temp + working_dir: /app + command: > + java -Xms512m -Xmx1024m + -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m + -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai + -XX:+UseG1GC -XX:MaxGCPauseMillis=200 + -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ + -Dspring.profiles.active=prod-9902 + -Dserver.port=9902 + -jar ruoyi-admin.jar + environment: + - SPRING_PROFILES_ACTIVE=prod-9902 + - SERVER_PORT=9902 + restart: unless-stopped + networks: + - ruoyi-network + + # 9903端口实例 + ruoyi-admin-9903: + image: openjdk:17-jre-slim + container_name: ruoyi-admin-9903 + ports: + - "9903:9903" + volumes: + - ./ruoyi-admin.jar:/app/ruoyi-admin.jar + - ./logs:/app/logs + - ./temp:/app/temp + working_dir: /app + command: > + java -Xms512m -Xmx1024m + -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m + -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai + -XX:+UseG1GC -XX:MaxGCPauseMillis=200 + -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/ + -Dspring.profiles.active=prod-9903 + -Dserver.port=9903 + -jar ruoyi-admin.jar + environment: + - SPRING_PROFILES_ACTIVE=prod-9903 + - SERVER_PORT=9903 + restart: unless-stopped + networks: + - ruoyi-network + + # Nginx负载均衡器 + nginx: + image: nginx:alpine + container_name: ruoyi-nginx + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - ruoyi-admin-9900 + - ruoyi-admin-9901 + - ruoyi-admin-9902 + - ruoyi-admin-9903 + restart: unless-stopped + networks: + - ruoyi-network + +networks: + ruoyi-network: + driver: bridge + +volumes: + logs: + temp: diff --git a/docs/部署文档/杭州第一技师部署文档/nginx.conf b/docs/部署文档/杭州第一技师部署文档/nginx.conf new file mode 100644 index 00000000..00242db3 --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/nginx.conf @@ -0,0 +1,116 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 日志格式 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log; + + # 基础配置 + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # 上游服务器配置 + upstream ruoyi_backend { + # 轮询负载均衡 + server ruoyi-admin-9900:9900 weight=1 max_fails=3 fail_timeout=30s; + server ruoyi-admin-9901:9901 weight=1 max_fails=3 fail_timeout=30s; + server ruoyi-admin-9902:9902 weight=1 max_fails=3 fail_timeout=30s; + server ruoyi-admin-9903:9903 weight=1 max_fails=3 fail_timeout=30s; + + # 健康检查 + keepalive 32; + } + + # 主服务器配置 + server { + listen 80; + server_name localhost; + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + proxy_pass http://ruoyi_backend; + } + + # API请求 + location / { + proxy_pass http://ruoyi_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 超时设置 + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + + # 缓冲设置 + proxy_buffering on; + proxy_buffer_size 4k; + proxy_buffers 8 4k; + proxy_busy_buffers_size 8k; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # 状态监控页面 + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + deny all; + } + } + + # 监控和日志配置 + server { + listen 8080; + server_name localhost; + + location / { + return 200 "Nginx Load Balancer Status\n"; + add_header Content-Type text/plain; + } + + location /status { + stub_status on; + access_log off; + } + } +} diff --git a/docs/部署文档/杭州第一技师部署文档/start-extend-services.sh b/docs/部署文档/杭州第一技师部署文档/start-extend-services.sh new file mode 100644 index 00000000..f15f39a9 --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/start-extend-services.sh @@ -0,0 +1,368 @@ +#!/bin/bash + +# 扩展服务启动停止一体化脚本 +# 使用方法: ./start-extend-services.sh [start|stop|restart|status] [service_name] +# 服务名称: monitor, snailjob, all + +# 配置变量 +MONITOR_JAR="ruoyi-monitor-admin.jar" +SNAILJOB_JAR="ruoyi-snailjob-server.jar" +JAVA_OPTS="-Xms2048m -Xmx2048m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m" +JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai" +JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200" +JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/" + +# 服务配置 +declare -A SERVICES +SERVICES[monitor]="9090:ruoyi-monitor-admin:prod" +SERVICES[snailjob]="8800:ruoyi-snailjob-server:prod" + +# 日志目录 +LOG_DIR="logs" +mkdir -p $LOG_DIR + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 打印带颜色的消息 +print_message() { + local color=$1 + local message=$2 + echo -e "${color}${message}${NC}" +} + +# 检查JAR文件是否存在 +check_jar() { + local service=$1 + local jar_file="" + + case $service in + monitor) + jar_file="./$MONITOR_JAR" + ;; + snailjob) + jar_file="./$SNAILJOB_JAR" + ;; + esac + + if [ ! -f "$jar_file" ]; then + print_message $RED "错误: $jar_file 文件不存在!" + print_message $YELLOW "请先编译项目: mvn clean package -Pprod" + return 1 + fi + return 0 +} + +# 启动单个服务 +start_service() { + local service=$1 + local config=${SERVICES[$service]} + local port=$(echo $config | cut -d: -f1) + local app_name=$(echo $config | cut -d: -f2) + local profile=$(echo $config | cut -d: -f3) + + local pid_file="logs/${app_name}.pid" + local log_file="logs/${app_name}.log" + + # 检查是否已经运行 + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "服务 $service 已经在运行中 (PID: $pid)" + return + else + # PID文件存在但进程不存在,清理PID文件 + rm -f "$pid_file" + fi + fi + + # 检查JAR文件 + if ! check_jar $service; then + return 1 + fi + + print_message $GREEN "正在启动服务 $service (端口: $port, 配置文件: $profile)..." + + # 启动应用 + case $service in + monitor) + local jar_path="../ruoyi-extend/ruoyi-monitor-admin/target/$MONITOR_JAR" + nohup java $JAVA_OPTS \ + -Dspring.profiles.active=$profile \ + -Dserver.port=$port \ + -jar "$jar_path" \ + > "$log_file" 2>&1 & + ;; + snailjob) + local jar_path="../ruoyi-extend/ruoyi-snailjob-server/target/$SNAILJOB_JAR" + nohup java $JAVA_OPTS \ + -Dspring.profiles.active=$profile \ + -Dserver.port=$port \ + -jar "$jar_path" \ + > "$log_file" 2>&1 & + ;; + esac + + local new_pid=$! + echo $new_pid > "$pid_file" + + # 等待启动 + sleep 5 + + # 检查启动状态 + if ps -p $new_pid > /dev/null 2>&1; then + print_message $GREEN "服务 $service 启动成功 (PID: $new_pid)" + print_message $GREEN "访问地址: http://localhost:$port" + print_message $GREEN "日志文件: $log_file" + + # 显示服务特定信息 + case $service in + monitor) + print_message $BLUE "监控中心: http://localhost:$port/admin" + ;; + snailjob) + print_message $BLUE "任务调度: http://localhost:$port/snail-job" + ;; + esac + else + print_message $RED "服务 $service 启动失败,请检查日志文件: $log_file" + rm -f "$pid_file" + fi +} + +# 停止单个服务 +stop_service() { + local service=$1 + local config=${SERVICES[$service]} + local app_name=$(echo $config | cut -d: -f2) + local pid_file="logs/${app_name}.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "正在停止服务 $service (PID: $pid)..." + kill $pid + + # 等待进程结束 + local count=0 + while ps -p $pid > /dev/null 2>&1 && [ $count -lt 30 ]; do + sleep 1 + count=$((count + 1)) + done + + # 强制杀死进程 + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "强制停止服务 $service..." + kill -9 $pid + fi + + rm -f "$pid_file" + print_message $GREEN "服务 $service 已停止" + else + print_message $YELLOW "服务 $service 未运行" + rm -f "$pid_file" + fi + else + print_message $YELLOW "服务 $service 未运行" + fi +} + +# 重启单个服务 +restart_service() { + local service=$1 + print_message $YELLOW "重启服务 $service..." + stop_service $service + sleep 3 + start_service $service +} + +# 查看服务状态 +status_service() { + local service=$1 + local config=${SERVICES[$service]} + local port=$(echo $config | cut -d: -f1) + local app_name=$(echo $config | cut -d: -f2) + local pid_file="logs/${app_name}.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + local memory=$(ps -o rss= -p $pid | awk '{print $1/1024 " MB"}') + local uptime=$(ps -o etime= -p $pid) + print_message $GREEN "服务 $service: 运行中 (PID: $pid, 端口: $port, 内存: $memory, 运行时间: $uptime)" + else + print_message $RED "服务 $service: 异常 (PID文件存在但进程不存在)" + rm -f "$pid_file" + fi + else + print_message $YELLOW "服务 $service: 未运行 (端口: $port)" + fi +} + +# 启动所有服务 +start_all() { + print_message $GREEN "启动所有扩展服务..." + for service in "${!SERVICES[@]}"; do + start_service $service + sleep 3 + done +} + +# 停止所有服务 +stop_all() { + print_message $YELLOW "停止所有扩展服务..." + for service in "${!SERVICES[@]}"; do + stop_service $service + done +} + +# 重启所有服务 +restart_all() { + print_message $YELLOW "重启所有扩展服务..." + stop_all + sleep 5 + start_all +} + +# 查看所有服务状态 +status_all() { + print_message $GREEN "所有扩展服务状态:" + for service in "${!SERVICES[@]}"; do + status_service $service + done +} + +# 编译项目 +build_project() { + print_message $BLUE "开始编译扩展服务项目..." + + # 编译monitor-admin + print_message $YELLOW "编译 ruoyi-monitor-admin..." + cd ../ruoyi-extend/ruoyi-monitor-admin + mvn clean package -Pprod + if [ $? -eq 0 ]; then + print_message $GREEN "ruoyi-monitor-admin 编译成功" + else + print_message $RED "ruoyi-monitor-admin 编译失败" + return 1 + fi + + # 编译snailjob-server + print_message $YELLOW "编译 ruoyi-snailjob-server..." + cd ../ruoyi-snailjob-server + mvn clean package -Pprod + if [ $? -eq 0 ]; then + print_message $GREEN "ruoyi-snailjob-server 编译成功" + else + print_message $RED "ruoyi-snailjob-server 编译失败" + return 1 + fi + + # 返回原目录 + cd ../../部署文档/杭州第一技师部署文档 + print_message $GREEN "所有扩展服务编译完成" +} + +# 显示帮助信息 +show_help() { + echo "扩展服务启动停止一体化脚本使用方法:" + echo " $0 [命令] [服务名称]" + echo "" + echo "命令:" + echo " start 启动服务" + echo " stop 停止服务" + echo " restart 重启服务" + echo " status 查看服务状态" + echo " build 编译项目" + echo "" + echo "服务名称:" + echo " monitor 启动监控中心服务 (端口: 9090)" + echo " snailjob 启动任务调度服务 (端口: 8800)" + echo " all 操作所有服务" + echo "" + echo "示例:" + echo " $0 start monitor # 启动监控中心服务" + echo " $0 start snailjob # 启动任务调度服务" + echo " $0 start all # 启动所有服务" + echo " $0 stop all # 停止所有服务" + echo " $0 status all # 查看所有服务状态" + echo " $0 build # 编译所有扩展服务" + echo "" + echo "服务访问地址:" + echo " 监控中心: http://localhost:9090/admin" + echo " 任务调度: http://localhost:8800/snail-job" +} + +# 主函数 +main() { + local action=$1 + local service=$2 + + # 检查参数 + if [ -z "$action" ]; then + show_help + exit 1 + fi + + case $action in + start) + if [ "$service" = "all" ]; then + start_all + elif [ -n "${SERVICES[$service]}" ]; then + start_service $service + else + print_message $RED "无效的服务名称: $service" + show_help + exit 1 + fi + ;; + stop) + if [ "$service" = "all" ]; then + stop_all + elif [ -n "${SERVICES[$service]}" ]; then + stop_service $service + else + print_message $RED "无效的服务名称: $service" + show_help + exit 1 + fi + ;; + restart) + if [ "$service" = "all" ]; then + restart_all + elif [ -n "${SERVICES[$service]}" ]; then + restart_service $service + else + print_message $RED "无效的服务名称: $service" + show_help + exit 1 + fi + ;; + status) + if [ "$service" = "all" ]; then + status_all + elif [ -n "${SERVICES[$service]}" ]; then + status_service $service + else + print_message $RED "无效的服务名称: $service" + show_help + exit 1 + fi + ;; + build) + build_project + ;; + *) + print_message $RED "无效的命令: $action" + show_help + exit 1 + ;; + esac +} + +# 执行主函数 +main "$@" \ No newline at end of file diff --git a/docs/部署文档/杭州第一技师部署文档/start-multi-instance.bat b/docs/部署文档/杭州第一技师部署文档/start-multi-instance.bat new file mode 100644 index 00000000..a3ad321f --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/start-multi-instance.bat @@ -0,0 +1,296 @@ +@echo off +chcp 65001 >nul +setlocal enabledelayedexpansion + +REM 多实例启动脚本 (Windows版本) +REM 使用方法: start-multi-instance.bat [start|stop|restart|status] [instance_name] +REM 实例名称: 9900, 9901, 9902, 9903, all + +REM 配置变量 +set JAR_NAME=ruoyi-admin.jar +set JAVA_OPTS=-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m +set JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai +set JAVA_OPTS=%JAVA_OPTS% -XX:+UseG1GC -XX:MaxGCPauseMillis=200 +set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs\ + +REM 日志目录 +if not exist logs mkdir logs + +REM 颜色输出函数 +:print_message +set color=%1 +set message=%2 +if "%color%"=="RED" ( + echo [31m%message%[0m +) else if "%color%"=="GREEN" ( + echo [32m%message%[0m +) else if "%color%"=="YELLOW" ( + echo [33m%message%[0m +) else ( + echo %message% +) +goto :eof + +REM 检查JAR文件是否存在 +:check_jar +if not exist "%JAR_NAME%" ( + call :print_message RED "错误: %JAR_NAME% 文件不存在!" + call :print_message YELLOW "请确保在正确的目录下运行此脚本,或先执行 mvn clean package -Pprod 编译项目" + pause + exit /b 1 +) +goto :eof + +REM 启动单个实例 +:start_instance +set port=%1 +set profile=%2 +set pid_file=logs\ruoyi-admin-%port%.pid +set log_file=logs\ruoyi-admin-%port%.log + +REM 检查是否已经运行 +if exist "%pid_file%" ( + for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i + tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul + if !errorlevel! equ 0 ( + call :print_message YELLOW "实例 %port% 已经在运行中 (PID: !pid!)" + goto :eof + ) else ( + REM PID文件存在但进程不存在,清理PID文件 + del "%pid_file%" 2>nul + ) +) + +call :print_message GREEN "正在启动实例 %port% (配置文件: %profile%)..." + +REM 启动应用 +start /b java %JAVA_OPTS% -Dspring.profiles.active=%profile% -Dserver.port=%port% -jar %JAR_NAME% > "%log_file%" 2>&1 + +REM 等待启动 +timeout /t 3 /nobreak >nul + +REM 查找新启动的进程 +for /f "tokens=2" %%i in ('tasklist /FI "IMAGENAME eq java.exe" /FO CSV ^| findstr /I "java.exe"') do ( + set new_pid=%%i + set new_pid=!new_pid:"=! + echo !new_pid! > "%pid_file%" + call :print_message GREEN "实例 %port% 启动成功 (PID: !new_pid!)" + call :print_message GREEN "访问地址: http://localhost:%port%" + call :print_message GREEN "日志文件: %log_file%" + goto :eof +) + +call :print_message RED "实例 %port% 启动失败,请检查日志文件: %log_file%" +if exist "%pid_file%" del "%pid_file%" +goto :eof + +REM 停止单个实例 +:stop_instance +set port=%1 +set pid_file=logs\ruoyi-admin-%port%.pid + +if exist "%pid_file%" ( + for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i + tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul + if !errorlevel! equ 0 ( + call :print_message YELLOW "正在停止实例 %port% (PID: !pid!)..." + taskkill /PID !pid! /F >nul 2>&1 + del "%pid_file%" 2>nul + call :print_message GREEN "实例 %port% 已停止" + ) else ( + call :print_message YELLOW "实例 %port% 未运行" + del "%pid_file%" 2>nul + ) +) else ( + call :print_message YELLOW "实例 %port% 未运行" +) +goto :eof + +REM 重启单个实例 +:restart_instance +set port=%1 +call :print_message YELLOW "重启实例 %port%..." +call :stop_instance %port% +timeout /t 2 /nobreak >nul +if "%port%"=="9900" call :start_instance %port% prod-9900 +if "%port%"=="9901" call :start_instance %port% prod-9901 +if "%port%"=="9902" call :start_instance %port% prod-9902 +if "%port%"=="9903" call :start_instance %port% prod-9903 +goto :eof + +REM 查看实例状态 +:status_instance +set port=%1 +set pid_file=logs\ruoyi-admin-%port%.pid + +if exist "%pid_file%" ( + for /f "tokens=*" %%i in (%pid_file%) do set pid=%%i + tasklist /FI "PID eq !pid!" 2>nul | find /I "!pid!" >nul + if !errorlevel! equ 0 ( + call :print_message GREEN "实例 %port%: 运行中 (PID: !pid!)" + ) else ( + call :print_message RED "实例 %port%: 异常 (PID文件存在但进程不存在)" + del "%pid_file%" 2>nul + ) +) else ( + call :print_message YELLOW "实例 %port%: 未运行" +) +goto :eof + +REM 启动所有实例 +:start_all +call :print_message GREEN "启动所有实例..." +call :start_instance 9900 prod-9900 +timeout /t 2 /nobreak >nul +call :start_instance 9901 prod-9901 +timeout /t 2 /nobreak >nul +call :start_instance 9902 prod-9902 +timeout /t 2 /nobreak >nul +call :start_instance 9903 prod-9903 +goto :eof + +REM 停止所有实例 +:stop_all +call :print_message YELLOW "停止所有实例..." +call :stop_instance 9900 +call :stop_instance 9901 +call :stop_instance 9902 +call :stop_instance 9903 +goto :eof + +REM 重启所有实例 +:restart_all +call :print_message YELLOW "重启所有实例..." +call :stop_all +timeout /t 3 /nobreak >nul +call :start_all +goto :eof + +REM 查看所有实例状态 +:status_all +call :print_message GREEN "所有实例状态:" +call :status_instance 9900 +call :status_instance 9901 +call :status_instance 9902 +call :status_instance 9903 +goto :eof + +REM 显示帮助信息 +:show_help +echo 多实例启动脚本使用方法: +echo %0 [命令] [实例名称] +echo. +echo 命令: +echo start 启动实例 +echo stop 停止实例 +echo restart 重启实例 +echo status 查看实例状态 +echo. +echo 实例名称: +echo 9900 启动9900端口实例 +echo 9901 启动9901端口实例 +echo 9902 启动9902端口实例 +echo 9903 启动9903端口实例 +echo all 操作所有实例 +echo. +echo 示例: +echo %0 start 9900 # 启动9900端口实例 +echo %0 start all # 启动所有实例 +echo %0 stop 9900 # 停止9900端口实例 +echo %0 status all # 查看所有实例状态 +echo %0 restart all # 重启所有实例 +goto :eof + +REM 主函数 +:main +set action=%1 +set instance=%2 + +REM 检查参数 +if "%action%"=="" ( + call :show_help + pause + exit /b 1 +) + +REM 检查JAR文件 +call :check_jar +if !errorlevel! neq 0 goto :eof + +if "%action%"=="start" ( + if "%instance%"=="all" ( + call :start_all + ) else if "%instance%"=="9900" ( + call :start_instance 9900 prod-9900 + ) else if "%instance%"=="9901" ( + call :start_instance 9901 prod-9901 + ) else if "%instance%"=="9902" ( + call :start_instance 9902 prod-9902 + ) else if "%instance%"=="9903" ( + call :start_instance 9903 prod-9903 + ) else ( + call :print_message RED "无效的实例名称: %instance%" + call :show_help + pause + exit /b 1 + ) +) else if "%action%"=="stop" ( + if "%instance%"=="all" ( + call :stop_all + ) else if "%instance%"=="9900" ( + call :stop_instance 9900 + ) else if "%instance%"=="9901" ( + call :stop_instance 9901 + ) else if "%instance%"=="9902" ( + call :stop_instance 9902 + ) else if "%instance%"=="9903" ( + call :stop_instance 9903 + ) else ( + call :print_message RED "无效的实例名称: %instance%" + call :show_help + pause + exit /b 1 + ) +) else if "%action%"=="restart" ( + if "%instance%"=="all" ( + call :restart_all + ) else if "%instance%"=="9900" ( + call :restart_instance 9900 + ) else if "%instance%"=="9901" ( + call :restart_instance 9901 + ) else if "%instance%"=="9902" ( + call :restart_instance 9902 + ) else if "%instance%"=="9903" ( + call :restart_instance 9903 + ) else ( + call :print_message RED "无效的实例名称: %instance%" + call :show_help + pause + exit /b 1 + ) +) else if "%action%"=="status" ( + if "%instance%"=="all" ( + call :status_all + ) else if "%instance%"=="9900" ( + call :status_instance 9900 + ) else if "%instance%"=="9901" ( + call :status_instance 9901 + ) else if "%instance%"=="9902" ( + call :status_instance 9902 + ) else if "%instance%"=="9903" ( + call :status_instance 9903 + ) else ( + call :print_message RED "无效的实例名称: %instance%" + call :show_help + pause + exit /b 1 + ) +) else ( + call :print_message RED "无效的命令: %action%" + call :show_help + pause + exit /b 1 +) + +pause +goto :eof diff --git a/docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh b/docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh new file mode 100644 index 00000000..73e11c8d --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh @@ -0,0 +1,284 @@ +#!/bin/bash + +# 多实例启动脚本 +# 使用方法: ./start-multi-instance.sh [start|stop|restart|status] [instance_name] +# 实例名称: 9900, 9901, 9902, 9903, all + +# 配置变量 +JAR_NAME="ruoyi-admin.jar" +JAVA_OPTS="-Xms8192m -Xmx8192m -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m" +JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai" +JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=200" +JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=logs/" + +# 实例配置 +declare -A INSTANCES +INSTANCES[9900]="prod-9900" +INSTANCES[9901]="prod-9901" +INSTANCES[9902]="prod-9902" +INSTANCES[9903]="prod-9903" + +# 日志目录 +LOG_DIR="logs" +mkdir -p $LOG_DIR + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 打印带颜色的消息 +print_message() { + local color=$1 + local message=$2 + echo -e "${color}${message}${NC}" +} + +# 检查JAR文件是否存在 +check_jar() { + if [ ! -f "$JAR_NAME" ]; then + print_message $RED "错误: $JAR_NAME 文件不存在!" + print_message $YELLOW "请确保在正确的目录下运行此脚本,或先执行 mvn clean package -Pprod 编译项目" + exit 1 + fi +} + +# 启动单个实例 +start_instance() { + local port=$1 + local profile=$2 + + local pid_file="logs/ruoyi-admin-${port}.pid" + local log_file="logs/ruoyi-admin-${port}.log" + + # 检查是否已经运行 + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "实例 $port 已经在运行中 (PID: $pid)" + return + else + # PID文件存在但进程不存在,清理PID文件 + rm -f "$pid_file" + fi + fi + + print_message $GREEN "正在启动实例 $port (配置文件: $profile)..." + + # 启动应用 + nohup java $JAVA_OPTS \ + -Dspring.profiles.active=prod,$profile \ + -Dserver.port=$port \ + -jar $JAR_NAME \ + > "$log_file" 2>&1 & + + local new_pid=$! + echo $new_pid > "$pid_file" + + # 等待启动 + sleep 3 + + # 检查启动状态 + if ps -p $new_pid > /dev/null 2>&1; then + print_message $GREEN "实例 $port 启动成功 (PID: $new_pid)" + print_message $GREEN "访问地址: http://localhost:$port" + print_message $GREEN "日志文件: $log_file" + else + print_message $RED "实例 $port 启动失败,请检查日志文件: $log_file" + rm -f "$pid_file" + fi +} + +# 停止单个实例 +stop_instance() { + local port=$1 + local pid_file="logs/ruoyi-admin-${port}.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "正在停止实例 $port (PID: $pid)..." + kill $pid + + # 等待进程结束 + local count=0 + while ps -p $pid > /dev/null 2>&1 && [ $count -lt 30 ]; do + sleep 1 + count=$((count + 1)) + done + + # 强制杀死进程 + if ps -p $pid > /dev/null 2>&1; then + print_message $YELLOW "强制停止实例 $port..." + kill -9 $pid + fi + + rm -f "$pid_file" + print_message $GREEN "实例 $port 已停止" + else + print_message $YELLOW "实例 $port 未运行" + rm -f "$pid_file" + fi + else + print_message $YELLOW "实例 $port 未运行" + fi +} + +# 重启单个实例 +restart_instance() { + local port=$1 + print_message $YELLOW "重启实例 $port..." + stop_instance $port + sleep 2 + start_instance $port ${INSTANCES[$port]} +} + +# 查看实例状态 +status_instance() { + local port=$1 + local pid_file="logs/ruoyi-admin-${port}.pid" + + if [ -f "$pid_file" ]; then + local pid=$(cat "$pid_file") + if ps -p $pid > /dev/null 2>&1; then + local memory=$(ps -o rss= -p $pid | awk '{print $1/1024 " MB"}') + local uptime=$(ps -o etime= -p $pid) + print_message $GREEN "实例 $port: 运行中 (PID: $pid, 内存: $memory, 运行时间: $uptime)" + else + print_message $RED "实例 $port: 异常 (PID文件存在但进程不存在)" + rm -f "$pid_file" + fi + else + print_message $YELLOW "实例 $port: 未运行" + fi +} + +# 启动所有实例 +start_all() { + print_message $GREEN "启动所有实例..." + for port in "${!INSTANCES[@]}"; do + start_instance $port ${INSTANCES[$port]} + sleep 2 + done +} + +# 停止所有实例 +stop_all() { + print_message $YELLOW "停止所有实例..." + for port in "${!INSTANCES[@]}"; do + stop_instance $port + done +} + +# 重启所有实例 +restart_all() { + print_message $YELLOW "重启所有实例..." + stop_all + sleep 3 + start_all +} + +# 查看所有实例状态 +status_all() { + print_message $GREEN "所有实例状态:" + for port in "${!INSTANCES[@]}"; do + status_instance $port + done +} + +# 显示帮助信息 +show_help() { + echo "多实例启动脚本使用方法:" + echo " $0 [命令] [实例名称]" + echo "" + echo "命令:" + echo " start 启动实例" + echo " stop 停止实例" + echo " restart 重启实例" + echo " status 查看实例状态" + echo "" + echo "实例名称:" + echo " 9900 启动9900端口实例" + echo " 9901 启动9901端口实例" + echo " 9902 启动9902端口实例" + echo " 9903 启动9903端口实例" + echo " all 操作所有实例" + echo "" + echo "示例:" + echo " $0 start 9900 # 启动9900端口实例" + echo " $0 start all # 启动所有实例" + echo " $0 stop 9900 # 停止9900端口实例" + echo " $0 status all # 查看所有实例状态" + echo " $0 restart all # 重启所有实例" +} + +# 主函数 +main() { + local action=$1 + local instance=$2 + + # 检查参数 + if [ -z "$action" ]; then + show_help + exit 1 + fi + + # 检查JAR文件 + check_jar + + case $action in + start) + if [ "$instance" = "all" ]; then + start_all + elif [ -n "${INSTANCES[$instance]}" ]; then + start_instance $instance ${INSTANCES[$instance]} + else + print_message $RED "无效的实例名称: $instance" + show_help + exit 1 + fi + ;; + stop) + if [ "$instance" = "all" ]; then + stop_all + elif [ -n "${INSTANCES[$instance]}" ]; then + stop_instance $instance + else + print_message $RED "无效的实例名称: $instance" + show_help + exit 1 + fi + ;; + restart) + if [ "$instance" = "all" ]; then + restart_all + elif [ -n "${INSTANCES[$instance]}" ]; then + restart_instance $instance + else + print_message $RED "无效的实例名称: $instance" + show_help + exit 1 + fi + ;; + status) + if [ "$instance" = "all" ]; then + status_all + elif [ -n "${INSTANCES[$instance]}" ]; then + status_instance $instance + else + print_message $RED "无效的实例名称: $instance" + show_help + exit 1 + fi + ;; + *) + print_message $RED "无效的命令: $action" + show_help + exit 1 + ;; + esac +} + +# 执行主函数 +main "$@" diff --git a/docs/部署文档/杭州第一技师部署文档/test-config-fix.sh b/docs/部署文档/杭州第一技师部署文档/test-config-fix.sh new file mode 100644 index 00000000..8939d900 --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/test-config-fix.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# 配置修复验证脚本 +# 使用方法: ./test-config-fix.sh + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +print_message() { + local color=$1 + local message=$2 + echo -e "${color}${message}${NC}" +} + +print_message $GREEN "=== 多实例配置修复验证脚本 ===" + +# 检查JAR文件 +JAR_NAME="ruoyi-admin.jar" +if [ ! -f "$JAR_NAME" ]; then + print_message $RED "错误: $JAR_NAME 文件不存在!" + print_message $YELLOW "请先执行 mvn clean package -Pprod 编译项目" + exit 1 +fi + +print_message $GREEN "✓ JAR文件存在" + +# 检查配置文件 +CONFIG_FILES=( + "backend/ruoyi-admin/src/main/resources/application-prod-9900.yml" + "backend/ruoyi-admin/src/main/resources/application-prod-9901.yml" + "backend/ruoyi-admin/src/main/resources/application-prod-9902.yml" + "backend/ruoyi-admin/src/main/resources/application-prod-9903.yml" +) + +for config_file in "${CONFIG_FILES[@]}"; do + if [ -f "$config_file" ]; then + # 检查是否包含spring.profiles.include + if grep -q "spring.profiles.include" "$config_file"; then + print_message $RED "✗ $config_file 仍包含 spring.profiles.include" + else + print_message $GREEN "✓ $config_file 配置正确" + fi + else + print_message $RED "✗ $config_file 文件不存在" + fi +done + +# 检查启动脚本 +START_SCRIPT="docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh" +if [ -f "$START_SCRIPT" ]; then + if grep -q "spring.profiles.active=prod,\$profile" "$START_SCRIPT"; then + print_message $GREEN "✓ 启动脚本配置正确" + else + print_message $RED "✗ 启动脚本配置错误" + fi +else + print_message $RED "✗ 启动脚本不存在" +fi + +print_message $GREEN "" +print_message $GREEN "=== 验证完成 ===" +print_message $YELLOW "如果所有检查都通过,可以尝试启动实例:" +print_message $YELLOW " ./start-multi-instance.sh start 9900" \ No newline at end of file diff --git a/docs/部署文档/杭州第一技师部署文档/多实例启动配置修复说明.md b/docs/部署文档/杭州第一技师部署文档/多实例启动配置修复说明.md new file mode 100644 index 00000000..9b1a73a7 --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/多实例启动配置修复说明.md @@ -0,0 +1,96 @@ +# 多实例启动配置修复说明 + +## 问题描述 + +在使用 `start-multi-instance.sh` 启动实例时,出现以下错误: + +``` +org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property 'spring.profiles.include' imported from location 'class path resource [application-prod-9900.yml]' is invalid in a profile specific resource +``` + +## 问题原因 + +在Spring Boot 2.4+版本中,`spring.profiles.include` 属性不能在profile-specific的配置文件中使用(如 `application-prod-9900.yml`)。这个属性只能在主配置文件(`application.yml`)中使用。 + +## 修复方案 + +### 1. 修改配置文件结构 + +移除了所有实例配置文件中的 `spring.profiles.include: prod` 配置: + +**修复前:** +```yaml +# application-prod-9900.yml +spring: + profiles: + include: prod # ❌ 这在profile-specific文件中不被允许 +``` + +**修复后:** +```yaml +# application-prod-9900.yml +spring: + application: + name: RuoYi-Vue-Plus-9900 # ✅ 直接配置应用名称 +``` + +### 2. 修改启动脚本 + +修改启动脚本,在启动时同时激活 `prod` 和具体的实例profile: + +**修复前:** +```bash +-Dspring.profiles.active=$profile # 只激活 prod-9900 +``` + +**修复后:** +```bash +-Dspring.profiles.active=prod,$profile # 同时激活 prod 和 prod-9900 +``` + +## 修复的文件 + +1. `backend/ruoyi-admin/src/main/resources/application-prod-9900.yml` +2. `backend/ruoyi-admin/src/main/resources/application-prod-9901.yml` +3. `backend/ruoyi-admin/src/main/resources/application-prod-9902.yml` +4. `backend/ruoyi-admin/src/main/resources/application-prod-9903.yml` +5. `docs/部署文档/杭州第一技师部署文档/start-multi-instance.sh` + +## 配置加载顺序 + +修复后的配置加载顺序: + +1. `application.yml` (主配置文件) +2. `application-prod.yml` (生产环境基础配置) +3. `application-prod-9900.yml` (9900端口实例特定配置) + +## 验证方法 + +1. 重新编译项目: + ```bash + mvn clean package -Pprod + ``` + +2. 启动单个实例测试: + ```bash + ./start-multi-instance.sh start 9900 + ``` + +3. 检查启动日志,确认没有配置错误 + +4. 访问应用验证功能正常: + ```bash + curl http://localhost:9900 + ``` + +## 注意事项 + +1. 确保在修改配置文件后重新编译项目 +2. 如果之前有实例在运行,需要先停止再重新启动 +3. 检查日志文件确认配置加载正确 +4. 验证监控中心和SnailJob配置是否正常工作 + +## 相关文档 + +- [Spring Boot Profile配置最佳实践](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific) +- [多实例部署指南](../README.md) \ No newline at end of file diff --git a/docs/部署文档/杭州第一技师部署文档/多实例部署说明.md b/docs/部署文档/杭州第一技师部署文档/多实例部署说明.md new file mode 100644 index 00000000..09af58de --- /dev/null +++ b/docs/部署文档/杭州第一技师部署文档/多实例部署说明.md @@ -0,0 +1,293 @@ +# 🚀 RuoYi-Admin 多实例部署说明 + +## 📋 概述 + +本文档说明如何在生产环境中部署多个 `ruoyi-admin.jar` 实例到不同端口,实现负载均衡和高可用。 + +## 🏗️ 部署架构 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 前端应用 │ │ Nginx │ │ 后端实例 │ +│ │ │ 负载均衡器 │ │ │ +│ http://80 │───▶│ http://80 │───▶│ 9900, 9901 │ +│ │ │ │ │ 9902, 9903 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +## 📁 文件结构 + +``` +backend/ +├── ruoyi-admin.jar # 编译后的JAR包 +├── application-prod-9900.yml # 9900端口配置文件 +├── application-prod-9901.yml # 9901端口配置文件 +├── application-prod-9902.yml # 9902端口配置文件 +├── application-prod-9903.yml # 9903端口配置文件 +├── start-multi-instance.sh # Linux启动脚本 +├── start-multi-instance.bat # Windows启动脚本 +├── docker-compose-multi.yml # Docker部署配置 +├── nginx.conf # Nginx负载均衡配置 +└── logs/ # 日志目录 + ├── ruoyi-admin-9900.log + ├── ruoyi-admin-9901.log + ├── ruoyi-admin-9902.log + └── ruoyi-admin-9903.log +``` + +## 🔧 部署步骤 + +### 1. 编译项目 + +```bash +# 进入backend目录 +cd backend + +# 编译生产环境JAR包 +mvn clean package -Pprod + +# 检查生成的JAR文件 +ls -la ruoyi-admin/target/ruoyi-admin.jar +``` + +### 2. 准备部署文件 + +```bash +# 复制JAR文件到部署目录 +cp ruoyi-admin/target/ruoyi-admin.jar ./ + +# 创建日志目录 +mkdir -p logs + +# 设置脚本执行权限 (Linux) +chmod +x start-multi-instance.sh +``` + +### 3. 启动多实例 + +#### Linux环境 + +```bash +# 启动所有实例 +./start-multi-instance.sh start all + +# 启动单个实例 +./start-multi-instance.sh start 9900 +./start-multi-instance.sh start 9901 +./start-multi-instance.sh start 9902 +./start-multi-instance.sh start 9903 + +# 查看实例状态 +./start-multi-instance.sh status all + +# 停止实例 +./start-multi-instance.sh stop all + +# 重启实例 +./start-multi-instance.sh restart all +``` + +#### Windows环境 + +```cmd +# 启动所有实例 +start-multi-instance.bat start all + +# 启动单个实例 +start-multi-instance.bat start 9900 +start-multi-instance.bat start 9901 +start-multi-instance.bat start 9902 +start-multi-instance.bat start 9903 + +# 查看实例状态 +start-multi-instance.bat status all + +# 停止实例 +start-multi-instance.bat stop all + +# 重启实例 +start-multi-instance.bat restart all +``` + +### 4. Docker部署 (可选) + +```bash +# 启动所有服务 +docker-compose -f docker-compose-multi.yml up -d + +# 查看服务状态 +docker-compose -f docker-compose-multi.yml ps + +# 查看日志 +docker-compose -f docker-compose-multi.yml logs -f + +# 停止服务 +docker-compose -f docker-compose-multi.yml down +``` + +## 🌐 访问地址 + +### 直接访问各实例 +- **实例1**: http://localhost:9900 +- **实例2**: http://localhost:9901 +- **实例3**: http://localhost:9902 +- **实例4**: http://localhost:9903 + +### 通过负载均衡器访问 +- **主入口**: http://localhost (Nginx负载均衡) +- **监控页面**: http://localhost:8080/status + +## 📊 监控和管理 + +### 1. 实例状态监控 + +```bash +# 查看所有实例状态 +./start-multi-instance.sh status all + +# 查看单个实例状态 +./start-multi-instance.sh status 9900 +``` + +### 2. 日志监控 + +```bash +# 查看实时日志 +tail -f logs/ruoyi-admin-9900.log +tail -f logs/ruoyi-admin-9901.log +tail -f logs/ruoyi-admin-9902.log +tail -f logs/ruoyi-admin-9903.log + +# 查看错误日志 +grep "ERROR" logs/ruoyi-admin-*.log +``` + +### 3. 进程监控 + +```bash +# 查看Java进程 +ps aux | grep java + +# 查看端口占用 +netstat -tlnp | grep :99 +``` + +## ⚠️ 注意事项 + +### 1. 端口冲突 +- 确保9900-9903端口未被其他服务占用 +- 检查防火墙设置,确保端口可访问 + +### 2. 内存配置 +- 每个实例默认配置:最小512MB,最大1024MB +- 根据服务器配置调整 `JAVA_OPTS` 参数 + +### 3. 数据库连接 +- 所有实例共享同一个数据库 +- 确保数据库连接池配置合理,避免连接数过多 + +### 4. 文件上传 +- 临时文件目录:`/ruoyi/server/temp` +- 确保目录存在且有写权限 + +## 🔄 故障排查 + +### 1. 实例启动失败 + +```bash +# 检查JAR文件是否存在 +ls -la ruoyi-admin.jar + +# 检查端口是否被占用 +netstat -tlnp | grep :99 + +# 查看启动日志 +tail -f logs/ruoyi-admin-9900.log +``` + +### 2. 实例无法访问 + +```bash +# 检查防火墙设置 +firewall-cmd --list-ports + +# 检查SELinux状态 (CentOS/RHEL) +getenforce + +# 测试端口连通性 +telnet localhost 9900 +``` + +### 3. 负载均衡不工作 + +```bash +# 检查Nginx状态 +docker-compose -f docker-compose-multi.yml ps nginx + +# 查看Nginx日志 +docker-compose -f docker-compose-multi.yml logs nginx + +# 测试后端服务 +curl http://localhost:9900/health +``` + +## 📈 性能优化建议 + +### 1. JVM调优 +```bash +# 根据服务器内存调整JVM参数 +JAVA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m" +``` + +### 2. 数据库连接池优化 +```yaml +# 在application-prod-*.yml中调整 +spring: + datasource: + hikari: + maxPoolSize: 100 # 根据实例数调整 + minIdle: 20 + connectionTimeout: 30000 +``` + +### 3. 缓存配置 +```yaml +# Redis连接池优化 +redisson: + threads: 32 + nettyThreads: 64 + singleServerConfig: + connectionPoolSize: 128 +``` + +## 🚀 扩展部署 + +### 1. 添加更多实例 +- 复制配置文件模板 +- 修改端口号和应用名称 +- 更新启动脚本和Docker配置 + +### 2. 集群部署 +- 使用Redis集群 +- 配置数据库主从复制 +- 实现会话共享 + +### 3. 容器化部署 +- 使用Kubernetes管理 +- 配置自动扩缩容 +- 实现滚动更新 + +## 📞 技术支持 + +如遇到部署问题,请检查: +1. 系统日志和应用日志 +2. 端口占用情况 +3. 防火墙和网络配置 +4. 数据库连接状态 +5. 内存和磁盘空间 + +--- + +**📝 文档版本**: v1.0 +**🔄 最后更新**: 2025-01-03 +**👨‍💻 维护人员**: 开发团队 \ No newline at end of file diff --git a/docs/问题解决方案/SnailJob命名空间配置修复方案.md b/docs/问题解决方案/SnailJob命名空间配置修复方案.md new file mode 100644 index 00000000..95cf0a36 --- /dev/null +++ b/docs/问题解决方案/SnailJob命名空间配置修复方案.md @@ -0,0 +1,96 @@ +# SnailJob命名空间配置修复方案 + +## 问题描述 + +四个ruoyi-admin实例启动后都提示SnailJob Token认证失败: + +``` +2025-08-31 01:44:09 [snail-job-grpc-client-executor-172.16.0.67-1216] ERROR c.a.s.c.c.handler.ClientRegister + - heartbeat check requestId:[1216] message:[Token authentication failed. [namespace:prod,prod-9900 groupName:ruoyi_group_9900 token:SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT]] +``` + +## 问题分析 + +### 根本原因 +1. **命名空间配置不匹配**:客户端尝试连接的命名空间是 `prod,prod-9900`,但数据库中的命名空间只有 `prod` +2. **动态配置问题**:配置文件使用 `${spring.profiles.active}` 作为命名空间,导致9900-9903实例的命名空间变成了 `prod-9900`, `prod-9901` 等 +3. **数据库数据缺失**:数据库中缺少对应的命名空间记录 + +### 配置对比 + +**数据库中的命名空间:** +```sql +1 Development dev +2 Production prod +``` + +**客户端尝试连接的命名空间:** +- prod,prod-9900 +- prod,prod-9901 +- prod,prod-9902 +- prod,prod-9903 + +## 解决方案 + +### 方案一:修改配置文件(已实施) + +将所有实例的命名空间统一为 `prod`,而不是使用 `${spring.profiles.active}`: + +**修改的文件:** +- `application-prod-9900.yml` +- `application-prod-9901.yml` +- `application-prod-9902.yml` +- `application-prod-9903.yml` + +**修改内容:** +```yaml +snail-job: + namespace: "prod" # 从 ${spring.profiles.active} 改为固定值 +``` + +### 方案二:数据库方案(备选) + +如果希望保持动态命名空间配置,可以在数据库中创建对应的命名空间: + +```sql +-- 为每个实例创建对应的命名空间 +INSERT INTO `sj_namespace` VALUES (3, 'Production-9900', 'prod-9900', '', 0, now(), now()); +INSERT INTO `sj_namespace` VALUES (4, 'Production-9901', 'prod-9901', '', 0, now(), now()); +INSERT INTO `sj_namespace` VALUES (5, 'Production-9902', 'prod-9902', '', 0, now(), now()); +INSERT INTO `sj_namespace` VALUES (6, 'Production-9903', 'prod-9903', '', 0, now(), now()); + +-- 为每个命名空间创建对应的组配置 +INSERT INTO `sj_group_config` VALUES (13, 'prod-9900', 'ruoyi_group_9900', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now()); +INSERT INTO `sj_group_config` VALUES (14, 'prod-9901', 'ruoyi_group_9901', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now()); +INSERT INTO `sj_group_config` VALUES (15, 'prod-9902', 'ruoyi_group_9902', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now()); +INSERT INTO `sj_group_config` VALUES (16, 'prod-9903', 'ruoyi_group_9903', '', 'SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT', 1, 1, 0, 1, 1, now(), now()); +``` + +## 验证步骤 + +1. **重启应用实例** + ```bash + # 重启所有实例 + ./hot-start.sh + ``` + +2. **检查日志** + - 确认不再出现Token认证失败错误 + - 确认SnailJob客户端成功注册 + +3. **验证连接** + - 访问SnailJob管理界面 + - 确认所有实例都显示为在线状态 + +## 最佳实践建议 + +1. **命名空间管理**:建议使用统一的命名空间(如 `prod`),避免动态配置导致的复杂性 +2. **组名管理**:使用有意义的组名,便于识别和管理 +3. **配置验证**:在部署前验证配置文件与数据库配置的一致性 +4. **监控告警**:配置SnailJob连接状态的监控告警 + +## 相关文档 + +- [SnailJob官方文档](https://github.com/aizuda/snail-job) +- [项目部署文档](../部署文档/) +- [数据库设计文档](../设计文档/database/) \ No newline at end of file diff --git a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue index beb6612b..545330e1 100644 --- a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue +++ b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue @@ -2652,7 +2652,7 @@ const handleAdd = async () => { form.majorName = classData.major_name || classData.majorName; // 确保学历层次为数字类型,避免类型不匹配导致的显示问题 const classType = classData.class_type || classData.classType; - form.academicLevel = classType ? Number(classType) : undefined; + form.academicLevel = classType ? classType : undefined; form.grade = classData.grade; form.enrollmentYear = classData.enrollment_year || classData.enrollmentYear; form.classTeacherId = classData.class_teacher_id || classData.classTeacherId;