当前项目已经具备以下与登录相关的基础形态:
frontend/src/pages/public/LoginPage.tsx。frontend/src/services/api/auth.ts。frontend/src/services/http/session.ts 和 frontend/src/shared/RequireAuth.tsx。backend/src/modules/auth/,其中 POST /api/v1/auth/stylist/login 尚未实现真实逻辑。{ ok, data, error },并坚持“零 Mock”原则。现阶段项目开发方式改为 case-by-case,因此第一个 case 需要先把“美发师后台登录”做成一个真实可跑通的最小闭环,为后续 dashboard、customers、templates、simulations 等后台能力提供统一鉴权基础。
email + password 的真实登录能力。401 时,前端自动清理登录态并要求重新登录。本 case 只覆盖以下闭环:
401 自动清理登录态。本 case 不要求同时解决所有认证相关专题,而是优先保证“真实登录进入后台”这条主路径可用且稳定。
auth 模块和通用鉴权层,不把 token 解析散落到各业务模块。frontend/src/services/api/auth.ts 已约定登录成功后返回 token、tokenName、stylistId、name、email。frontend/src/services/http/client.ts 已支持在请求头中自动附加 Authorization: <token>。frontend/src/services/http/session.ts 当前使用 localStorage 保存 token 和 tokenName。frontend/src/shared/RequireAuth.tsx 当前只根据本地是否存在 token 判断是否允许进入后台页面。frontend/src/pages/public/__tests__/login-page.test.tsx 已覆盖登录错误提示区域的稳定渲染行为。backend/src/modules/auth/dto/stylist-login.dto.ts 已定义 email、password、remember 三个字段。backend/src/modules/auth/dto/login-result.dto.ts 已定义登录成功返回字段。backend/prisma/schema.prisma 已包含 HairstylistUser 模型,对应 hairstylist_user 表。backend/src/modules/auth/auth.service.ts 目前仍抛出 NotImplementedException,尚未接入真实账号校验。因此,本设计优先补齐真实认证逻辑,而不是重新设计一套新的前后端接口。
POST /api/v1/auth/stylist/login请求体:
{
"email": "stylist@example.com",
"password": "plain-password",
"remember": false
}
字段约束:
email: 必填,合法邮箱格式。password: 必填,最少 8 位。remember: 选填,布尔值。成功响应:
{
"ok": true,
"data": {
"token": "jwt-token",
"tokenName": "Authorization",
"stylistId": 1,
"name": "Ogawa",
"email": "stylist@example.com"
},
"error": null
}
失败响应:
{
"ok": false,
"data": null,
"error": {
"code": "AUTH_INVALID_CREDENTIALS",
"message": "邮箱或密码错误"
}
}
约束说明:
tokenName 固定返回 Authorization,与前端现有发送方式保持一致。POST /api/v1/auth/logout成功响应:
{
"ok": true,
"data": {
"success": true
},
"error": null
}
说明:
prisma.hairstylistUser 查询 hairstylist_user 表。is_deleted = 0 的账号参与登录。password 视为加密后的口令,而不是明文密码。bcrypt 做密码哈希比对。sub、email、username。username 供工作台欢迎区直接消费,不再额外请求 /me 接口。remember 继续透传给后端,保持接口兼容。remember 不触发不同的 token 生命周期策略。remember 实际语义。POST /auth/stylist/login 和 POST /auth/logout。负责 4 件核心工作:
这样可以把真实认证逻辑集中在一个地方,便于后续继续扩展注册、重置密码或审计能力。
Authorization 请求头。submitting 状态,禁止重复提交。stylistLogin()。setSession() 保存 token 和 tokenName。/{locale}/dashboard。localStorage 保存 token。RequireAuth 仅判断本地 token 是否存在,不在前端解析 JWT 内容。frontend/src/services/http/client.ts 自动附加 Authorization: <token>。401 时,前端统一执行 clearSession()。RequireAuth 自动重定向回登录页。login-page.test.tsx 的测试意图保持一致。POST /auth/logout。clearSession()。这样可以保证用户感知上的退出动作是确定完成的,而不依赖服务端必须维护黑名单。
以下情况统一作为登录失败处理:
对外统一返回:
401AUTH_INVALID_CREDENTIALS邮箱或密码错误以下情况统一返回 401:
前端面对这些情况的行为统一是:
401 的测试。401 后清 session 的行为测试。dashboard,刷新页面后仍保持已登录状态。401,前端自动清理登录态。StylistLoginDto 和 LoginResultDto,避免重复定义契约。hairstylist_user.password 还不是 bcrypt 哈希,需要先明确初始化策略,再决定如何创建首批测试账号。logout 在本轮是“前端闭环接口”,不是“服务端会话撤销接口”,实现时不要过度设计。在本 case 完成后,建议按以下顺序继续推进: