服务端:
- Class 新增 code(唯一)/passwordHash 与迁移(老数据回填 C00001 形式, 未设密码禁止登录)
- POST /auth/class-login: 班级码 + 班级密码 -> classSession 会话(sub=classId)
- PATCH /classes/:id/class-account: 管理员改班级码/重置密码(唯一性、格式校验)
- auth 中间件/socket 握手支持班级会话(sub 为 classId, 回查 Class 表, 加入 user:{classId} 房间)
- lib/session.ts: 班级设备代全班操作(通知已读/作业完成/成绩总览按全班读写), 普通学生账号行为不变
- 叫人消息同时广播到 user:{classId}, 班级设备立即上岛
- 班级列表/详情按权限返回班级码与密码状态(哈希不外泄)
客户端:
- 登录页改为班级码 + 班级密码(个人学生入口移除, /auth/login 接口保留兼容)
- 作业按钮/成绩标题按班级会话显示(全班标记完成 / 本班成绩)
- 冒烟脚本自动准备班级账号凭据(兼容全新与升级安装)
Web 管理端:
- 班级管理新增「班级账号」列与设置弹窗(改班级码/重置密码/随机生成)
- 新建班级可选自定义班级码, 默认由服务端生成
验证: verify:e2e 125/125(新增班级账号 18 项) verify:web 18/18 verify:desktop 40/40
(login=高一(1)班 classSession=true)
文档: README 新增「学生端班级账号」章节、API/迁移/验收/默认账号更新; 安装包 README 同步
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { Router } from 'express';
|
|
import type { LoginResponse } from '@classhelper/shared';
|
|
import { sendOk } from '../../lib/http.js';
|
|
import { authenticate, getAuthUser } from '../../middleware/auth.js';
|
|
import { validate } from '../../middleware/validate.js';
|
|
import { defineModule } from '../module.types.js';
|
|
import {
|
|
changePasswordSchema,
|
|
classLoginSchema,
|
|
loginSchema,
|
|
type ChangePasswordInput,
|
|
type ClassLoginInput,
|
|
type LoginInput,
|
|
} from './auth.schemas.js';
|
|
import * as authService from './auth.service.js';
|
|
|
|
const router = Router();
|
|
|
|
/** POST /api/auth/login - 登录(公开):教师 / 管理员 / 个人学生账号 */
|
|
router.post('/login', validate({ body: loginSchema }), async (req, res) => {
|
|
const result: LoginResponse = await authService.login(req.body as LoginInput);
|
|
sendOk(res, result, '登录成功');
|
|
});
|
|
|
|
/**
|
|
* POST /api/auth/class-login - 班级账号登录(公开,学生端主入口)
|
|
*
|
|
* 班级码 + 班级密码 → 以「班级」为主体的 JWT(个人数据按全班读写)。
|
|
* 个人学生账号仍可用 /auth/login 登录(向后兼容,客户端已不再展示该入口)。
|
|
*/
|
|
router.post('/class-login', validate({ body: classLoginSchema }), async (req, res) => {
|
|
const result: LoginResponse = await authService.classLogin(req.body as ClassLoginInput);
|
|
sendOk(res, result, '班级登录成功');
|
|
});
|
|
|
|
/** GET /api/auth/me - 当前用户信息(班级账号返回班级信息) */
|
|
router.get('/me', authenticate(), async (req, res) => {
|
|
const user = getAuthUser(req);
|
|
sendOk(res, await authService.getProfile(user), '获取当前用户成功');
|
|
});
|
|
|
|
/** PATCH /api/auth/password - 修改密码(班级账号修改的是班级密码) */
|
|
router.patch('/password', authenticate(), validate({ body: changePasswordSchema }), async (req, res) => {
|
|
const user = getAuthUser(req);
|
|
await authService.changePassword(user, req.body as ChangePasswordInput);
|
|
sendOk(res, { changed: true }, '密码修改成功,请重新登录');
|
|
});
|
|
|
|
/**
|
|
* POST /api/auth/logout - 退出登录。
|
|
* JWT 为无状态凭证,服务端不做黑名单,客户端丢弃 token 即可。
|
|
*/
|
|
router.post('/logout', authenticate(), (_req, res) => {
|
|
sendOk(res, { loggedOut: true }, '已退出登录');
|
|
});
|
|
|
|
export const authModule = defineModule({
|
|
name: 'auth',
|
|
basePath: '/auth',
|
|
router,
|
|
});
|