From f030749dd5cfad8a0561ea12e72cc1e2de2515fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=97=AD=E6=98=8E?= Date: Thu, 28 Aug 2025 13:55:36 +0800 Subject: [PATCH 1/8] stgl3.1 --- .../stgl/controller/StglMyController.java | 5 +- .../org/dromara/stgl/mapper/StglMyMapper.java | 8 +- .../dromara/stgl/service/IStglMyService.java | 3 +- .../stgl/service/impl/StglMyServiceImpl.java | 4 +- .../main/resources/mapper/StglMyMapper.xml | 56 +- web/src/api/stgl/my/types.ts | 4 +- .../components/ActivityDetailView.vue | 617 +++++++++--------- .../club/info/components/JoinedMembersTab.vue | 5 +- .../info/components/PendingMembersTab.vue | 5 +- web/src/views/stgl/my/club/index.vue | 8 +- web/src/views/stgl/my/record/index.vue | 60 +- web/src/views/stgl/my/volunteer/index.vue | 6 +- .../stgl/volunteer/registration/index.vue | 7 + 13 files changed, 408 insertions(+), 380 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMyController.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMyController.java index 6f12bd15..c76f423f 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMyController.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/controller/StglMyController.java @@ -80,8 +80,9 @@ public class StglMyController extends BaseController { @GetMapping("/registrations") public TableDataInfo getMyRegistrations( @Parameter(description = "分页参数") PageQuery pageQuery, - @Parameter(description = "报名类型(1-社团 2-志愿团队 3-活动)") @RequestParam(required = false) String registrationType) { - return stglMyService.queryMyRegistrations(pageQuery, registrationType); + @Parameter(description = "报名类型(1-社团 2-志愿团队 3-活动)") @RequestParam(required = false) String registrationType, + @Parameter(description = "名称(模糊查询)") @RequestParam(required = false) String name) { + return stglMyService.queryMyRegistrations(pageQuery, registrationType, name); } /** diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMyMapper.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMyMapper.java index 1ae29933..56237a84 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMyMapper.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/mapper/StglMyMapper.java @@ -54,10 +54,12 @@ public interface StglMyMapper { * * @param userId 用户ID * @param registrationType 报名类型(可选) + * @param name 名称(可选,模糊查询) * @return 报名记录列表 */ List selectMyRegistrations(@Param("userId") Long userId, - @Param("registrationType") String registrationType); + @Param("registrationType") String registrationType, + @Param("name") String name); /** * 分页查询我的报名记录(社团+志愿团队+活动) @@ -65,11 +67,13 @@ public interface StglMyMapper { * @param page 分页对象 * @param userId 用户ID * @param registrationType 报名类型(可选) + * @param name 名称(可选,模糊查询) * @return 分页结果 */ Page selectMyRegistrationsPage(@Param("page") Page page, @Param("userId") Long userId, - @Param("registrationType") String registrationType); + @Param("registrationType") String registrationType, + @Param("name") String name); /** * 查询我的报名记录总数 diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMyService.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMyService.java index c04bf3a2..e056827d 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMyService.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/IStglMyService.java @@ -47,9 +47,10 @@ public interface IStglMyService { * * @param pageQuery 分页参数 * @param registrationType 报名类型(可选) + * @param name 名称(可选,模糊查询) * @return 报名记录分页信息 */ - TableDataInfo queryMyRegistrations(PageQuery pageQuery, String registrationType); + TableDataInfo queryMyRegistrations(PageQuery pageQuery, String registrationType, String name); /** * 对活动进行评分 diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMyServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMyServiceImpl.java index 592c1bae..6cb9406c 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMyServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglMyServiceImpl.java @@ -72,10 +72,10 @@ public class StglMyServiceImpl implements IStglMyService { * 分页查询我的报名记录 */ @Override - public TableDataInfo queryMyRegistrations(PageQuery pageQuery, String registrationType) { + public TableDataInfo queryMyRegistrations(PageQuery pageQuery, String registrationType, String name) { Long userId = LoginHelper.getUserId(); - Page result = stglMyMapper.selectMyRegistrationsPage(pageQuery.build(), userId, registrationType); + Page result = stglMyMapper.selectMyRegistrationsPage(pageQuery.build(), userId, registrationType, name); return TableDataInfo.build(result); } diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml index 6188531c..2df4193b 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/StglMyMapper.xml @@ -267,8 +267,8 @@ NULL as approval_time, NULL as approval_by, m.remark, - CASE WHEN m.status = '1' THEN '正常' ELSE '待审核' END as current_status, - (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_role' AND dict_value = CAST(m.role_type AS CHAR) AND tenant_id = '000000') as role, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_status' AND dict_value = CAST(m.status AS CHAR) AND tenant_id = '000000') as current_status, + m.role_type as role, c.president_name as organizer, c.establish_date as time_info, '' as location, @@ -279,6 +279,9 @@ FROM stgl_member m INNER JOIN stgl_club c ON m.relation_id = c.id AND m.member_type = 1 AND m.tenant_id = '000000' AND c.tenant_id = '000000' WHERE m.user_id = #{userId} AND m.deleted = 0 AND c.deleted = 0 + + AND c.club_name LIKE CONCAT('%', #{name}, '%') + ) UNION ALL ( @@ -293,7 +296,7 @@ NULL as approval_time, NULL as approval_by, m.remark, - CASE WHEN m.status = '1' THEN '正常' ELSE '待审核' END as current_status, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_status' AND dict_value = CAST(m.status AS CHAR) AND tenant_id = '000000') as current_status, (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_volunteer_role' AND dict_value = CAST(m.role_type AS CHAR) AND tenant_id = '000000') as role, vt.leader_name as organizer, vt.establish_date as time_info, @@ -305,6 +308,9 @@ FROM stgl_member m INNER JOIN stgl_volunteer_team vt ON m.relation_id = vt.id AND m.member_type = 2 AND m.tenant_id = '000000' AND vt.tenant_id = '000000' WHERE m.user_id = #{userId} AND m.deleted = 0 AND vt.deleted = 0 + + AND vt.team_name LIKE CONCAT('%', #{name}, '%') + ) UNION ALL ( @@ -319,7 +325,7 @@ ar.approval_time, ar.approval_by, ar.remark, - CASE WHEN ar.approval_status = '1' THEN '已报名' ELSE '待审核' END as current_status, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_approval_status' AND dict_value = CAST(ar.approval_status AS CHAR) AND tenant_id = '000000') as current_status, '参与者' as role, a.organizer_name as organizer, a.start_time as time_info, @@ -331,10 +337,18 @@ FROM stgl_activity_registration ar INNER JOIN stgl_activity a ON ar.activity_id = a.id AND ar.tenant_id = '000000' AND a.tenant_id = '000000' WHERE ar.user_id = #{userId} AND ar.deleted = 0 AND a.deleted = 0 + + AND a.activity_name LIKE CONCAT('%', #{name}, '%') + ) - - WHERE registration_type = #{registrationType} - + + + AND registration_type = #{registrationType} + + + AND target_name LIKE CONCAT('%', #{name}, '%') + + ORDER BY registration_time DESC @@ -379,8 +393,8 @@ NULL as approval_time, NULL as approval_by, m.remark, - CASE WHEN m.status = '1' THEN '正常' ELSE '待审核' END as current_status, - (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_role' AND dict_value = CAST(m.role_type AS CHAR) AND tenant_id = '000000') as role, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_status' AND dict_value = CAST(m.status AS CHAR) AND tenant_id = '000000') as current_status, + m.role_type as role, c.president_name as organizer, c.establish_date as time_info, '' as location, @@ -391,6 +405,9 @@ FROM stgl_member m INNER JOIN stgl_club c ON m.relation_id = c.id AND m.member_type = 1 AND m.tenant_id = '000000' AND c.tenant_id = '000000' WHERE m.user_id = #{userId} AND m.deleted = 0 AND c.deleted = 0 + + AND c.club_name LIKE CONCAT('%', #{name}, '%') + ) UNION ALL ( @@ -405,7 +422,7 @@ NULL as approval_time, NULL as approval_by, m.remark, - CASE WHEN m.status = '1' THEN '正常' ELSE '待审核' END as current_status, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_member_status' AND dict_value = CAST(m.status AS CHAR) AND tenant_id = '000000') as current_status, (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_volunteer_role' AND dict_value = CAST(m.role_type AS CHAR) AND tenant_id = '000000') as role, vt.leader_name as organizer, vt.establish_date as time_info, @@ -417,6 +434,9 @@ FROM stgl_member m INNER JOIN stgl_volunteer_team vt ON m.relation_id = vt.id AND m.member_type = 2 AND m.tenant_id = '000000' AND vt.tenant_id = '000000' WHERE m.user_id = #{userId} AND m.deleted = 0 AND vt.deleted = 0 + + AND vt.team_name LIKE CONCAT('%', #{name}, '%') + ) UNION ALL ( @@ -431,7 +451,7 @@ ar.approval_time, ar.approval_by, ar.remark, - CASE WHEN ar.approval_status = '1' THEN '已报名' ELSE '待审核' END as current_status, + (SELECT dict_label FROM sys_dict_data WHERE dict_type = 'stgl_approval_status' AND dict_value = CAST(ar.approval_status AS CHAR) AND tenant_id = '000000') as current_status, '参与者' as role, a.organizer_name as organizer, a.start_time as time_info, @@ -443,11 +463,19 @@ FROM stgl_activity_registration ar INNER JOIN stgl_activity a ON ar.activity_id = a.id AND ar.tenant_id = '000000' AND a.tenant_id = '000000' WHERE ar.user_id = #{userId} AND ar.deleted = 0 AND a.deleted = 0 + + AND a.activity_name LIKE CONCAT('%', #{name}, '%') + ) ) all_registrations - - WHERE registration_type = #{registrationType} - + + + AND registration_type = #{registrationType} + + + AND target_name LIKE CONCAT('%', #{name}, '%') + + ORDER BY registration_time DESC diff --git a/web/src/api/stgl/my/types.ts b/web/src/api/stgl/my/types.ts index 50c92546..056375f0 100644 --- a/web/src/api/stgl/my/types.ts +++ b/web/src/api/stgl/my/types.ts @@ -211,8 +211,8 @@ export interface MyRegistrationQuery { pageNum?: number; /** 每页显示记录数 */ pageSize?: number; + /** 名称 */ + name?: string; /** 报名类型 */ registrationType?: string; - /** 审核状态 */ - approvalStatus?: string; } diff --git a/web/src/views/stgl/activity/components/ActivityDetailView.vue b/web/src/views/stgl/activity/components/ActivityDetailView.vue index 22a247c9..ad70a5eb 100644 --- a/web/src/views/stgl/activity/components/ActivityDetailView.vue +++ b/web/src/views/stgl/activity/components/ActivityDetailView.vue @@ -3,186 +3,69 @@ - - -
- -
{{ activity.activityName || '暂无' }}
-
-
- -
- -
- -
-
-
-
+ + 基本信息 + + {{ activity.activityName || '暂无' }} + + + + {{ activity.activityTheme || '暂无' }} + {{ activity.location || '暂无' }} + {{ activity.startTime || '暂无' }} + {{ activity.endTime || '暂无' }} + + + + {{ activity.relatedClubName || '暂无' }} + - - -
- -
{{ activity.activityTheme || '暂无' }}
-
-
- -
- -
{{ activity.location || '暂无' }}
-
-
-
- - - -
- -
{{ activity.startTime || '暂无' }}
-
-
- -
- -
{{ activity.endTime || '暂无' }}
-
-
-
- - - -
- -
- -
-
-
- -
- -
{{ activity.relatedClubName || '暂无' }}
-
-
-
- -
- -
-
- -
-
- 暂无海报 -
-
+ + 活动海报 +
+ +
+
+ 暂无海报
-
- -
-
- {{ activity.description || '暂无简介' }} -
-
+ + 活动简介 +
+ {{ activity.description || '暂无简介' }}
- -
-

主办方信息

-
- - -
- -
{{ activity.organizerName || '暂无' }}
-
-
-
- - -
- -
{{ activity.organizerContact || '暂无' }}
-
-
- -
- -
{{ activity.organizerPhone || '暂无' }}
-
-
-
+ + 主办方信息 + + {{ activity.organizerName || '暂无' }} + {{ activity.organizerContact || '暂无' }} + {{ activity.organizerPhone || '暂无' }} + - -
-

承办方信息

-
+ + 承办方信息
-
- - -
- -
{{ organizer.name || '暂无' }}
-
-
- -
- -
{{ organizer.type || '暂无' }}
-
-
- -
- -
{{ organizer.contact || '暂无' }}
-
-
- -
- -
{{ organizer.phone || '暂无' }}
-
-
-
+
+ + {{ organizer.name || '暂无' }} + {{ organizer.contact || '暂无' }} + {{ organizer.phone || '暂无' }} +
暂无承办方信息
- -
-

指导老师信息

-
+ + 指导老师信息
-
- - -
- -
{{ instructor.name || '暂无' }}
-
-
- -
- -
{{ instructor.dept || '暂无' }}
-
-
- -
- -
{{ instructor.phone || '暂无' }}
-
-
- -
- -
{{ instructor.email || '暂无' }}
-
-
-
+
+ + {{ instructor.name || '暂无' }} + {{ instructor.dept || '暂无' }} + {{ instructor.phone || '暂无' }} + {{ instructor.email || '暂无' }} +
暂无指导老师信息
@@ -190,100 +73,46 @@ - - -
- -
- -
-
-
- -
- -
- - 需要审核 - 无需审核 - {{ activity.needApproval }} -
-
-
-
+ + 报名方式和审核设置 + + + + + + + 需要审核 + 无需审核 + {{ activity.needApproval }} + + + + + + + + - - -
- -
- -
-
-
-
+ + 报名时间 + + {{ activity.registrationStart || '暂无' }} + {{ activity.registrationEnd || '暂无' }} + - - -
- -
{{ activity.registrationStart || '暂无' }}
-
-
- -
- -
{{ activity.registrationEnd || '暂无' }}
-
-
-
+ + 人数限制 + + {{ activity.maxParticipants }} + {{ activity.maxTeams }} + {{ activity.teamSizeMin }} + {{ activity.teamSizeMax }} + - - -
- -
{{ activity.maxParticipants }}
-
-
-
- - - -
- -
{{ activity.maxTeams }}
-
-
- -
- -
{{ activity.teamSizeMin }}
-
-
- -
- -
{{ activity.teamSizeMax }}
-
-
-
- - - -
- -
- -
-
-
- -
- -
{{ activity.registrationCount || 0 }}
-
-
-
+ +
+ 暂无人数限制设置 +
@@ -506,36 +335,149 @@ onMounted(() => { padding: 16px; } -/* 详情项样式 */ -.detail-item { +/* el-descriptions组件相关样式 */ +.basic-info-section, +.organizer-info-section, +.registration-info-section, +.registration-time-section, +.registration-limit-section { margin-bottom: 20px; } -.detail-label { - display: inline-block; - width: 120px; - color: #606266; - font-weight: 500; - font-size: 14px; - margin-bottom: 8px; - text-align: right; - padding-right: 12px; +/* 自定义宽度的报名信息表格 */ +.custom-width-registration :deep(.el-descriptions__table) { + table-layout: fixed !important; + width: 100% !important; +} + +.custom-width-registration :deep(.el-descriptions__cell) { vertical-align: top; } -.detail-value { - display: inline-block; - vertical-align: top; - width: calc(100% - 120px); - color: #303133; - font-size: 14px; - line-height: 1.5; +/* 强制设置标签和内容的宽度 */ +.custom-width-registration :deep(.el-descriptions__label) { + width: 20% !important; + min-width: 20% !important; } +.custom-width-registration :deep(.el-descriptions__content) { + width: 30% !important; + min-width: 30% !important; +} + +/* 自定义宽度的主办方信息表格 */ +.custom-width-organizer :deep(.el-descriptions__table) { + table-layout: fixed !important; + width: 100% !important; +} + +.custom-width-organizer :deep(.el-descriptions__cell) { + vertical-align: top; +} + +/* 强制设置主办方信息标签和内容的宽度 */ +.custom-width-organizer :deep(.el-descriptions__label) { + box-sizing: border-box !important; + word-wrap: break-word !important; +} + +.custom-width-organizer :deep(.el-descriptions__content) { + box-sizing: border-box !important; + word-wrap: break-word !important; +} + +/* 主办方信息区域 - 具体宽度控制 */ +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(1)) { + width: 13% !important; +} +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(2)) { + width: 35% !important; +} +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(3)) { + width: 10% !important; +} +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(4)) { + width: 15% !important; +} +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(5)) { + width: 10% !important; +} +.custom-width-organizer :deep(.el-descriptions__table tr td:nth-child(6)) { + width: 17% !important; +} + +/* 承办方信息区域 - 具体宽度控制 */ +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(1)) { + width: 12% !important; +} +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(2)) { + width: 36% !important; +} +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(3)) { + width: 10% !important; +} +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(4)) { + width: 16% !important; +} +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(5)) { + width: 10% !important; +} +.custom-width-co-organizer :deep(.el-descriptions__table tr td:nth-child(6)) { + width: 16% !important; +} + +/* 自定义宽度的指导老师信息表格 */ +.custom-width-instructor :deep(.el-descriptions__table) { + table-layout: fixed !important; + width: 100% !important; +} + +.custom-width-instructor :deep(.el-descriptions__cell) { + vertical-align: top; +} + +/* 强制设置指导老师信息标签和内容的宽度 */ +.custom-width-instructor :deep(.el-descriptions__label) { + box-sizing: border-box !important; + word-wrap: break-word !important; +} + +.custom-width-instructor :deep(.el-descriptions__content) { + box-sizing: border-box !important; + word-wrap: break-word !important; +} + +/* 指导老师信息区域 - 具体宽度控制 (8+15+8+17+8+15+8+17) */ +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(1)) { + width: 8% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(2)) { + width: 15% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(3)) { + width: 8% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(4)) { + width: 17% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(5)) { + width: 8% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(6)) { + width: 15% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(7)) { + width: 8% !important; +} +.custom-width-instructor :deep(.el-descriptions__table tr td:nth-child(8)) { + width: 17% !important; +} + +/* 海报显示区域样式 */ .view-poster { display: flex; align-items: center; - justify-content: flex-start; + justify-content: center; padding: 20px 0; } @@ -548,6 +490,7 @@ onMounted(() => { border: 1px dashed #dcdfe6; border-radius: 4px; background-color: #fafafa; + margin: 20px auto; } .no-poster-text { @@ -556,6 +499,7 @@ onMounted(() => { text-align: center; } +/* 描述文本区域样式 */ .view-description { padding: 12px; border: 1px solid #e4e7ed; @@ -567,26 +511,12 @@ onMounted(() => { white-space: pre-wrap; word-break: break-word; background-color: #fafafa; + margin-bottom: 20px; } -/* 章节分隔样式 */ -.section-divider { - margin: 24px 0 16px 0; - padding-top: 16px; - border-top: 1px solid #e4e7ed; -} - -.section-title { - margin: 0 0 16px 0; - color: #303133; - font-size: 16px; - font-weight: 600; - border-bottom: 2px solid #409eff; - padding-bottom: 8px; -} - -/* 承办方项目样式 */ -.co-organizer-item { +/* 承办方和指导老师项目样式 */ +.co-organizer-item, +.instructor-item { margin-bottom: 16px; padding: 16px; background: #f8f9fa; @@ -594,24 +524,14 @@ onMounted(() => { border: 1px solid #e9ecef; } -.co-organizer-item:last-child { - margin-bottom: 0; -} - -/* 指导老师项目样式 */ -.instructor-item { - margin-bottom: 16px; - padding: 16px; - background: #fafafa; - border-radius: 6px; - border: 1px solid #ebeef5; -} - +.co-organizer-item:last-child, .instructor-item:last-child { margin-bottom: 0; } -.no-data { +/* 无数据提示样式 */ +.no-data, +.no-limit-data { color: #909399; font-size: 13px; font-style: italic; @@ -620,10 +540,9 @@ onMounted(() => { background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef; + margin-bottom: 20px; } - - /* 附件显示区域样式 */ .attachment-list { display: flex; @@ -647,4 +566,78 @@ onMounted(() => { .attachment-icon { font-size: 14px; } + +/* el-divider样式优化 */ +:deep(.el-divider--horizontal) { + margin: 24px 0 16px 0; +} + +:deep(.el-divider__text) { + background-color: #fff; + color: #303133; + font-size: 16px; + font-weight: 600; + padding: 0 20px; +} + +/* el-descriptions样式优化 */ +:deep(.el-descriptions) { + .el-descriptions__header { + margin-bottom: 20px; + } + + .el-descriptions__title { + font-size: 16px; + font-weight: 600; + color: #303133; + } + + .el-descriptions__body { + .el-descriptions__table { + .el-descriptions__cell { + padding: 12px 16px; + } + + .el-descriptions__label { + font-weight: 500; + color: #606266; + background-color: #fafafa; + } + + .el-descriptions__content { + color: #303133; + } + } + } +} + +/* 标签样式优化 */ +:deep(.el-tag) { + border-radius: 4px; + font-size: 12px; + height: 24px; + line-height: 22px; +} + +/* 响应式布局优化 */ +@media (max-width: 768px) { + .activity-detail-view { + padding: 12px; + } + + :deep(.el-descriptions) { + .el-descriptions__body { + .el-descriptions__table { + .el-descriptions__cell { + padding: 8px 12px; + } + } + } + } + + .co-organizer-item, + .instructor-item { + padding: 12px; + } +} diff --git a/web/src/views/stgl/club/info/components/JoinedMembersTab.vue b/web/src/views/stgl/club/info/components/JoinedMembersTab.vue index 5931e03c..1cd6b896 100644 --- a/web/src/views/stgl/club/info/components/JoinedMembersTab.vue +++ b/web/src/views/stgl/club/info/components/JoinedMembersTab.vue @@ -479,10 +479,11 @@ const getList = async () => { console.log('JoinedMembersTab: getList 开始查询,参数:', queryData); - // 如果状态字段为空或未选择,则查询已加入(status=2)和已退出(status=3)的成员 + // 已加入页面:查询已加入(status=2)和已退出(status=4)的成员 + // 字典配置:0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出 if (!queryData.status) { // 使用params传递多状态查询 - queryData.params = { ...queryData.params, statusList: ['2', '3'] } as any; + queryData.params = { ...queryData.params, statusList: ['2', '4'] } as any; delete queryData.status; // 删除单个status字段 } diff --git a/web/src/views/stgl/club/info/components/PendingMembersTab.vue b/web/src/views/stgl/club/info/components/PendingMembersTab.vue index 55503a6b..348cd06f 100644 --- a/web/src/views/stgl/club/info/components/PendingMembersTab.vue +++ b/web/src/views/stgl/club/info/components/PendingMembersTab.vue @@ -283,10 +283,11 @@ const getList = async () => { console.log('PendingMembersTab: getList 开始查询,参数:', queryData); - // 如果状态字段为空或未选择,则查询待审核(status=0)和已驳回(status=4)的成员 + // 待审核页面:查询待审核(status=0)和已驳回(status=3)的成员 + // 字典配置:0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出 if (!queryData.status) { // 使用params传递多状态查询 - queryData.params = { ...queryData.params, statusList: ['0', '4'] } as any; + queryData.params = { ...queryData.params, statusList: ['0', '3'] } as any; delete queryData.status; // 删除单个status字段 } diff --git a/web/src/views/stgl/my/club/index.vue b/web/src/views/stgl/my/club/index.vue index 640960ca..2b07e062 100644 --- a/web/src/views/stgl/my/club/index.vue +++ b/web/src/views/stgl/my/club/index.vue @@ -63,7 +63,7 @@ @@ -124,7 +124,7 @@ - + {{ detailForm.myRole || '暂无' }} {{ parseTime(detailForm.joinTime, '{y}-{m}-{d} {h}:{i}') }} @@ -166,8 +166,8 @@ import type { MyClubVO, MyClubQuery } from '@/api/stgl/my/types'; import { UserFilled } from '@element-plus/icons-vue'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; -const { stgl_club_type, sys_normal_disable, stgl_member_role, stgl_member_status } = toRefs( - proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_member_role', 'stgl_member_status') +const { stgl_club_type, sys_normal_disable, stgl_member_status } = toRefs( + proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_member_status') ); const queryRef = ref(); diff --git a/web/src/views/stgl/my/record/index.vue b/web/src/views/stgl/my/record/index.vue index e151464e..5f92e816 100644 --- a/web/src/views/stgl/my/record/index.vue +++ b/web/src/views/stgl/my/record/index.vue @@ -16,15 +16,17 @@ - - - - - - - + + + @@ -69,13 +71,7 @@ {{ parseTime(scope.row.registrationTime, '{y}-{m}-{d} {h}:{i}') }} - - - + - + + @@ -520,6 +522,8 @@ function resetQuery() { function handleTabChange(tabName: string) { activeTab.value = tabName; queryParams.value.pageNum = 1; + // 注释掉"我的申请"页签相关逻辑 + /* if (tabName === 'my') { // 切换到"我的申请"页签时,直接打开申请列表对话框 handleMyApplications(); @@ -531,6 +535,9 @@ function handleTabChange(tabName: string) { // 切换到"志愿者大厅"页签时,刷新队伍列表 getList(); } + */ + // 简化逻辑,只刷新队伍列表 + getList(); } /** 查看详情 */ From 69b73a93739af5629356c6ff33b1016378b0b847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=97=AD=E6=98=8E?= Date: Fri, 29 Aug 2025 23:02:52 +0800 Subject: [PATCH 2/8] =?UTF-8?q?=E5=BF=83=E7=90=86=E5=92=A8=E8=AF=A2?= =?UTF-8?q?=E9=A2=84=E7=BA=A6=E7=A7=BB=E5=8A=A8=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ljkConsultationAppointmentServiceImpl.java | 18 +- mobile/src/api/xljk.js | 236 ++++ mobile/src/pages.json | 26 +- mobile/src/pages/index/index.vue | 24 +- .../src/pages/xljk/my-appointments/index.vue | 737 ++++++++++++ .../xljk/reservation/appointment-form.vue | 708 ++++++++++++ mobile/src/pages/xljk/reservation/index.vue | 1006 +++++++++++++++++ 7 files changed, 2752 insertions(+), 3 deletions(-) create mode 100644 mobile/src/api/xljk.js create mode 100644 mobile/src/pages/xljk/my-appointments/index.vue create mode 100644 mobile/src/pages/xljk/reservation/appointment-form.vue create mode 100644 mobile/src/pages/xljk/reservation/index.vue diff --git a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java index 94af7357..98bb0546 100644 --- a/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xljk/src/main/java/org/dromara/xljk/service/impl/XljkConsultationAppointmentServiceImpl.java @@ -431,7 +431,23 @@ public class XljkConsultationAppointmentServiceImpl implements IXljkConsultation slot.put("availableCount", totalDetailSlots - bookedCount); // 可用数量 slot.put("isExpired", isExpired); // 是否已过期 - // TODO: 添加咨询师姓名等信息 + // 查询咨询师姓名 + if (schedule.getCounselorId() != null) { + try { + String sql = "SELECT nick_name FROM sys_user WHERE user_id = ?"; + List> result = jdbcTemplate.queryForList(sql, schedule.getCounselorId()); + if (!result.isEmpty()) { + slot.put("counselorName", result.get(0).get("nick_name")); + } else { + slot.put("counselorName", null); + } + } catch (Exception e) { + log.warn("查询咨询师姓名失败, counselorId: {}, error: {}", schedule.getCounselorId(), e.getMessage()); + slot.put("counselorName", null); + } + } else { + slot.put("counselorName", null); + } availableSlots.add(slot); } diff --git a/mobile/src/api/xljk.js b/mobile/src/api/xljk.js new file mode 100644 index 00000000..926a3c1c --- /dev/null +++ b/mobile/src/api/xljk.js @@ -0,0 +1,236 @@ +import { request } from '@/common/request'; + +/** + * 心理健康咨询预约 API + * 移动端API接口 - 基于web端功能移植 + */ + +// ===== 预约时段管理 API ===== + +/** + * 获取可预约时段列表(用于日历视图) + * @param {string} startDate 开始日期 YYYY-MM-DD + * @param {string} endDate 结束日期 YYYY-MM-DD + */ +export const getAvailableTimeSlots = (startDate, endDate) => { + return request({ + url: '/xljk/consultation/appointment/available-slots', + method: 'GET', + params: { startDate, endDate } + }); +}; + +/** + * 获取可用的详细时段列表(30分钟一个时段) + * @param {string} date 日期 YYYY-MM-DD + * @param {string} timeSlot 时段 如 "09:00-12:00" + */ +export const getAvailableDetailSlots = (date, timeSlot) => { + return request({ + url: '/xljk/consultation/appointment/available-detail-slots', + method: 'GET', + params: { date, timeSlot } + }); +}; + +// ===== 预约记录管理 API ===== + +/** + * 学生提交预约 + * @param {object} data 预约数据 + */ +export const submitAppointment = (data) => { + return request({ + url: '/xljk/consultation/appointment/submit', + method: 'POST', + data + }); +}; + +/** + * 获取我的预约记录(学生用) + * @param {string|number} studentId 学生ID + * @param {boolean} forceRefresh 是否强制刷新 + */ +export const getMyAppointments = (studentId, forceRefresh = false) => { + let url = `/xljk/consultation/appointment/my-appointments/${studentId}`; + + // 强制添加时间戳参数,确保绕过浏览器缓存 + url += `?_t=${new Date().getTime()}`; + + // 如果需要额外强制刷新 + if (forceRefresh) { + url += `&force=true`; + } + + return request({ + url, + method: 'GET', + header: { + // 禁止所有缓存 + 'Cache-Control': 'no-cache, no-store, must-revalidate', + 'Pragma': 'no-cache', + 'Expires': '0' + } + }); +}; + +/** + * 查询预约记录详细信息 + * @param {string|number} id 预约记录ID + */ +export const getAppointmentDetail = (id) => { + return request({ + url: `/xljk/consultation/appointment/${id}`, + method: 'GET' + }); +}; + +/** + * 取消预约(更新状态为已取消,不删除记录) + * @param {string|number} id 预约记录ID + */ +export const cancelAppointment = (id) => { + return request({ + url: `/xljk/consultation/appointment`, + method: 'PUT', + data: { + id: id, + status: 2 // 已取消状态 + } + }); +}; + +/** + * 更新预约信息(通用方法) + * @param {object} data 更新数据,必须包含id字段 + */ +export const updateAppointment = (data) => { + return request({ + url: `/xljk/consultation/appointment`, + method: 'PUT', + data + }); +}; + +// ===== 选项数据 API ===== + +/** + * 获取咨询师选项 + */ +export const getCounselorOptions = () => { + return request({ + url: '/xljk/consultation/schedule/options/counselors', + method: 'GET' + }); +}; + +/** + * 获取学生信息(通过学号) + * @param {string} studentNo 学号 + */ +export const getStudentByNo = (studentNo) => { + return request({ + url: `/xljk/consultation/student/${studentNo}`, + method: 'GET' + }); +}; + +/** + * 获取院系选项 + */ +export const getCollegeOptions = () => { + return request({ + url: '/xljk/consultation/appointment/options/colleges', + method: 'GET' + }); +}; + +/** + * 获取专业选项 + * @param {string} collegeName 院系名称(可选) + */ +export const getMajorOptions = (collegeName) => { + return request({ + url: '/xljk/consultation/appointment/options/majors', + method: 'GET', + params: { collegeName } + }); +}; + +/** + * 获取班级选项 + * @param {string} collegeName 院系名称(可选) + * @param {string} majorName 专业名称(可选) + */ +export const getClassOptions = (collegeName, majorName) => { + return request({ + url: '/xljk/consultation/appointment/options/classes', + method: 'GET', + params: { collegeName, majorName } + }); +}; + +// ===== 移动端专用辅助方法 ===== + +/** + * 格式化日期为 YYYY-MM-DD + * @param {Date} date 日期对象 + */ +export const formatDate = (date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +}; + +/** + * 获取指定日期的周日期范围 + * @param {Date} date 指定日期 + * @returns {object} { startDate, endDate, weekDates } + */ +export const getWeekDateRange = (date = new Date()) => { + const currentDate = new Date(date); + const currentDay = currentDate.getDay(); // 0=Sunday, 1=Monday, ... + + // 计算本周一的日期 + const monday = new Date(currentDate); + monday.setDate(currentDate.getDate() - currentDay + (currentDay === 0 ? -6 : 1)); + + // 生成一周的日期数组 + const weekDates = []; + for (let i = 0; i < 7; i++) { + const weekDate = new Date(monday); + weekDate.setDate(monday.getDate() + i); + weekDates.push({ + date: formatDate(weekDate), + dayName: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][i], + dayNumber: weekDate.getDate(), + isToday: formatDate(weekDate) === formatDate(new Date()) + }); + } + + return { + startDate: formatDate(monday), + endDate: formatDate(new Date(monday.getTime() + 6 * 24 * 60 * 60 * 1000)), + weekDates + }; +}; + +/** + * 检查预约限制 + * @param {string|number} studentId 学生ID + */ +export const checkAppointmentLimitation = async (studentId) => { + try { + // 这里可能需要导入noshow相关的API,暂时使用简单的实现 + return request({ + url: `/xljk/consultation/appointment/check-limitation/${studentId}`, + method: 'GET' + }); + } catch (error) { + console.error('检查预约限制失败', error); + // 检查失败不阻止预约 + return { code: 200, data: { limited: false } }; + } +}; \ No newline at end of file diff --git a/mobile/src/pages.json b/mobile/src/pages.json index c3960042..8db5e1ac 100644 --- a/mobile/src/pages.json +++ b/mobile/src/pages.json @@ -302,7 +302,31 @@ "navigationBarBackgroundColor": "#0051d9", "navigationBarTextStyle": "white" } - } + }, + { + "path": "pages/xljk/reservation/index", + "style": { + "navigationBarTitleText": "心理咨询预约", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xljk/my-appointments/index", + "style": { + "navigationBarTitleText": "我的预约", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, + { + "path": "pages/xljk/reservation/appointment-form", + "style": { + "navigationBarTitleText": "预约咨询", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + } ], "globalStyle": { "navigationBarTextStyle": "black", diff --git a/mobile/src/pages/index/index.vue b/mobile/src/pages/index/index.vue index 202e2d01..71c588cd 100644 --- a/mobile/src/pages/index/index.vue +++ b/mobile/src/pages/index/index.vue @@ -262,6 +262,20 @@ /> + + + + @@ -710,7 +724,8 @@ export default { this.hasPermi(['student:dormhygiene:listapp']) || this.hasPermi(['student:workstudy:list']) || this.hasPermi(['student:violation:listapp']) || - this.hasPermi(['student:assessment:applyapp']) + this.hasPermi(['student:assessment:applyapp']) || + this.hasPermi(['student:counsel:reservation']) ); }, @@ -966,6 +981,13 @@ export default { }); }, + // 跳转到心理咨询预约页面 + gotoPsychologicalReservation() { + uni.navigateTo({ + url: '/pages/xljk/reservation/index', + }); + }, + // 跳转到修改密码页面 gotoResetPassword() { uni.navigateTo({ diff --git a/mobile/src/pages/xljk/my-appointments/index.vue b/mobile/src/pages/xljk/my-appointments/index.vue new file mode 100644 index 00000000..efb6c804 --- /dev/null +++ b/mobile/src/pages/xljk/my-appointments/index.vue @@ -0,0 +1,737 @@ + + + + + \ No newline at end of file diff --git a/mobile/src/pages/xljk/reservation/appointment-form.vue b/mobile/src/pages/xljk/reservation/appointment-form.vue new file mode 100644 index 00000000..2667b2af --- /dev/null +++ b/mobile/src/pages/xljk/reservation/appointment-form.vue @@ -0,0 +1,708 @@ + + + + + \ No newline at end of file diff --git a/mobile/src/pages/xljk/reservation/index.vue b/mobile/src/pages/xljk/reservation/index.vue new file mode 100644 index 00000000..5310ec70 --- /dev/null +++ b/mobile/src/pages/xljk/reservation/index.vue @@ -0,0 +1,1006 @@ + + + + + From 3134870b68df63424e760517aab75b5462a03fed Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 30 Aug 2025 19:20:45 +0800 Subject: [PATCH 3/8] =?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 --- .cursorignore | 2 ++ .gitignore | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.cursorignore b/.cursorignore index cc9e232e..08adbaec 100644 --- a/.cursorignore +++ b/.cursorignore @@ -1,2 +1,4 @@ # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) +scripts/ +test/ diff --git a/.gitignore b/.gitignore index 7fbc1b8a..fd624ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -325,4 +325,5 @@ docs/变更管理/ .playwright-mcp/ 查找界面中涉及到的权限字符.md docs/部署文档/nginx配置/hzdyjsweb.camasort.com.conf -首页完善的提示词.md \ No newline at end of file +首页完善的提示词.md +scripts/ \ No newline at end of file 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 4/8] =?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; From 2c51e0ec3b885251ed6eb1fb95ecdfb17e6a50f1 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Mon, 1 Sep 2025 14:33:00 +0800 Subject: [PATCH 5/8] =?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 --- .../service/impl/XgStudentMsgServiceImpl.java | 59 ++++++--- .../xg/xsjcxx/studentMsg/studentMsg/index.vue | 112 ++++++++++++++++-- 2 files changed, 147 insertions(+), 24 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java index 14be9ddd..654d757e 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java @@ -1136,29 +1136,60 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService { return; } - // 1. 批量更新学生信息表的is_boarder字段为0,同时清空宿舍楼栋和房间号 + // 1. 查询学生信息,检查哪些学生已经分配了宿舍 + List students = baseMapper.selectList( + Wrappers.lambdaQuery(XgStudentMsg.class) + .in(XgStudentMsg::getId, studentIds) + .eq(XgStudentMsg::getDeleted, 0)); + + // 2. 过滤出可以取消床位申请的学生(宿舍楼栋、房间号、床位号都为空的学生) + List allowedStudentIds = new ArrayList<>(); + List skippedStudentIds = new ArrayList<>(); + + for (XgStudentMsg student : students) { + // 检查宿舍楼栋、房间号、床位号是否都不为空 + boolean hasAssignedBed = StringUtils.isNotBlank(student.getDormitoryBuilding()) || + StringUtils.isNotBlank(student.getDormitoryRoom()) || + StringUtils.isNotBlank(student.getExtField1()); // 床位号字段 + + if (hasAssignedBed) { + skippedStudentIds.add(student.getId()); + log.warn("学生 {} (ID: {}) 已分配宿舍,跳过取消床位申请操作", student.getName(), student.getId()); + } else { + allowedStudentIds.add(student.getId()); + } + } + + // 3. 如果没有学生可以取消床位申请,直接返回 + if (allowedStudentIds.isEmpty()) { + log.warn("所有学生都已分配宿舍,无法执行取消床位申请操作"); + return; + } + + // 4. 批量更新允许取消床位申请的学生信息 LambdaQueryWrapper updateWrapper = Wrappers.lambdaQuery(); - updateWrapper.in(XgStudentMsg::getId, studentIds); + updateWrapper.in(XgStudentMsg::getId, allowedStudentIds); XgStudentMsg updateEntity = new XgStudentMsg(); updateEntity.setIsBoarder(0L); - updateEntity.setDormitoryBuilding(null); // 清空宿舍楼栋 - updateEntity.setDormitoryRoom(null); // 清空宿舍房间号 int updateCount = baseMapper.update(updateEntity, updateWrapper); - log.info("批量取消床位 - 更新学生住宿状态并清空宿舍信息完成,共更新 {} 条学生记录", updateCount); + log.info("批量取消床位 - 更新学生住宿状态完成,共更新 {} 条学生记录,跳过 {} 条已分配宿舍的学生记录", + updateCount, skippedStudentIds.size()); - // 2. 更新宿舍床位学生表,将状态改为1(已退宿),设置退宿时间为当前时间 - LambdaQueryWrapper bedUpdateWrapper = Wrappers.lambdaQuery(); - bedUpdateWrapper.in(XgDormBedStudent::getStudentId, studentIds); - bedUpdateWrapper.eq(XgDormBedStudent::getStatus, "0"); // 只更新状态为正常的记录 + // 5. 更新宿舍床位学生表,将状态改为1(已退宿),设置退宿时间为当前时间 + // LambdaQueryWrapper bedUpdateWrapper = + // Wrappers.lambdaQuery(); + // bedUpdateWrapper.in(XgDormBedStudent::getStudentId, studentIds); + // bedUpdateWrapper.eq(XgDormBedStudent::getStatus, "0"); // 只更新状态为正常的记录 - XgDormBedStudent bedUpdateEntity = new XgDormBedStudent(); - bedUpdateEntity.setStatus("1"); // 设置为已退宿 - bedUpdateEntity.setCheckoutTime(new Date()); // 设置退宿时间为当前时间 + // XgDormBedStudent bedUpdateEntity = new XgDormBedStudent(); + // bedUpdateEntity.setStatus("1"); // 设置为已退宿 + // bedUpdateEntity.setCheckoutTime(new Date()); // 设置退宿时间为当前时间 - int bedUpdateCount = xgDormBedStudentMapper.update(bedUpdateEntity, bedUpdateWrapper); - log.info("批量取消床位 - 更新宿舍床位状态完成,共更新 {} 条床位记录", bedUpdateCount); + // int bedUpdateCount = xgDormBedStudentMapper.update(bedUpdateEntity, + // bedUpdateWrapper); + // log.info("批量取消床位 - 更新宿舍床位状态完成,共更新 {} 条床位记录", bedUpdateCount); } /** diff --git a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue index 545330e1..7588831d 100644 --- a/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue +++ b/web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue @@ -91,7 +91,7 @@ 批量申请床位 - 批量取消床位 + 批量取消床位申请 +
@@ -183,12 +186,14 @@ import type { WorkStudyPositionVO } from '@/api/jcbt/workStudyPosition'; import { getStudentApplicationStatusList } from '@/api/jcbt/workStudyApplication'; import PositionDetail from './detail.vue'; import ApplicationForm from './form.vue'; +import ApplicationDetail from './components/ApplicationDetail.vue'; const router = useRouter(); // 组件引用 const queryFormRef = ref(); const tableRef = ref(); +const applicationDetailRef = ref(); // 响应式数据 const loading = ref(false); @@ -313,7 +318,14 @@ const handleApply = (position: WorkStudyPositionVO) => { // 如果已有申请且状态为已提交或审核中,跳转到申请管理页面 if (application && ['submitted', 'college_reviewing', 'final_reviewing'].includes(application.status)) { - router.push('/jcbt/workstudy/my-applications'); + const id = application?.applicationId || application?.id; + if (id && applicationDetailRef.value) { + applicationDetailRef.value.handleDetail(id, 'application'); + } else { + // 回退到岗位详情对话框 + selectedPositionId.value = position.id as number; + detailDialogVisible.value = true; + } return; } From e80446145f260421abdbd1c4e06f1d7d88f714d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=97=AD=E6=98=8E?= Date: Mon, 1 Sep 2025 16:30:58 +0800 Subject: [PATCH 8/8] lxm0901 --- .../dromara/stgl/domain/bo/StglClassBo.java | 2 +- .../stgl/domain/vo/StglActivityVo.java | 7 +++ .../service/impl/StglClassServiceImpl.java | 6 +-- .../mapper/stgl/StglActivityMapper.xml | 42 ++++++++++++++- .../resources/mapper/stgl/StglClassMapper.xml | 51 ++++++++++++++++--- .../controller/VolunteerMemberController.java | 1 - web/src/api/stgl/activity/types.ts | 8 ++- .../components/ActivityRatingView.vue | 26 +++++++--- .../components/RegisteredMembersTab.vue | 32 ++++++++++-- web/src/views/stgl/activity/hall/index.vue | 4 +- web/src/views/stgl/activity/index.vue | 38 +++++++++----- web/src/views/stgl/my/club/index.vue | 14 ++--- web/src/views/stgl/stclass/index.vue | 2 +- 13 files changed, 181 insertions(+), 52 deletions(-) delete mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/VolunteerMemberController.java diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClassBo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClassBo.java index 89df987d..86ea4fc1 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClassBo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/bo/StglClassBo.java @@ -30,7 +30,7 @@ public class StglClassBo extends BaseEntity { /** * 学生姓名 */ - private String name; + private String studentName; /** * 性别 diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java index dacf563c..ccb9848b 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/domain/vo/StglActivityVo.java @@ -217,11 +217,18 @@ public class StglActivityVo implements Serializable { /** * 活动状态 + * 注意:此字段返回的是根据时间动态计算后的状态 */ @ExcelProperty(value = "活动状态", converter = ExcelDictConvert.class, index = 24) @ExcelDictFormat(dictType = "stgl_activity_status") private String activityStatus; + /** + * 原始活动状态(数据库中存储的实际状态) + * 用于权限控制和业务逻辑判断 + */ + private String originalActivityStatus; + /** * 是否为已办活动 */ diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClassServiceImpl.java b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClassServiceImpl.java index 34867143..5c253529 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClassServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/java/org/dromara/stgl/service/impl/StglClassServiceImpl.java @@ -126,8 +126,8 @@ public class StglClassServiceImpl implements IStglClassService { if (StringUtils.isNotBlank(bo.getClassId())) { params.put("classId", bo.getClassId()); } - if (StringUtils.isNotBlank(bo.getName())) { - params.put("studentName", bo.getName()); + if (StringUtils.isNotBlank(bo.getStudentName())) { + params.put("studentName", bo.getStudentName()); } if (StringUtils.isNotBlank(bo.getStudentNo())) { params.put("studentNo", bo.getStudentNo()); @@ -145,7 +145,7 @@ public class StglClassServiceImpl implements IStglClassService { Map params = bo.getParams(); LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.eq(StringUtils.isNotBlank(bo.getClassId()), StglClass::getClassId, bo.getClassId()); - lqw.like(StringUtils.isNotBlank(bo.getName()), StglClass::getName, bo.getName()); + lqw.like(StringUtils.isNotBlank(bo.getStudentName()), StglClass::getName, bo.getStudentName()); lqw.like(StringUtils.isNotBlank(bo.getStudentNo()), StglClass::getStudentNo, bo.getStudentNo()); return lqw; } diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml index a4adce16..f94ee3f5 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglActivityMapper.xml @@ -34,6 +34,7 @@ + @@ -81,7 +82,26 @@ ELSE sa.registration_status END AS registration_status, sa.max_participants, sa.max_teams, sa.team_size_min, sa.team_size_max, sa.need_approval, sa.activity_range, - sa.activity_status, sa.is_finished, sa.summary, sa.summary_attachments, sa.achievements, sa.achievements_attachments, sa.import_source, + -- 动态计算活动状态:审批通过后根据时间自动更新状态 + CASE + -- 手动控制的状态保持不变(草稿、待审批、已取消、已驳回) + WHEN sa.activity_status IN ('0', '2', '6', '7') THEN sa.activity_status + + -- 审批通过后的状态根据时间自动计算 + WHEN sa.activity_status IN ('3', '4', '5') THEN + CASE + -- 已通过(未开始) + = sa.start_time AND NOW() <= sa.end_time THEN '4']]> -- 进行中 + sa.end_time THEN '5']]> -- 已结束 + ELSE sa.activity_status + END + + -- 其他情况保持原状态 + ELSE sa.activity_status + END AS activity_status, + -- 返回原始活动状态(用于权限控制) + sa.activity_status AS original_activity_status, + sa.is_finished, sa.summary, sa.summary_attachments, sa.achievements, sa.achievements_attachments, sa.import_source, sa.remark, sa.create_by, sa.create_time, sa.update_by, sa.update_time, -- 关联查询创建者姓名 su.nick_name AS create_by_name, @@ -215,6 +235,26 @@ AND sa.organizer_type = #{activity.organizerType} + + AND ( + CASE + -- 手动控制的状态保持不变(草稿、待审批、已取消、已驳回) + WHEN sa.activity_status IN ('0', '2', '6', '7') THEN sa.activity_status + + -- 审批通过后的状态根据时间自动计算 + WHEN sa.activity_status IN ('3', '4', '5') THEN + CASE + -- 已通过(未开始) + = sa.start_time AND NOW() <= sa.end_time THEN '4']]> -- 进行中 + sa.end_time THEN '5']]> -- 已结束 + ELSE sa.activity_status + END + + -- 其他情况保持原状态 + ELSE sa.activity_status + END + ) = #{activity.activityStatus} + AND ( CASE diff --git a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml index ea22f2a6..06ebe6c7 100644 --- a/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml +++ b/backend/ruoyi-modules/ruoyi-stgl/src/main/resources/mapper/stgl/StglClassMapper.xml @@ -81,13 +81,49 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" GROUP BY m.user_id ) volunteer_stats ON s.id = volunteer_stats.user_id LEFT JOIN ( - SELECT - ar.user_id, - COUNT(DISTINCT ar.activity_id) as activity_count - FROM stgl_activity_registration ar - WHERE ar.deleted = 0 - AND ar.approval_status = 1 - GROUP BY ar.user_id + -- 修改活动统计逻辑:同时统计个人报名和团队报名的活动数量 + SELECT + user_id, + COUNT(DISTINCT activity_id) as activity_count + FROM ( + -- 个人报名的活动 + SELECT + ar.user_id, + ar.activity_id + FROM stgl_activity_registration ar + WHERE ar.deleted = 0 + AND ar.approval_status = 1 + AND ar.tenant_id = '000000' + + UNION + + -- 团队报名的活动(通过团队成员信息) + SELECT + JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) as user_id, + ati.activity_id + FROM stgl_activity_team_info ati + CROSS JOIN ( + SELECT 0 as n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION + SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION + SELECT 10 UNION SELECT 11 UNION SELECT 12 UNION SELECT 13 UNION SELECT 14 UNION + SELECT 15 UNION SELECT 16 UNION SELECT 17 UNION SELECT 18 UNION SELECT 19 + ) numbers + WHERE ati.deleted = 0 + AND ati.tenant_id = '000000' + AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) IS NOT NULL + AND JSON_UNQUOTE(JSON_EXTRACT(ati.team_members, CONCAT('$[', numbers.n, '].userId'))) != '' + AND EXISTS ( + SELECT 1 FROM stgl_activity_registration team_ar + WHERE team_ar.team_id = ati.id + AND team_ar.deleted = 0 + AND team_ar.approval_status = 1 + AND team_ar.registration_type = 2 + AND team_ar.tenant_id = '000000' + ) + ) all_activities + WHERE user_id IS NOT NULL + AND user_id != '' + GROUP BY user_id ) activity_stats ON s.id = activity_stats.user_id s.deleted = 0 @@ -174,7 +210,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND m.deleted = 0 AND m.status = 2 AND vt.deleted = 0 - AND vt.status = 1 ORDER BY vt.create_time DESC 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/web/src/api/stgl/activity/types.ts b/web/src/api/stgl/activity/types.ts index aa502267..e5640174 100644 --- a/web/src/api/stgl/activity/types.ts +++ b/web/src/api/stgl/activity/types.ts @@ -145,10 +145,16 @@ export interface StglActivityVO { activityRange?: number; /** - * 活动状态 + * 活动状态(动态计算后的状态) */ activityStatus?: string; + /** + * 原始活动状态(数据库中存储的实际状态) + * 用于权限控制和业务逻辑判断 + */ + originalActivityStatus?: string; + /** * 是否为已办活动 */ diff --git a/web/src/views/stgl/activity/components/ActivityRatingView.vue b/web/src/views/stgl/activity/components/ActivityRatingView.vue index 37569e21..0f4f083d 100644 --- a/web/src/views/stgl/activity/components/ActivityRatingView.vue +++ b/web/src/views/stgl/activity/components/ActivityRatingView.vue @@ -90,7 +90,7 @@