From df4c21bd0dd555dc0f62e1fa0aeca80cdf7337d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cwuzhen=E2=80=9D?= Date: Mon, 26 Oct 2026 08:29:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(xg):=20=E6=96=B0=E5=A2=9E=E6=8C=89?= =?UTF-8?q?=E5=AD=A6=E5=8F=B7=E9=87=8D=E7=BD=AE=E5=AD=A6=E7=94=9F=E5=AF=86?= =?UTF-8?q?=E7=A0=81=EF=BC=88Web/=E7=A7=BB=E5=8A=A8=E7=AB=AF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 学工老师可按学号查询并重置学生登录密码为固定默认值,独立权限不复用系统用户重置。 Co-authored-by: Cursor --- .../xg/controller/XgStudentMsgController.java | 24 +++ .../xg/domain/vo/StudentPwdResetVo.java | 31 +++ .../xg/service/IXgStudentMsgService.java | 17 ++ .../service/impl/XgStudentMsgServiceImpl.java | 77 +++++++ ...update_20261026_student_pwd_reset_menu.sql | 76 +++++++ docs/开发文档/API/API文档总览.md | 1 + docs/开发文档/API/学生密码重置API.md | 141 ++++++++++++ mobile/src/api/studentPwdReset.js | 26 +++ mobile/src/pages.json | 8 + mobile/src/pages/index/index.vue | 17 ++ .../pages/xg/xsjcxx/studentPwdReset/index.vue | 203 ++++++++++++++++++ mobile/src/utils/router.js | 2 + .../xg/xsjcxx/studentMsg/studentMsg/index.ts | 28 +++ .../studentMsg/studentPwdReset/index.vue | 130 +++++++++++ 14 files changed, 781 insertions(+) create mode 100644 backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/StudentPwdResetVo.java create mode 100644 backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql create mode 100644 docs/开发文档/API/学生密码重置API.md create mode 100644 mobile/src/api/studentPwdReset.js create mode 100644 mobile/src/pages/xg/xsjcxx/studentPwdReset/index.vue create mode 100644 web/src/views/xg/xsjcxx/studentMsg/studentPwdReset/index.vue diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java index 580e48f2..13c05ca4 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java @@ -44,6 +44,7 @@ import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.xg.domain.vo.XgStudentMsgVo; +import org.dromara.xg.domain.vo.StudentPwdResetVo; import org.dromara.xg.domain.bo.XgStudentMsgBo; import org.dromara.xg.domain.bo.XgStudentMsgAdvancedSearchBo; import org.dromara.xg.service.IXgStudentMsgService; @@ -660,6 +661,29 @@ public class XgStudentMsgController extends BaseController { return R.ok(studentInfo); } + /** + * 按学号查询学生基本信息(重置密码页) + * 须声明在 /{id} 之前,避免被路径变量吞掉 + */ + @SaCheckPermission("studentMsg:studentPwdReset:query") + @GetMapping("/queryByStudentNo/{studentNo}") + public R queryByStudentNo( + @NotBlank(message = "学号不能为空") @PathVariable("studentNo") String studentNo) { + return R.ok(xgStudentMsgService.queryForPwdResetByStudentNo(studentNo)); + } + + /** + * 按学号重置学生登录密码为固定默认值 + */ + @SaCheckPermission("studentMsg:studentPwdReset:reset") + @Log(title = "重置学生密码", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/resetStudentPassword/{studentNo}") + public R resetStudentPassword( + @NotBlank(message = "学号不能为空") @PathVariable("studentNo") String studentNo) { + return toAjax(xgStudentMsgService.resetStudentPasswordByStudentNo(studentNo)); + } + /** * 获取学生信息详细信息 * diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/StudentPwdResetVo.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/StudentPwdResetVo.java new file mode 100644 index 00000000..8bf93520 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/vo/StudentPwdResetVo.java @@ -0,0 +1,31 @@ +package org.dromara.xg.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 重置学生密码:按学号查询展示信息 + */ +@Data +public class StudentPwdResetVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 学生ID(与 sys_user.user_id 相同) */ + private Long id; + + /** 学号 */ + private String studentNo; + + /** 姓名 */ + private String name; + + /** 班级名称 */ + private String className; + + /** 班主任姓名 */ + private String classTeacherName; +} diff --git a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java index 030c9bd8..3cd5e827 100644 --- a/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java +++ b/backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java @@ -3,6 +3,7 @@ package org.dromara.xg.service; import org.dromara.xg.domain.vo.XgStudentMsgExportVo; import org.dromara.xg.domain.vo.XgStudentMsgVo; import org.dromara.xg.domain.vo.XgStudentTreeVo; +import org.dromara.xg.domain.vo.StudentPwdResetVo; import org.dromara.xg.domain.bo.XgStudentMsgBo; import org.dromara.xg.domain.bo.XgStudentMsgAdvancedSearchBo; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -176,4 +177,20 @@ public interface IXgStudentMsgService { * @return 学生信息树 */ List getCurrentUserStudentTree(); + + /** + * 按学号查询学生基本信息(重置密码页展示) + * + * @param studentNo 学号 + * @return 姓名/学号/班级/班主任 + */ + StudentPwdResetVo queryForPwdResetByStudentNo(String studentNo); + + /** + * 按学号重置学生登录密码为固定默认值 + * + * @param studentNo 学号 + * @return 是否成功 + */ + Boolean resetStudentPasswordByStudentNo(String studentNo); } 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 268d7de1..f2cb44c8 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 @@ -25,6 +25,8 @@ import org.dromara.system.mapper.SysDeptMapper; import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.SysDept; import org.dromara.system.domain.vo.SysDeptVo; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.xg.domain.vo.StudentPwdResetVo; import cn.hutool.crypto.digest.BCrypt; import cn.hutool.core.util.ObjectUtil; import java.util.List; @@ -1557,4 +1559,79 @@ public class XgStudentMsgServiceImpl implements IXgStudentMsgService { private Long getCurrentUserCollegeId() { return currentUserCollegeHelper.resolveCurrentUserCollegeId(); } + + /** 学生账号重置密码固定值(仅用于管理端重置学生密码) */ + private static final String STUDENT_RESET_PASSWORD = "Hzdyjsxs123456!@#"; + + @Override + public StudentPwdResetVo queryForPwdResetByStudentNo(String studentNo) { + if (StringUtils.isBlank(studentNo)) { + throw new ServiceException("学号不能为空"); + } + XgStudentMsg student = baseMapper.selectOne( + Wrappers.lambdaQuery() + .eq(XgStudentMsg::getStudentNo, studentNo.trim()) + .last("LIMIT 1") + ); + if (student == null) { + throw new ServiceException("未找到学号对应的学生信息"); + } + StudentPwdResetVo vo = new StudentPwdResetVo(); + vo.setId(student.getId()); + vo.setStudentNo(student.getStudentNo()); + vo.setName(student.getName()); + vo.setClassName(student.getClassName()); + vo.setClassTeacherName(student.getClassTeacherName()); + // 学生档案班主任为空时,回退班级表 + if (StringUtils.isBlank(vo.getClassTeacherName()) && StringUtils.isNotBlank(student.getClassId())) { + try { + XgClassMsg classMsg = xgClassMsgMapper.selectById(Long.valueOf(student.getClassId())); + if (classMsg != null && StringUtils.isNotBlank(classMsg.getClassTeacherName())) { + vo.setClassTeacherName(classMsg.getClassTeacherName()); + } + if (StringUtils.isBlank(vo.getClassName()) && classMsg != null) { + vo.setClassName(classMsg.getClassName()); + } + } catch (NumberFormatException ignored) { + // classId 非数字时忽略回退 + } + } + return vo; + } + + @Override + public Boolean resetStudentPasswordByStudentNo(String studentNo) { + if (StringUtils.isBlank(studentNo)) { + throw new ServiceException("学号不能为空"); + } + String no = studentNo.trim(); + XgStudentMsg student = baseMapper.selectOne( + Wrappers.lambdaQuery() + .eq(XgStudentMsg::getStudentNo, no) + .last("LIMIT 1") + ); + if (student == null) { + throw new ServiceException("未找到学号对应的学生信息"); + } + if (student.getId() == null) { + throw new ServiceException("学生数据异常,缺少主键"); + } + SysUserVo user = sysUserService.selectUserById(student.getId()); + if (user == null) { + throw new ServiceException("该学生没有登录账号,无法重置密码"); + } + if (!"2".equals(user.getAccountType())) { + throw new ServiceException("该账号不是学生账号,禁止重置"); + } + if (StringUtils.isNotBlank(user.getUserName()) && !no.equals(user.getUserName())) { + throw new ServiceException("学号与登录账号不一致,请联系管理员"); + } + int rows = sysUserService.resetUserPwd(student.getId(), BCrypt.hashpw(STUDENT_RESET_PASSWORD)); + if (rows <= 0) { + throw new ServiceException("重置密码失败,请稍后重试"); + } + log.info("学生密码已重置, studentNo={}, studentId={}, operator={}", + no, student.getId(), LoginHelper.getUserId()); + return true; + } } diff --git a/backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql b/backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql new file mode 100644 index 00000000..84efbea8 --- /dev/null +++ b/backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql @@ -0,0 +1,76 @@ +-- ============================================================================= +-- 重置学生密码:菜单 + 按钮权限 +-- 日期:2026-10-26 +-- 父菜单:学生管理 (1959499382894141441) +-- Web 页面:xg/xsjcxx/studentMsg/studentPwdReset/index +-- 移动端首页入口权限:student:pwdreset:listapp +-- 重置密码固定值(服务端写死):Hzdyjsxs123456!@# +-- 执行:开发/生产库均可幂等执行;执行后给角色勾选菜单或跑下方角色授权段 +-- ============================================================================= + +-- 1. 菜单页(学生管理下级) +INSERT INTO `sys_menu` ( + `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, + `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, + `create_dept`, `create_by`, `create_time`, `remark` +) +SELECT 2026102610000000101, '重置学生密码', 1959499382894141441, 16, 'studentPwdReset', + 'xg/xsjcxx/studentMsg/studentPwdReset/index', NULL, + 1, 0, 'C', '0', '0', 'studentMsg:studentPwdReset:list', 'password', + 103, 1, NOW(), '按学号查询并重置学生登录密码' +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000101); + +-- 2. 按钮:查询 +INSERT INTO `sys_menu` ( + `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, + `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, + `create_dept`, `create_by`, `create_time`, `remark` +) +SELECT 2026102610000000102, '查询学生', 2026102610000000101, 1, '#', '', NULL, + 1, 0, 'F', '0', '0', 'studentMsg:studentPwdReset:query', '#', + 103, 1, NOW(), '按学号查询学生信息' +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000102); + +-- 3. 按钮:重置密码 +INSERT INTO `sys_menu` ( + `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, + `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, + `create_dept`, `create_by`, `create_time`, `remark` +) +SELECT 2026102610000000103, '确认重置', 2026102610000000101, 2, '#', '', NULL, + 1, 0, 'F', '0', '0', 'studentMsg:studentPwdReset:reset', '#', + 103, 1, NOW(), '确认重置学生密码' +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000103); + +-- 4. 移动端首页入口权限(挂在同一菜单页下,供 hasPermi 控制入口可见) +INSERT INTO `sys_menu` ( + `menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, + `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, + `create_dept`, `create_by`, `create_time`, `remark` +) +SELECT 2026102610000000104, '重置学生密码(移动端)', 2026102610000000101, 3, '', '', NULL, + 1, 0, 'F', '0', '0', 'student:pwdreset:listapp', '#', + 103, 1, NOW(), '移动端首页入口权限' +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` WHERE `menu_id` = 2026102610000000104); + +-- 5. 给已有「学生信息」菜单权限的角色补授权(幂等) +INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) +SELECT DISTINCT rm.role_id, m.menu_id +FROM `sys_role_menu` rm +CROSS JOIN ( + SELECT 2026102610000000101 AS menu_id UNION ALL + SELECT 2026102610000000102 UNION ALL + SELECT 2026102610000000103 UNION ALL + SELECT 2026102610000000104 +) m +WHERE rm.menu_id = 1948289114628902914 + AND NOT EXISTS ( + SELECT 1 FROM `sys_role_menu` x + WHERE x.role_id = rm.role_id AND x.menu_id = m.menu_id + ); + +-- 核对 +SELECT menu_id, menu_name, parent_id, path, component, perms, menu_type +FROM sys_menu +WHERE menu_id BETWEEN 2026102610000000101 AND 2026102610000000104 +ORDER BY menu_id; diff --git a/docs/开发文档/API/API文档总览.md b/docs/开发文档/API/API文档总览.md index 42bfc7d6..aca46189 100644 --- a/docs/开发文档/API/API文档总览.md +++ b/docs/开发文档/API/API文档总览.md @@ -8,6 +8,7 @@ ## API模块列表 - [学生信息管理API](./学生信息管理API.md) +- [学生密码重置API](./学生密码重置API.md) - **新增**:按学号查询并重置学生登录密码为固定默认值(2026-10-26) - [学生信息收集批次管理API](./学生信息收集批次管理API.md) - [学生信息收集批次API](./学生信息收集批次API.md) - **重要更新**:修改列表排序方式为按创建时间倒序(2025-01-03) - [班级信息管理API](./班级信息管理API.md) diff --git a/docs/开发文档/API/学生密码重置API.md b/docs/开发文档/API/学生密码重置API.md new file mode 100644 index 00000000..c51e82b6 --- /dev/null +++ b/docs/开发文档/API/学生密码重置API.md @@ -0,0 +1,141 @@ +# 学生密码重置 API 接口文档 + +## 模块概述 + +**模块名称**:Student Password Reset / 学生密码重置 +**基础路径**:`/studentMsg/studentMsg` +**版本**:v1.0 +**负责人**:学工模块 +**更新时间**:2026-10-26 + +### 模块功能 + +学工老师按学号查询学生基本信息后,一键将对应学生登录账号密码重置为系统固定默认值。 +**不复用** `system:user:resetPwd`,避免把系统用户管理权下放给学工老师。 + +**核心约束**: +- 仅允许重置 `sys_user.account_type = '2'`(学生账号) +- 要求 `user_id = xg_student_msg.id`,且 `user_name` 与学号一致(若有用户名) +- 密码由服务端写死常量,前端不可传入自定义密码 +- 有菜单/按钮权限即可操作任意学生,不做班主任数据范围限制 + +**固定默认密码**(服务端常量,勿在客户端硬编码用于改密请求):`Hzdyjsxs123456!@#` + +### 权限说明 + +| 权限标识 | 说明 | +| -------- | ---- | +| `studentMsg:studentPwdReset:list` | Web 菜单页可见 | +| `studentMsg:studentPwdReset:query` | 按学号查询 | +| `studentMsg:studentPwdReset:reset` | 确认重置密码 | +| `student:pwdreset:listapp` | 移动端首页入口可见 | + +菜单脚本:`backend/script/sql/update/update_20261026_student_pwd_reset_menu.sql` + +--- + +## 接口概览 + +| 接口名称 | HTTP 方法 | 路径 | 权限要求 | 功能描述 | +| -------- | --------- | ---- | -------- | -------- | +| 按学号查询学生 | GET | `/studentMsg/studentMsg/queryByStudentNo/{studentNo}` | `studentMsg:studentPwdReset:query` | 返回姓名、学号、班级、班主任 | +| 重置学生密码 | PUT | `/studentMsg/studentMsg/resetStudentPassword/{studentNo}` | `studentMsg:studentPwdReset:reset` | 重置为固定默认密码 | + +--- + +## 详细接口说明 + +### 1. 按学号查询学生(重置密码页) + +- **接口路径**:`GET /studentMsg/studentMsg/queryByStudentNo/{studentNo}` +- **权限标识**:`studentMsg:studentPwdReset:query` +- **功能描述**:按学号查询学生档案,用于重置前确认身份;班主任姓名优先取学生档案,为空时回退班级表 + +#### 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ---- | ---- | ---- | +| studentNo | string | 是 | 学号 | + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "操作成功", + "data": { + "id": 1234567890123456789, + "studentNo": "202513063048", + "name": "张三", + "className": "计应2401", + "classTeacherName": "李老师" + } +} +``` + +#### 错误场景 + +| 场景 | 提示 | +| ---- | ---- | +| 学号为空 | 学号不能为空 | +| 学号不存在 | 未找到学号对应的学生信息 | +| 无权限 | 403 | + +--- + +### 2. 重置学生密码 + +- **接口路径**:`PUT /studentMsg/studentMsg/resetStudentPassword/{studentNo}` +- **权限标识**:`studentMsg:studentPwdReset:reset` +- **功能描述**:将学号对应学生登录密码重置为固定默认值;写操作日志「重置学生密码」 +- **防重复提交**:`@RepeatSubmit` + +#### 路径参数 + +| 参数名 | 类型 | 必填 | 说明 | +| ------ | ---- | ---- | ---- | +| studentNo | string | 是 | 学号 | + +#### 请求体 + +无。密码由服务端常量生成 BCrypt 后写入,不允许客户端传密。 + +#### 成功响应 + +```json +{ + "code": 200, + "msg": "操作成功" +} +``` + +#### 错误场景 + +| 场景 | 提示 | +| ---- | ---- | +| 学号不存在 | 未找到学号对应的学生信息 | +| 无登录账号 | 该学生没有登录账号,无法重置密码 | +| 非学生账号 | 该账号不是学生账号,禁止重置 | +| 学号与登录名不一致 | 学号与登录账号不一致,请联系管理员 | +| 无权限 | 403 | + +--- + +## 前端与移动端 + +| 端 | 路径 / 入口 | +| -- | ----------- | +| Web | `web/src/views/xg/xsjcxx/studentMsg/studentPwdReset/index.vue` | +| Web API | `web/src/api/xg/xsjcxx/studentMsg/studentMsg/index.ts` → `queryStudentForPwdReset` / `resetStudentPassword` | +| 移动端页面 | `mobile/src/pages/xg/xsjcxx/studentPwdReset/index.vue` | +| 移动端 API | `mobile/src/api/studentPwdReset.js` | +| 移动端首页 | 学工管理 →「重置学生密码」(`student:pwdreset:listapp`) | + +--- + +## 验收要点 + +1. 有权限账号:学号查得到 → 重置成功 → 该学号可用默认密码登录 +2. 无权限账号:接口 403 +3. 不存在学号 / 无用户 / 非学生账号:友好错误,不改密 +4. Web、移动端流程一致:输入学号 → 查询 → 确认 → 重置 diff --git a/mobile/src/api/studentPwdReset.js b/mobile/src/api/studentPwdReset.js new file mode 100644 index 00000000..26207ca0 --- /dev/null +++ b/mobile/src/api/studentPwdReset.js @@ -0,0 +1,26 @@ +/** + * 重置学生密码 API + */ +import { request } from '@/common/request.js'; + +/** + * 按学号查询学生基本信息(重置密码页) + * @param {string} studentNo 学号 + */ +export const queryStudentForPwdReset = (studentNo) => { + return request({ + url: `/studentMsg/studentMsg/queryByStudentNo/${encodeURIComponent(studentNo)}`, + method: 'GET' + }); +}; + +/** + * 按学号重置学生登录密码为固定默认值 + * @param {string} studentNo 学号 + */ +export const resetStudentPassword = (studentNo) => { + return request({ + url: `/studentMsg/studentMsg/resetStudentPassword/${encodeURIComponent(studentNo)}`, + method: 'PUT' + }); +}; diff --git a/mobile/src/pages.json b/mobile/src/pages.json index d2b6df29..af1f5525 100644 --- a/mobile/src/pages.json +++ b/mobile/src/pages.json @@ -65,6 +65,14 @@ "navigationBarTextStyle": "white" } }, + { + "path": "pages/xg/xsjcxx/studentPwdReset/index", + "style": { + "navigationBarTitleText": "重置学生密码", + "navigationBarBackgroundColor": "#0051d9", + "navigationBarTextStyle": "white" + } + }, { "path": "pages/xg/xsjcxx/xsws/index", "style": { diff --git a/mobile/src/pages/index/index.vue b/mobile/src/pages/index/index.vue index 66c9f5ee..fd79b683 100644 --- a/mobile/src/pages/index/index.vue +++ b/mobile/src/pages/index/index.vue @@ -86,6 +86,15 @@ /> + + +