BUG-P3B-02 测试报告:/auth/me 未返回师傅真实 display_name 和 worker_id¶
DWLG 编号:DWLG-20260811-06 日期:2026-08-11 报告人:DT(听写) 关联文档:DWLG-20260811-05 全面业务测试过程 关联测试项:W3(/auth/me 返回师傅信息)
一、问题描述¶
1.1 现象¶
师傅端使用 worker_uat_0001 / worker123 登录后,调用 GET /api/v1/auth/me 获取当前用户信息,返回的 display_name 为 USR-WKR-UAT0000001(user_id),而非数据库中存储的 張師傅。同时,响应体中缺少 worker_id 字段。
1.2 预期行为¶
{
"user_id": "USR-WKR-UAT0000001",
"roles": ["FL"],
"permissions": ["cpt:list:self"],
"display_name": "張師傅",
"worker_id": "WKR0UAT0000001"
}
1.3 实际行为¶
{
"user_id": "USR-WKR-UAT0000001",
"roles": ["FL"],
"permissions": ["cpt:list:self"],
"display_name": "USR-WKR-UAT0000001"
}
1.4 影响分析¶
| 维度 | 影响 |
|---|---|
| 用户体验 | 师傅端 App 冷启动 hydrate 时,顶部显示"USR-WKR-UAT0000001"而非"張師傅",用户体验差 |
| 功能完整性 | 师傅端无法从 /auth/me 获取 worker_id,需从 JWT token 中解析(前端可 workaround) |
| 安全性 | 无安全风险(user_id 本身不是敏感信息,但暴露了内部 ID 格式) |
| 数据一致性 | /auth/login 返回的 user.display_name 是正确的"張師傅",但 /auth/me 返回错误值,两端不一致 |
二、根因分析¶
2.1 问题代码位置¶
文件:app/interfaces/api/auth.py
行号:206-214
@router.get("/me", response_model=UserMeResponse)
async def current_user(user: CurrentUser = Depends(get_current_user)) -> UserMeResponse:
"""当前登录用户信息(roles 为数组)."""
return UserMeResponse(
user_id=user.user_id,
roles=[user.role] if user.role else [],
permissions=user.permissions,
display_name="开发管理员" if user.role == "TL" else user.user_id, # ← 问题行
)
2.2 根因¶
- display_name 硬编码:第 213 行对非 TL 角色直接用
user.user_id作为display_name,没有查询sys_users表获取真实的display_name字段 - worker_id 未返回:
UserMeResponse模型没有worker_id字段,虽然CurrentUser对象中已包含worker_id属性(从 JWT claims 中提取)
2.3 对比:/auth/login 的正确实现¶
/auth/login 端点(auth.py:65-179)从数据库查询 sys_users 表,正确返回了 display_name:
# auth.py:106-116 — 从数据库查询
row = (
await db.execute(
text(
"SELECT u.user_id, u.username, u.password_hash, u.display_name,"
" u.worker_id, u.status,"
" ARRAY_AGG(r.role_id) FILTER (WHERE r.role_id IS NOT NULL) AS roles"
" FROM sys_users u"
" LEFT JOIN sys_user_roles r ON r.user_id = u.user_id"
" WHERE u.username = :uname AND u.deleted_at IS NULL"
" GROUP BY u.user_id, u.username, u.password_hash,"
" u.display_name, u.worker_id, u.status"
),
{"uname": req.username},
)
).first()
# auth.py:176 — 正确使用 row.display_name
display_name=str(row.display_name),
2.4 CurrentUser 已有 worker_id¶
文件:app/interfaces/dependencies.py
行号:18-33, 74-82
class CurrentUser:
def __init__(
self,
user_id: str,
role: str = "",
permissions: list[str] | None = None,
worker_id: str | None = None, # ← 已有 worker_id
customer_id: str | None = None,
) -> None:
self.user_id = user_id
self.worker_id = worker_id # ← 已赋值
...
# 从 JWT payload 中提取
worker_id: str | None = payload.get("worker_id") # ← 已从 JWT 提取
三、修复方案¶
3.1 方案 A(推荐):从 CurrentUser 直接获取¶
优势:无需查库,JWT 中已有 worker_id;display_name 可从 sys_users 查询或缓存
修改 1:app/application/dtos/auth.py — UserMeResponse 增加 worker_id 字段
class UserMeResponse(BaseModel):
user_id: str
roles: list[str]
permissions: list[str]
display_name: str
worker_id: str | None = None # ← 新增
customer_id: str | None = None # ← 新增(可选,客户端场景)
修改 2:app/interfaces/api/auth.py — current_user 函数
@router.get("/me", response_model=UserMeResponse)
async def current_user(
user: CurrentUser = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> UserMeResponse:
"""当前登录用户信息(roles 为数组).
师傅(FL)返回 worker_id;客户(CST)返回 customer_id。
display_name 从 sys_users 表查询(TL 固定为"开发管理员")。
"""
display_name = "开发管理员" if user.role == "TL" else user.user_id
worker_id = None
customer_id = None
if user.role == "FL":
worker_id = user.worker_id
# 查询 sys_users 获取真实 display_name
row = (
await db.execute(
text(
"SELECT display_name FROM sys_users"
" WHERE user_id = :uid AND deleted_at IS NULL"
),
{"uid": user.user_id},
)
).first()
if row and row.display_name:
display_name = str(row.display_name)
elif user.role == "CST":
customer_id = user.customer_id
# 客户 display_name 从 mds_cpl_customers 查询
if user.customer_id:
row = (
await db.execute(
text(
"SELECT name FROM mds_cpl_customers"
" WHERE customer_id = :cid AND deleted_at IS NULL"
),
{"cid": user.customer_id},
)
).first()
if row and row.name:
display_name = str(row.name)
return UserMeResponse(
user_id=user.user_id,
roles=[user.role] if user.role else [],
permissions=user.permissions,
display_name=display_name,
worker_id=worker_id,
customer_id=customer_id,
)
3.2 方案 B(最小改动):仅从 JWT 提取,不查库¶
优势:改动最小,不增加 DB 查询 劣势:display_name 仍无法获取(JWT 中未存储 display_name)
@router.get("/me", response_model=UserMeResponse)
async def current_user(user: CurrentUser = Depends(get_current_user)) -> UserMeResponse:
"""当前登录用户信息."""
return UserMeResponse(
user_id=user.user_id,
roles=[user.role] if user.role else [],
permissions=user.permissions,
display_name="开发管理员" if user.role == "TL" else (
user.worker_id or user.customer_id or user.user_id
), # 仍非真实 display_name,但比 user_id 更有意义
worker_id=user.worker_id, # ← 新增
customer_id=user.customer_id, # ← 新增
)
3.3 推荐方案¶
推荐方案 A:虽然多一次 DB 查询,但能返回正确的 display_name,用户体验最佳。/auth/me 是低频接口(仅冷启动调用),额外查询开销可接受。
四、测试验证¶
4.1 修复后预期响应¶
师傅(FL):
{
"user_id": "USR-WKR-UAT0000001",
"roles": ["FL"],
"permissions": ["cpt:list:self"],
"display_name": "張師傅",
"worker_id": "WKR0UAT0000001"
}
客户(CST):
{
"user_id": "CUS0UAT0000009",
"roles": ["CST"],
"permissions": ["cpt:list:self"],
"display_name": "周太",
"customer_id": "CUS0UAT0000009"
}
管理员(TL):
{
"user_id": "USR-DEV-ADMIN-000001",
"roles": ["TL"],
"permissions": ["*"],
"display_name": "开发管理员"
}
4.2 验证命令¶
# 师傅登录后验证
$workerToken = (Invoke-RestMethod -Uri "http://localhost:8000/api/v1/auth/login" `
-Method Post -Body '{"username":"worker_uat_0001","password":"worker123"}' `
-ContentType "application/json").access_token
Invoke-RestMethod -Uri "http://localhost:8000/api/v1/auth/me" `
-Headers @{"Authorization"="Bearer $workerToken"} | ConvertTo-Json
# 预期:display_name="張師傅", worker_id="WKR0UAT0000001"
五、优先级与计划¶
| 维度 | 内容 |
|---|---|
| 严重程度 | P2(中) |
| 影响范围 | 师傅端 App 冷启动 hydrate + 客户端 App 冷启动 hydrate |
| 修复时限 | 生产环境部署前必须修复 |
| 修复负责人 | Kimi(代码修正) |
| 验证负责人 | DT(听写,回归测试) |
| 修复状态 | ✅ 已修复(2026-08-11 14:06)— 采用方案 A |
| 回归验证 | ✅ 通过(2026-08-11 14:08)— W3 从 FAIL 转为 PASS;27 项测试 25 PASS / 0 FAIL / 2 SKIP |
5.1 修复确认¶
修复内容(auth.py:56-62, 208-251):
1. UserMeResponse 增加 worker_id: str | None = None 和 customer_id: str | None = None 字段
2. current_user 函数增加 db: AsyncSession 依赖,查询 sys_users(FL 角色)或 mds_cpl_customers(CST 角色)获取真实 display_name
3. 返回 worker_id 和 customer_id
修复后 /auth/me 响应:
{
"user_id": "USR-WKR-UAT0000001",
"roles": ["FL"],
"permissions": ["cpt:list:self"],
"display_name": "張師傅",
"worker_id": "WKR0UAT0000001",
"customer_id": null
}
修订记录¶
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| V1.0 | 2026-08-11 | DT(听写) | 创建:BUG-P3B-02 测试报告,含根因分析 + 2 套修复方案 + 验证命令 |
本报告对齐 DIP1-WLG V1.1 规范,编号 DWLG-20260811-06。BUG-P3B-02 为 P2 级代码问题,需在生产环境部署前修复。