From 12592c5a2454d49a64a3411eaafc7ba31e3e555d Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Sat, 22 Nov 2025 16:01:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E6=B6=88=E6=81=AF=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.bin/mysql/sql/initMenuData.sql | 22 +- schoolNewsServ/.bin/mysql/sql/reInit.sh | 62 +++- .../sensitiveData/sensitive_word_dict.txt | 0 .../{ => sql}/sensitiveData/writeWord.py | 36 +- schoolNewsServ/achievement/docs/MIGRATION.md | 331 ------------------ .../impl/ACHAchievementServiceImpl.java | 68 +++- .../task/newsTask/NewsCrawlerTask.java | 4 +- .../menu/service/impl/SysMenuServiceImpl.java | 18 +- .../src/main/resources/mapper/MenuMapper.xml | 2 +- schoolNewsWeb/src/apis/system/menu.ts | 6 +- schoolNewsWeb/src/assets/styles/common.scss | 2 +- .../src/components/base/TopNavigation.vue | 1 - schoolNewsWeb/src/utils/route-generator.ts | 36 +- .../views/user/message/MyMessageListView.vue | 10 +- 消息中心调试步骤.md | 113 ++++++ 15 files changed, 331 insertions(+), 380 deletions(-) rename schoolNewsServ/.bin/mysql/{ => sql}/sensitiveData/sensitive_word_dict.txt (100%) rename schoolNewsServ/.bin/mysql/{ => sql}/sensitiveData/writeWord.py (81%) delete mode 100644 schoolNewsServ/achievement/docs/MIGRATION.md create mode 100644 消息中心调试步骤.md diff --git a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql index fcb6375..56cb959 100644 --- a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql +++ b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql @@ -69,8 +69,8 @@ INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, modu ('19','perm_sensitive_manage', '敏感词管理', 'sensitive:manage', '敏感词管理权限', 'module_sensitive', '1', now()); -- 插入角色-权限关联数据 -INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES -- 超级管理员:拥有所有权限 +INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES ('1', 'superadmin', 'perm_default', '1', now()), ('2', 'superadmin', 'perm_system_manage', '1', now()), ('3', 'superadmin', 'perm_system_dept_manage', '1', now()), @@ -116,8 +116,8 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat ('45', 'freedom', 'perm_message_view', '1', now()); -- 插入前端菜单数据 -INSERT INTO `tb_sys_menu` VALUES -- 用户前端菜单 (100-699) +INSERT INTO `tb_sys_menu` VALUES ('100', 'menu_home', '首页', NULL, '/home', 'user/home/HomeView', NULL, 1, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('101', 'menu_resource_hot', '热门资源', NULL, '/resource-hot', 'user/resource-center/HotResourceView', NULL, 2, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('200', 'menu_resource_center', '资源中心', NULL, '/resource-center', 'user/resource-center/ResourceCenterView', NULL, 2, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), @@ -128,13 +128,13 @@ INSERT INTO `tb_sys_menu` VALUES ('304', 'menu_course_detail', '课程详情', 'menu_study_plan', '/study-plan/course-detail', 'user/study-plan/CourseDetailView', NULL, 4, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('305', 'menu_course_study', '课程学习', 'menu_study_plan', '/study-plan/course-study', 'user/study-plan/CourseStudyView', NULL, 5, 3, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('400', 'menu_user_dropdown', '用户下拉菜单', NULL, '', '', NULL, 4, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), -('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user/user-center/UserCenterView', NULL, 4, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), +('401', 'menu_user_center', '个人中心', 'menu_user_dropdown', '/user-center', 'user/user-center/UserCenterLayout', NULL, 4, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('402', 'menu_learning_records', '学习记录', 'menu_user_center', '/user-center/learning-records', 'user/user-center/LearningRecordsView', NULL, 1, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('403', 'menu_my_favorites', '我的收藏', 'menu_user_center', '/user-center/favorites', 'user/user-center/MyFavoritesView', NULL, 2, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), ('404', 'menu_my_achievements', '我的成就', 'menu_user_center', '/user-center/achievements', 'user/user-center/MyAchievementsView', NULL, 3, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), -('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'user/profile/ProfileView', NULL, 5, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), -('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'user/profile/PersonalInfoView', NULL, 1, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), -('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'user/profile/AccountSettingsView', NULL, 2, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), +('500', 'menu_profile', '账号中心', 'menu_user_dropdown', '/profile', 'user/user-center/UserCenterLayout', NULL, 5, 1, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:49:56', NULL, 0), +('501', 'menu_personal_info', '个人信息', 'menu_profile', '/profile/personal-info', 'user/user-center/profile/PersonalInfoView', NULL, 1, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), +('502', 'menu_account_settings', '账号设置', 'menu_profile', '/profile/account-settings', 'user/user-center/profile/AccountSettingsView', NULL, 2, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), ('503', 'menu_search', '搜索', NULL, '/search', 'user/resource-center/SearchView', NULL, 3, 0, 'NavigationLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:48:39', NULL, 0), -- 管理后台菜单 (1000-8999) ('1000', 'menu_admin_overview', '系统总览', NULL, '/admin/overview', 'admin/overview/SystemOverviewView', 'admin/overview.svg', 1, 0, 'SidebarLayout', '1', NULL, '2025-10-27 17:26:06', '2025-10-29 11:52:32', NULL, 0), @@ -177,8 +177,8 @@ INSERT INTO `tb_sys_menu` VALUES ('651', 'menu_user_message_detail', '消息详情', 'menu_user_message_center', '/user/message/detail/:messageID', 'user/message/MyMessageDetailView', NULL, 1, 3, 'NavigationLayout', '1', NULL, '2025-11-13 10:00:00', '2025-11-13 10:00:00', NULL, 0); -- 插入菜单权限关联数据 -INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES -- 前端菜单权限关联 +INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES ('100', 'perm_default', 'menu_home', '1', now()), ('102', 'perm_default', 'menu_resource_hot', '1', now()), ('101', 'perm_default', 'menu_resource_center', '1', now()), @@ -241,11 +241,7 @@ INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, creat -- 消息通知管理菜单权限关联 ('240', 'perm_message_manage', 'menu_admin_message_manage', '1', now()), -('241', 'perm_message_manage', 'menu_admin_message_list', '1', now()), -('242', 'perm_message_send', 'menu_admin_message_create', '1', now()), -('243', 'perm_message_manage', 'menu_admin_message_detail', '1', now()), - -- 用户端消息中心权限关联 -('250', 'perm_message_view', 'menu_user_message_center', '1', now()), -('251', 'perm_message_view', 'menu_user_message_detail', '1', now()); +('250', 'perm_default', 'menu_user_message_center', '1', now()), +('251', 'perm_default', 'menu_user_message_detail', '1', now()); diff --git a/schoolNewsServ/.bin/mysql/sql/reInit.sh b/schoolNewsServ/.bin/mysql/sql/reInit.sh index 36474fa..84e9839 100644 --- a/schoolNewsServ/.bin/mysql/sql/reInit.sh +++ b/schoolNewsServ/.bin/mysql/sql/reInit.sh @@ -97,6 +97,51 @@ execute_init_script() { fi } +# 执行敏感词导入 +import_sensitive_words() { + print_message $BLUE "开始导入敏感词数据..." + + # 检查conda是否可用 + if ! command -v conda &> /dev/null; then + print_message $YELLOW "conda命令未找到,跳过敏感词导入" + return 0 + fi + + # 检查schoolNewsCrawler环境是否存在 + if ! conda env list | grep -q "schoolNewsCrawler"; then + print_message $YELLOW "conda环境 'schoolNewsCrawler' 不存在,跳过敏感词导入" + print_message $YELLOW "提示: 可以使用以下命令创建环境: conda create -n schoolNewsCrawler python=3.10" + return 0 + fi + + # 切换到敏感词脚本目录 + local sensitive_dir="$SCRIPT_DIR/sensitiveData" + if [ ! -d "$sensitive_dir" ]; then + print_message $YELLOW "敏感词脚本目录不存在: $sensitive_dir" + return 0 + fi + + if [ ! -f "$sensitive_dir/writeWord.py" ]; then + print_message $YELLOW "敏感词脚本不存在: $sensitive_dir/writeWord.py" + return 0 + fi + + print_message $BLUE "激活conda环境: schoolNewsCrawler" + cd "$sensitive_dir" + + # 使用conda run来在指定环境中执行命令,添加-y参数自动确认 + conda run -n schoolNewsCrawler python writeWord.py -y + + if [ $? -eq 0 ]; then + print_message $GREEN "敏感词数据导入成功" + else + print_message $YELLOW "敏感词数据导入失败,但不影响系统运行" + fi + + # 返回脚本目录 + cd "$SCRIPT_DIR" +} + # 验证初始化结果 verify_initialization() { print_message $BLUE "验证初始化结果..." @@ -120,6 +165,14 @@ verify_initialization() { else print_message $YELLOW "默认用户创建失败" fi + + # 检查敏感词数量 + local sensitive_count=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASSWORD" -D"$DB_NAME" -e "SELECT COUNT(*) FROM tb_sensitive_word;" 2>/dev/null | tail -n 1) + if [ -n "$sensitive_count" ] && [ "$sensitive_count" -gt 0 ]; then + print_message $GREEN "敏感词数据: $sensitive_count 个" + else + print_message $YELLOW "敏感词数据: 0 个 (可能未导入或表不存在)" + fi } # 显示初始化信息 @@ -146,6 +199,8 @@ show_initialization_info() { print_message $YELLOW " - AI智能体" print_message $YELLOW " - 系统配置" print_message $YELLOW " - 文件管理" + print_message $YELLOW " - 敏感词过滤" + print_message $YELLOW " - 消息通知" print_message $BLUE "=====================================================" } @@ -153,7 +208,7 @@ show_initialization_info() { main() { print_message $BLUE "=====================================================" print_message $BLUE "校园思政新闻平台数据库重新初始化脚本" - print_message $BLUE "版本: 1.0.0" + print_message $BLUE "版本: 1.1.0" print_message $BLUE "=====================================================" # 检查MySQL连接 @@ -170,12 +225,15 @@ main() { # 执行初始化脚本 execute_init_script + # 导入敏感词数据 + # 验证初始化结果 verify_initialization # 显示初始化信息 show_initialization_info - + import_sensitive_words + print_message $GREEN "初始化完成!" } diff --git a/schoolNewsServ/.bin/mysql/sensitiveData/sensitive_word_dict.txt b/schoolNewsServ/.bin/mysql/sql/sensitiveData/sensitive_word_dict.txt similarity index 100% rename from schoolNewsServ/.bin/mysql/sensitiveData/sensitive_word_dict.txt rename to schoolNewsServ/.bin/mysql/sql/sensitiveData/sensitive_word_dict.txt diff --git a/schoolNewsServ/.bin/mysql/sensitiveData/writeWord.py b/schoolNewsServ/.bin/mysql/sql/sensitiveData/writeWord.py similarity index 81% rename from schoolNewsServ/.bin/mysql/sensitiveData/writeWord.py rename to schoolNewsServ/.bin/mysql/sql/sensitiveData/writeWord.py index d9bd5b2..7ea89ea 100644 --- a/schoolNewsServ/.bin/mysql/sensitiveData/writeWord.py +++ b/schoolNewsServ/.bin/mysql/sql/sensitiveData/writeWord.py @@ -8,6 +8,7 @@ import pymysql import os import sys +import argparse from datetime import datetime # 数据库配置 @@ -110,13 +111,25 @@ def check_duplicates(connection, words): def main(): """主函数""" + # 解析命令行参数 + parser = argparse.ArgumentParser(description='敏感词批量导入工具') + parser.add_argument('-y', '--yes', action='store_true', + help='自动确认导入,跳过交互式确认') + parser.add_argument('--file', type=str, + help='指定敏感词文件路径(默认: sensitive_word_dict.txt)') + + args = parser.parse_args() + print("=" * 50) print("敏感词批量导入工具") print("=" * 50) - # 获取脚本所在目录 - script_dir = os.path.dirname(os.path.abspath(__file__)) - dict_file = os.path.join(script_dir, 'sensitive_word_dict.txt') + # 获取敏感词文件路径 + if args.file: + dict_file = args.file + else: + script_dir = os.path.dirname(os.path.abspath(__file__)) + dict_file = os.path.join(script_dir, 'sensitive_word_dict.txt') # 检查敏感词文件是否存在 if not os.path.exists(dict_file): @@ -150,11 +163,20 @@ def main(): # 确认导入 print(f"准备导入 {len(words)} 个敏感词到数据库") - confirm = input("是否继续?(y/N): ").strip().lower() - if confirm != 'y': - print("用户取消导入") - return + if args.yes: + print("自动确认模式,开始导入...") + else: + try: + confirm = input("是否继续?(y/N): ").strip().lower() + if confirm != 'y': + print("用户取消导入") + return + except EOFError: + print("检测到非交互式环境,自动确认导入...") + except KeyboardInterrupt: + print("\n用户中断导入") + return # 批量插入 print("开始批量导入敏感词...") diff --git a/schoolNewsServ/achievement/docs/MIGRATION.md b/schoolNewsServ/achievement/docs/MIGRATION.md deleted file mode 100644 index ddb811e..0000000 --- a/schoolNewsServ/achievement/docs/MIGRATION.md +++ /dev/null @@ -1,331 +0,0 @@ -# 成就模块迁移指南 - -## 📝 迁移概述 - -成就相关功能已从 `usercenter` 模块迁移到独立的 `achievement` 模块。 - -### 迁移日期 -2025-10-24 - -### 迁移原因 -- ✅ **模块职责分离** - usercenter负责用户中心,achievement专注于成就系统 -- ✅ **更好的可维护性** - 独立模块易于管理和扩展 -- ✅ **清晰的依赖关系** - 减少模块间的耦合 -- ✅ **可扩展性** - 便于添加新的成就类型和功能 - -## 🔄 包路径变更 - -### API接口变更 - -**旧路径(已删除):** -```java -org.xyzh.api.usercenter.achievement.UserAchievementService -``` - -**新路径:** -```java -org.xyzh.api.achievement.AchievementService -``` - -### 服务实现变更 - -**旧路径(已删除):** -```java -org.xyzh.usercenter.service.UCUserAchievementService -org.xyzh.usercenter.service.impl.UCUserAchievementServiceImpl -``` - -**新路径:** -```java -org.xyzh.api.achievement.AchievementService -org.xyzh.achievement.service.impl.ACHAchievementServiceImpl -``` - -### Mapper变更 - -**旧路径(已删除):** -```java -org.xyzh.usercenter.mapper.AchievementMapper -org.xyzh.usercenter.mapper.UserAchievementMapper -``` - -**新路径:** -```java -org.xyzh.achievement.mapper.AchievementMapper -org.xyzh.achievement.mapper.UserAchievementMapper -org.xyzh.achievement.mapper.UserAchievementProgressMapper // 新增 -``` - -### Controller变更 - -**旧路径(已删除):** -```java -org.xyzh.usercenter.controller.UserAchievementController -``` - -**新路径:** -```java -org.xyzh.achievement.controller.AchievementController -``` - -## 📦 依赖变更 - -### 如果你的模块之前依赖了成就功能 - -#### 旧的依赖配置(需要删除): -```xml - - org.xyzh - api-usercenter - 1.0.0 - -``` - -#### 新的依赖配置(需要添加): -```xml - - - org.xyzh - api-achievement - 1.0.0 - - - - - org.xyzh - achievement - 1.0.0 - -``` - -## 🔧 代码迁移步骤 - -### 1. 更新导入语句 - -**旧代码:** -```java -import org.xyzh.api.usercenter.achievement.UserAchievementService; -``` - -**新代码:** -```java -import org.xyzh.api.achievement.AchievementService; -``` - -### 2. 更新服务注入 - -**旧代码:** -```java -@Autowired -private UserAchievementService userAchievementService; -``` - -**新代码:** -```java -@Autowired -private AchievementService achievementService; -``` - -### 3. 更新方法调用 - -大部分方法签名保持不变,只需更新服务名称: - -**旧代码:** -```java -ResultDomain result = userAchievementService.getUserAchievements(userID, type); -``` - -**新代码:** -```java -ResultDomain result = achievementService.getUserAchievements(userID, type); -``` - -### 4. 新增功能调用 - -新的成就系统增加了事件驱动机制: - -```java -// 发布成就事件 -AchievementEvent event = AchievementEvent.builder(userID, AchievementEventType.COURSE_COMPLETED) - .value(1) - .build(); - -// 方式1:使用Spring事件发布(推荐) -eventPublisher.publishEvent(event); - -// 方式2:直接调用服务 -achievementService.processAchievementEvent(event); -``` - -## 🗄️ 数据库变更 - -### 新增表 - -```sql --- 用户成就进度表(新增) -CREATE TABLE tb_user_achievement_progress ( - id VARCHAR(32) NOT NULL, - user_id VARCHAR(32) NOT NULL, - achievement_id VARCHAR(32) NOT NULL, - current_value INT DEFAULT 0, - target_value INT DEFAULT 0, - progress_percentage INT DEFAULT 0, - completed TINYINT(1) DEFAULT 0, - last_update_time DATETIME, - create_time DATETIME, - PRIMARY KEY (id), - UNIQUE KEY uk_user_achievement_progress (user_id, achievement_id) -); -``` - -### 已有表 - -`tb_achievement` 和 `tb_user_achievement` 表结构保持不变,无需迁移数据。 - -## 🌐 REST API变更 - -### 接口路径变更 - -**旧路径:** -``` -/usercenter/achievement/* -``` - -**新路径:** -``` -/achievement/* -``` - -### 具体接口映射 - -| 功能 | 旧接口 | 新接口 | 变化 | -|-----|-------|-------|-----| -| 获取所有成就 | GET /usercenter/achievement/list | GET /achievement/list | 路径变更 | -| 获取用户成就 | GET /usercenter/achievement/user/{userID} | GET /achievement/user/{userID} | 路径变更 | -| 获取我的成就 | - | GET /achievement/my | **新增** | -| 授予成就 | POST /usercenter/achievement/grant | POST /achievement/grant | 路径变更 | -| 检查条件 | GET /usercenter/achievement/condition/{userID}/{achievementID} | GET /achievement/condition/check/{userID}/{achievementID} | 路径变更 | -| 获取进度 | - | GET /achievement/progress/{userID} | **新增** | -| 处理事件 | - | POST /achievement/event/process | **新增** | -| 获取统计 | - | GET /achievement/statistics/{userID} | **新增** | -| 排行榜 | - | GET /achievement/ranking | **新增** | - -## ✨ 新增功能 - -### 1. 事件驱动机制 -```java -// 自动监听业务事件并触发成就检测 -@Autowired -private ApplicationEventPublisher eventPublisher; - -AchievementEvent event = AchievementEvent.builder(userID, eventType) - .value(value) - .build(); -eventPublisher.publishEvent(event); -``` - -### 2. 成就进度追踪 -```java -// 查询用户的成就进度 -ResultDomain result = - achievementService.getMyAchievementProgress(achievementID); -``` - -### 3. 策略模式检测器 -- 可扩展的成就检测器 -- 支持多种成就类型 -- 易于添加新的成就类型 - -### 4. 统计和排行榜 -```java -// 获取用户统计 -ResultDomain> stats = - achievementService.getUserAchievementStatistics(userID); - -// 获取排行榜 -ResultDomain> ranking = - achievementService.getAchievementRanking(10); -``` - -## 🚨 注意事项 - -### 1. 破坏性变更 - -#### ❌ 已删除的类(不再可用) -- `org.xyzh.api.usercenter.achievement.UserAchievementService` -- `org.xyzh.usercenter.service.UCUserAchievementService` -- `org.xyzh.usercenter.mapper.AchievementMapper` -- `org.xyzh.usercenter.controller.UserAchievementController` - -#### ✅ 对应的新类 -- `org.xyzh.api.achievement.AchievementService` -- `org.xyzh.achievement.service.impl.ACHAchievementServiceImpl` -- `org.xyzh.achievement.mapper.AchievementMapper` -- `org.xyzh.achievement.controller.AchievementController` - -### 2. 接口变化 - -新的 `AchievementService` 接口方法更多、更完善: -- ✅ 所有旧方法都有对应的新方法 -- ✅ 新增了事件处理、进度查询、统计等功能 -- ✅ 方法签名大部分保持兼容 - -### 3. 前端调用 - -如果你的前端代码调用了成就相关API,需要: -1. 更新API路径:`/usercenter/achievement/*` → `/achievement/*` -2. 检查返回数据格式(大部分保持兼容) -3. 利用新增的API(进度、统计、排行榜等) - -## 📋 迁移检查清单 - -迁移时请检查以下项目: - -- [ ] 更新 pom.xml 依赖 -- [ ] 更新 import 语句 -- [ ] 更新服务注入 -- [ ] 更新方法调用 -- [ ] 测试成就创建功能 -- [ ] 测试成就授予功能 -- [ ] 测试成就查询功能 -- [ ] 测试事件触发功能(如有) -- [ ] 更新前端API路径(如有) -- [ ] 更新相关文档 -- [ ] 执行集成测试 - -## 🆘 常见问题 - -### Q1: 编译报错找不到 UserAchievementService -**A:** 更新 import 语句为: -```java -import org.xyzh.api.achievement.AchievementService; -``` - -### Q2: 旧的成就数据会丢失吗? -**A:** 不会。数据库表结构保持不变,所有数据完整保留。 - -### Q3: 需要修改数据库吗? -**A:** 需要执行新表创建SQL(`tb_user_achievement_progress`),见 `.bin/mysql/sql/createTableAchievement.sql`。 - -### Q4: 如何使用新的事件驱动功能? -**A:** 参考 `README.md` 中的"使用方法"章节。 - -### Q5: admin模块已经自动配置好了吗? -**A:** 是的,admin模块的pom.xml已自动添加achievement依赖。 - -## 📚 相关文档 - -- [成就系统使用文档](../README.md) -- [数据库建表SQL](../../.bin/mysql/sql/createTableAchievement.sql) - -## 👥 联系支持 - -如果在迁移过程中遇到问题,请联系: -- 开发者:yslg -- 创建Issue或PR - ---- - -**迁移完成日期**: 2025-10-24 -**版本**: 1.0.0 - diff --git a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java index d898dec..1ca2642 100644 --- a/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java +++ b/schoolNewsServ/achievement/src/main/java/org/xyzh/achievement/service/impl/ACHAchievementServiceImpl.java @@ -18,6 +18,7 @@ import org.xyzh.common.core.event.AchievementEvent; import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageParam; import org.xyzh.common.dto.user.TbSysUser; +import org.xyzh.common.dto.user.TbSysUserInfo; import org.xyzh.common.dto.usercenter.TbAchievement; import org.xyzh.common.dto.usercenter.TbUserAchievement; import org.xyzh.common.dto.usercenter.TbUserAchievementProgress; @@ -26,6 +27,7 @@ import org.xyzh.common.utils.IDUtils; import org.xyzh.common.vo.AchievementVO; import org.xyzh.system.utils.LoginUtil; import org.xyzh.api.system.permission.ResourcePermissionService; +import org.xyzh.system.mapper.UserInfoMapper; import org.xyzh.common.vo.UserDeptRoleVO; import org.xyzh.common.core.enums.ResourceType; @@ -62,6 +64,9 @@ public class ACHAchievementServiceImpl implements AchievementService { @Autowired private RedisService redisService; + + @Autowired + private UserInfoMapper userInfoMapper; // ==================== 成就定义管理 ==================== @@ -551,7 +556,7 @@ public class ACHAchievementServiceImpl implements AchievementService { // 检查是否达成 if (checker.check(event, achievement, currentProgress)) { - TbUserAchievement userAchievement = grantAchievementInternal(event.getUserID(), achievement.getAchievementID()); + TbUserAchievement userAchievement = grantAchievementInternal(event.getUserID(), achievement); if (userAchievement != null) { newAchievements.add(userAchievement); logger.info("用户 {} 通过事件 {} 获得成就: {}", event.getUserID(), event.getEventType(), achievement.getName()); @@ -748,17 +753,21 @@ public class ACHAchievementServiceImpl implements AchievementService { /** * 内部授予成就方法(不检查是否已获得) */ - private TbUserAchievement grantAchievementInternal(String userID, String achievementID) { + private TbUserAchievement grantAchievementInternal(String userID, TbAchievement achievement) { try { TbUserAchievement userAchievement = new TbUserAchievement(); userAchievement.setID(IDUtils.generateID()); userAchievement.setUserID(userID); - userAchievement.setAchievementID(achievementID); + userAchievement.setAchievementID(achievement.getAchievementID()); userAchievement.setObtainTime(new Date()); int result = userAchievementMapper.insertUserAchievement(userAchievement); if (result > 0) { - updateProgressToCompleted(userID, achievementID); + updateProgressToCompleted(userID, achievement.getAchievementID()); + + // 检查是否需要更新用户等级(learning_time开头的成就) + updateUserLevelIfNeeded(userID, achievement); + return userAchievement; } return null; @@ -824,5 +833,56 @@ public class ACHAchievementServiceImpl implements AchievementService { } return null; } + + /** + * 检查并更新用户等级(仅针对learning_time开头的成就) + * @param userID 用户ID + * @param achievement 成就对象 + */ + private void updateUserLevelIfNeeded(String userID, TbAchievement achievement) { + try { + // 检查成就ID是否以learning_time开头 + if (achievement == null || achievement.getAchievementID() == null || + !achievement.getAchievementID().startsWith("learning_time")) { + return; + } + + // 检查成就是否有等级信息 + if (achievement.getLevel() == null) { + logger.warn("成就没有等级信息: {}", achievement.getAchievementID()); + return; + } + + // 获取用户当前信息 + TbSysUserInfo userInfo = userInfoMapper.selectById(userID); + if (userInfo == null) { + logger.warn("用户信息不存在: {}", userID); + return; + } + + // 检查是否需要更新等级(只有当成就等级高于当前用户等级时才更新) + Integer currentLevel = userInfo.getLevel() != null ? userInfo.getLevel() : 0; + Integer achievementLevel = achievement.getLevel(); + + if (achievementLevel > currentLevel) { + userInfo.setLevel(achievementLevel); + int updateResult = userInfoMapper.updateUserInfo(userInfo); + + if (updateResult > 0) { + logger.info("用户 {} 通过成就 {} 等级从 {} 提升到 {}", + userID, achievement.getAchievementID(), currentLevel, achievementLevel); + } else { + logger.error("更新用户等级失败: userID={}, achievementID={}", userID, achievement.getAchievementID()); + } + } else { + logger.debug("用户 {} 当前等级 {} 已达到或超过成就等级 {},无需更新", + userID, currentLevel, achievementLevel); + } + + } catch (Exception e) { + logger.error("更新用户等级时发生异常: userID={}, achievementID={}, error={}", + userID, achievement.getAchievementID(), e.getMessage(), e); + } + } } diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/newsTask/NewsCrawlerTask.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/newsTask/NewsCrawlerTask.java index 1f98625..3b0a63a 100644 --- a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/newsTask/NewsCrawlerTask.java +++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/newsTask/NewsCrawlerTask.java @@ -272,8 +272,8 @@ public class NewsCrawlerTask extends PythonCommandTask { // 状态和时间 item.setStatus(0); // 未处理 item.setCrawlTime(now); - ResultDomain pass = auditService.auditText(item.getContent()); - if(pass.isSuccess() && pass.getData()){ + ResultDomain pass = auditService.auditText(item.getContent()); + if(pass.isSuccess()){ item.setIsAudited(true); passList.add(item); }else{ diff --git a/schoolNewsServ/system/src/main/java/org/xyzh/system/service/menu/service/impl/SysMenuServiceImpl.java b/schoolNewsServ/system/src/main/java/org/xyzh/system/service/menu/service/impl/SysMenuServiceImpl.java index 0c00c95..90babb8 100644 --- a/schoolNewsServ/system/src/main/java/org/xyzh/system/service/menu/service/impl/SysMenuServiceImpl.java +++ b/schoolNewsServ/system/src/main/java/org/xyzh/system/service/menu/service/impl/SysMenuServiceImpl.java @@ -265,15 +265,15 @@ public class SysMenuServiceImpl implements SysMenuService { } // 检查菜单名称是否已存在(排除自身) - ResultDomain checkResult = checkMenuNameExists(menu.getName(), menu.getID()); - if (!checkResult.isSuccess()) { - resultDomain.fail(checkResult.getMessage()); - return resultDomain; - } - if (checkResult.getData()) { - resultDomain.fail("菜单名称已存在"); - return resultDomain; - } + // ResultDomain checkResult = checkMenuNameExists(menu.getName(), menu.getID()); + // if (!checkResult.isSuccess()) { + // resultDomain.fail(checkResult.getMessage()); + // return resultDomain; + // } + // if (checkResult.getData()) { + // resultDomain.fail("菜单名称已存在"); + // return resultDomain; + // } // 设置更新时间 menu.setUpdateTime(new Date()); diff --git a/schoolNewsServ/system/src/main/resources/mapper/MenuMapper.xml b/schoolNewsServ/system/src/main/resources/mapper/MenuMapper.xml index 32efd42..65e8779 100644 --- a/schoolNewsServ/system/src/main/resources/mapper/MenuMapper.xml +++ b/schoolNewsServ/system/src/main/resources/mapper/MenuMapper.xml @@ -139,7 +139,7 @@ SELECT COUNT(1) FROM tb_sys_menu m WHERE deleted = 0 - AND m.name = #{name} + AND m.name = #{menuName} AND m.id != #{excludeId} diff --git a/schoolNewsWeb/src/apis/system/menu.ts b/schoolNewsWeb/src/apis/system/menu.ts index 3a9d3e1..9ce6533 100644 --- a/schoolNewsWeb/src/apis/system/menu.ts +++ b/schoolNewsWeb/src/apis/system/menu.ts @@ -64,7 +64,7 @@ export const menuApi = { * @returns Promise> 返回菜单ID */ async createMenu(menu: SysMenu): Promise> { - const response = await api.post('/menu', menu); + const response = await api.post('/menus/menu', menu); return response.data; }, @@ -75,7 +75,7 @@ export const menuApi = { * @returns Promise> */ async updateMenu(menu: SysMenu): Promise> { - const response = await api.put(`/menus`, menu); + const response = await api.put(`/menus/menu`, menu); return response.data; }, @@ -85,7 +85,7 @@ export const menuApi = { * @returns Promise> */ async deleteMenu(menuID: string): Promise> { - const response = await api.delete(`/menus`, { menuID }); + const response = await api.delete(`/menus/menu`, { menuID }); return response.data; }, diff --git a/schoolNewsWeb/src/assets/styles/common.scss b/schoolNewsWeb/src/assets/styles/common.scss index 174ac3c..5b1edd5 100644 --- a/schoolNewsWeb/src/assets/styles/common.scss +++ b/schoolNewsWeb/src/assets/styles/common.scss @@ -195,7 +195,7 @@ $spacing-xxl: 24px; justify-content: flex-end; align-items: center; padding: $spacing-md 0; - margin-top: 32px; + margin-top: auto; // 自动推到底部 // background: #F9FAFB; border-radius: $border-radius-large; diff --git a/schoolNewsWeb/src/components/base/TopNavigation.vue b/schoolNewsWeb/src/components/base/TopNavigation.vue index 04cc166..22abfd9 100644 --- a/schoolNewsWeb/src/components/base/TopNavigation.vue +++ b/schoolNewsWeb/src/components/base/TopNavigation.vue @@ -195,7 +195,6 @@ function handleWheel(event: WheelEvent) { // 处理导航点击 function handleNavClick(menu: SysMenu) { activeDropdown.value = null; - if (menu.url) { router.push(menu.url); } else if (menu.children && menu.children.length > 0) { diff --git a/schoolNewsWeb/src/utils/route-generator.ts b/schoolNewsWeb/src/utils/route-generator.ts index 5dd34c9..f7c688d 100644 --- a/schoolNewsWeb/src/utils/route-generator.ts +++ b/schoolNewsWeb/src/utils/route-generator.ts @@ -161,14 +161,41 @@ function generateRouteFromMenu(menu: SysMenu, isTopLevel = true): RouteRecordRaw } // 处理子路由 - if (layout && LAYOUT_MAP[layout] && !hasChildren && menu.component && isTopLevel) { - // 如果指定了布局但没有子菜单,将页面组件作为子路由 + if (layout && LAYOUT_MAP[layout] && menu.component && isTopLevel) { + // 如果指定了布局,将页面组件作为子路由 route.children = [{ path: '', name: `${menu.menuID}_page`, component: getComponent(menu.component), meta: route.meta }]; + + // 如果还有其他子菜单,继续添加 + if (hasChildren) { + const pageChildren: SysMenu[] = []; + const normalChildren: SysMenu[] = []; + + menu.children!.forEach(child => { + if (child.type === MenuType.PAGE) { + pageChildren.push(child); + } else { + normalChildren.push(child); + } + }); + + // 添加普通子菜单 + normalChildren.forEach(child => { + const childRoute = generateRouteFromMenu(child, false); + if (childRoute) { + route.children!.push(childRoute); + } + }); + + // PAGE 类型的菜单保存到 meta + if (pageChildren.length > 0) { + route.meta.pageChildren = pageChildren; + } + } } else if (hasChildren) { // 处理有子菜单的情况 route.children = []; @@ -290,6 +317,11 @@ function getComponent(componentName: string) { return module; }) .catch(error => { + console.error('[路由生成] 组件加载失败:', { + 原始组件名: componentName, + 最终路径: componentPath, + 错误: error + }); // 返回404组件 return import('@/views/public/error/404.vue').catch(() => Promise.resolve({ diff --git a/schoolNewsWeb/src/views/user/message/MyMessageListView.vue b/schoolNewsWeb/src/views/user/message/MyMessageListView.vue index 53004c8..858ff19 100644 --- a/schoolNewsWeb/src/views/user/message/MyMessageListView.vue +++ b/schoolNewsWeb/src/views/user/message/MyMessageListView.vue @@ -346,10 +346,12 @@ onMounted(() => {