users.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. from typing import List, Any
  2. import logging
  3. from fastapi import APIRouter, Depends, HTTPException, Body, BackgroundTasks, Request, Query
  4. from sqlalchemy.orm import Session, joinedload
  5. from sqlalchemy import or_
  6. from sqlalchemy.exc import IntegrityError
  7. from app.api.v1 import deps
  8. from app.core import security
  9. from app.core.utils import generate_english_name, get_client_ip
  10. from app.models.user import User, UserRole
  11. from app.models.organization import Organization
  12. from app.models.mapping import AppUserMapping
  13. from app.schemas.user import (
  14. User as UserSchema,
  15. UserCreate,
  16. UserUpdate,
  17. UserList,
  18. PromoteUserRequest,
  19. BatchResetEnglishNameRequest,
  20. BatchSetOrganizationRequest,
  21. DeleteUserRequest,
  22. )
  23. from app.services.webhook_service import WebhookService
  24. from app.services.captcha_service import CaptchaService
  25. from app.services.sms_service import SmsService
  26. from app.services.log_service import LogService
  27. from app.schemas.operation_log import ActionType
  28. router = APIRouter()
  29. logger = logging.getLogger(__name__)
  30. @router.get("/search", response_model=List[UserSchema], summary="搜索用户")
  31. def search_users(
  32. keyword: str = Query(None, alias="q"),
  33. limit: int = 20,
  34. db: Session = Depends(deps.get_db),
  35. current_subject: deps.AuthSubject = Depends(deps.get_current_user_or_app_by_api_key),
  36. ):
  37. """
  38. 搜索用户(支持姓名、手机号、英文名)。
  39. 支持用户 JWT Token 和应用 API Key (X-App-Access-Token) 两种认证方式。
  40. """
  41. query = (
  42. db.query(User)
  43. .options(joinedload(User.organization))
  44. .filter(
  45. User.is_deleted == 0,
  46. User.status == "ACTIVE",
  47. )
  48. )
  49. if keyword:
  50. query = query.filter(
  51. or_(
  52. User.mobile.ilike(f"%{keyword}%"),
  53. User.name.ilike(f"%{keyword}%"),
  54. User.english_name.ilike(f"%{keyword}%")
  55. )
  56. )
  57. # 如果是用户认证,排除自己;如果是应用认证,不排除
  58. if isinstance(current_subject, User):
  59. query = query.filter(User.id != current_subject.id)
  60. users = query.limit(limit).all()
  61. return users
  62. @router.get("/", response_model=UserList, summary="获取用户列表")
  63. def read_users(
  64. skip: int = 0,
  65. limit: int = 10,
  66. status: str = None,
  67. role: str = None,
  68. mobile: str = None,
  69. name: str = None,
  70. english_name: str = None,
  71. keyword: str = None,
  72. organization_id: int = None,
  73. db: Session = Depends(deps.get_db),
  74. current_user: User = Depends(deps.get_current_active_user),
  75. ):
  76. """
  77. 获取用户列表。已登录用户即可访问。
  78. 支持通过 mobile, name, english_name 精确/模糊筛选,
  79. 也支持通过 keyword 进行跨字段模糊搜索。
  80. organization_id:
  81. - None: 不过滤
  82. - 0: 仅未分配组织(organization_id IS NULL)
  83. - >0: 指定组织
  84. """
  85. # Filter out soft-deleted users
  86. query = db.query(User).filter(User.is_deleted == 0)
  87. if organization_id is not None:
  88. if organization_id == 0:
  89. query = query.filter(User.organization_id.is_(None))
  90. else:
  91. query = query.filter(User.organization_id == organization_id)
  92. if status:
  93. query = query.filter(User.status == status)
  94. if role:
  95. query = query.filter(User.role == role)
  96. # Specific field filters (AND logic)
  97. if mobile:
  98. query = query.filter(User.mobile.ilike(f"%{mobile}%"))
  99. if name:
  100. query = query.filter(User.name.ilike(f"%{name}%"))
  101. if english_name:
  102. query = query.filter(User.english_name.ilike(f"%{english_name}%"))
  103. # Keyword search (OR logic across fields) - intersects with previous filters
  104. if keyword:
  105. query = query.filter(
  106. or_(
  107. User.mobile.ilike(f"%{keyword}%"),
  108. User.name.ilike(f"%{keyword}%"),
  109. User.english_name.ilike(f"%{keyword}%")
  110. )
  111. )
  112. total = query.count()
  113. users = (
  114. query.options(joinedload(User.organization))
  115. .order_by(User.id.desc())
  116. .offset(skip)
  117. .limit(limit)
  118. .all()
  119. )
  120. return {"total": total, "items": users}
  121. @router.post("/", response_model=UserSchema, summary="创建用户")
  122. def create_user(
  123. *,
  124. db: Session = Depends(deps.get_db),
  125. user_in: UserCreate,
  126. request: Request,
  127. background_tasks: BackgroundTasks,
  128. current_user: User = Depends(deps.get_current_active_user),
  129. ):
  130. """
  131. 创建新用户。只有超级管理员可以直接创建用户。
  132. 公开注册请使用 /open/register。
  133. """
  134. if current_user.role != "SUPER_ADMIN":
  135. raise HTTPException(status_code=403, detail="权限不足")
  136. # Verify Admin Password
  137. if not user_in.admin_password or not security.verify_password(user_in.admin_password, current_user.password_hash):
  138. logger.warning(f"管理员创建用户失败: 密码错误 (Admin: {current_user.mobile})")
  139. raise HTTPException(status_code=403, detail="管理员密码错误")
  140. user = db.query(User).filter(User.mobile == user_in.mobile).first()
  141. if user:
  142. raise HTTPException(
  143. status_code=400,
  144. detail="该手机号已在系统中注册",
  145. )
  146. if user_in.password:
  147. user_in.password = user_in.password.strip()
  148. # Remove spaces from name
  149. if user_in.name:
  150. user_in.name = user_in.name.replace(" ", "")
  151. # Generate default name if missing
  152. if not user_in.name:
  153. random_suffix = security.generate_alphanumeric_password(6)
  154. user_in.name = f"用户{random_suffix}"
  155. # Ensure unique name
  156. if user_in.name:
  157. original_name = user_in.name
  158. counter = 1
  159. while db.query(User).filter(User.name == user_in.name, User.is_deleted == 0).first():
  160. user_in.name = f"{original_name}{counter}"
  161. counter += 1
  162. english_name = user_in.english_name
  163. if not english_name and user_in.name:
  164. english_name = generate_english_name(user_in.name)
  165. # Ensure unique english_name
  166. if english_name:
  167. original_english_name = english_name
  168. counter = 1
  169. while db.query(User).filter(User.english_name == english_name, User.is_deleted == 0).first():
  170. english_name = f"{original_english_name}{counter}"
  171. counter += 1
  172. org_id = None
  173. if user_in.organization_id is not None:
  174. org = db.query(Organization).filter(Organization.id == user_in.organization_id).first()
  175. if not org:
  176. raise HTTPException(status_code=400, detail="组织不存在")
  177. org_id = user_in.organization_id
  178. db_user = User(
  179. mobile=user_in.mobile,
  180. name=user_in.name,
  181. english_name=english_name,
  182. password_hash=security.get_password_hash(user_in.password),
  183. status="ACTIVE", # Admin created users are active by default
  184. role=user_in.role or UserRole.ORDINARY_USER,
  185. organization_id=org_id,
  186. )
  187. db.add(db_user)
  188. db.commit()
  189. db_user = (
  190. db.query(User)
  191. .options(joinedload(User.organization))
  192. .filter(User.id == db_user.id)
  193. .first()
  194. )
  195. # Log Operation
  196. LogService.create_log(
  197. db=db,
  198. operator_id=current_user.id,
  199. action_type=ActionType.MANUAL_ADD,
  200. target_user_id=db_user.id,
  201. target_mobile=db_user.mobile,
  202. ip_address=get_client_ip(request),
  203. details={"role": db_user.role}
  204. )
  205. logger.info(f"管理员创建用户成功: {db_user.mobile} (Role: {db_user.role})")
  206. return db_user
  207. @router.post("/batch/reset-english-name", summary="批量重置用户英文名")
  208. def batch_reset_english_name(
  209. *,
  210. db: Session = Depends(deps.get_db),
  211. req: BatchResetEnglishNameRequest,
  212. request: Request,
  213. current_user: User = Depends(deps.get_current_active_user),
  214. ):
  215. """
  216. 批量重置选中用户的英文名。
  217. 规则:根据姓名生成拼音,如有重复自动追加数字后缀。
  218. 需要管理员密码验证。
  219. """
  220. if current_user.role != "SUPER_ADMIN":
  221. raise HTTPException(status_code=403, detail="权限不足")
  222. # Verify Admin Password
  223. if not security.verify_password(req.admin_password, current_user.password_hash):
  224. logger.warning(f"批量重置英文名失败: 密码错误 (Admin: {current_user.mobile})")
  225. raise HTTPException(status_code=403, detail="管理员密码错误")
  226. if not req.user_ids:
  227. raise HTTPException(status_code=400, detail="请选择用户")
  228. success_count = 0
  229. users = db.query(User).filter(User.id.in_(req.user_ids), User.is_deleted == 0).all()
  230. for user in users:
  231. if not user.name:
  232. continue
  233. old_english_name = user.english_name
  234. new_english_name = generate_english_name(user.name)
  235. # Uniqueness check
  236. if new_english_name:
  237. original_base = new_english_name
  238. counter = 1
  239. # Helper to check existence
  240. def check_exists(name, current_id):
  241. return db.query(User).filter(
  242. User.english_name == name,
  243. User.is_deleted == 0,
  244. User.id != current_id
  245. ).first()
  246. while check_exists(new_english_name, user.id):
  247. new_english_name = f"{original_base}{counter}"
  248. counter += 1
  249. if old_english_name != new_english_name:
  250. user.english_name = new_english_name
  251. db.add(user)
  252. # Log
  253. LogService.create_log(
  254. db=db,
  255. operator_id=current_user.id,
  256. action_type=ActionType.UPDATE,
  257. target_user_id=user.id,
  258. target_mobile=user.mobile,
  259. ip_address=get_client_ip(request),
  260. details={
  261. "field": "english_name",
  262. "old": old_english_name,
  263. "new": new_english_name,
  264. "reason": "BATCH_RESET"
  265. }
  266. )
  267. success_count += 1
  268. db.commit()
  269. logger.info(f"批量重置英文名完成: 成功 {success_count} 个 (Admin: {current_user.mobile})")
  270. return {"success": True, "count": success_count}
  271. @router.post("/batch/organization", summary="批量设置用户所属组织")
  272. def batch_set_organization(
  273. *,
  274. db: Session = Depends(deps.get_db),
  275. req: BatchSetOrganizationRequest,
  276. request: Request,
  277. current_user: User = Depends(deps.get_current_active_user),
  278. ):
  279. """
  280. 将选中用户设为同一组织,或取消归属(organization_id 为 null)。
  281. 需超级管理员与管理员密码验证。
  282. """
  283. if current_user.role != "SUPER_ADMIN":
  284. raise HTTPException(status_code=403, detail="权限不足")
  285. if not security.verify_password(req.admin_password, current_user.password_hash):
  286. logger.warning(f"批量设置组织失败: 密码错误 (Admin: {current_user.mobile})")
  287. raise HTTPException(status_code=403, detail="管理员密码错误")
  288. if req.organization_id is not None:
  289. org = db.query(Organization).filter(Organization.id == req.organization_id).first()
  290. if not org:
  291. raise HTTPException(status_code=400, detail="组织不存在")
  292. user_ids = list(dict.fromkeys(req.user_ids))
  293. users = db.query(User).filter(User.id.in_(user_ids), User.is_deleted == 0).all()
  294. if len(users) != len(user_ids):
  295. raise HTTPException(status_code=400, detail="部分用户不存在或已删除")
  296. for user in users:
  297. old_oid = user.organization_id
  298. user.organization_id = req.organization_id
  299. db.add(user)
  300. if old_oid != req.organization_id:
  301. LogService.create_log(
  302. db=db,
  303. operator_id=current_user.id,
  304. action_type=ActionType.UPDATE,
  305. target_user_id=user.id,
  306. target_mobile=user.mobile,
  307. ip_address=get_client_ip(request),
  308. details={
  309. "field": "organization_id",
  310. "old": old_oid,
  311. "new": req.organization_id,
  312. "reason": "BATCH_ORGANIZATION",
  313. },
  314. )
  315. db.commit()
  316. logger.info(f"批量设置组织完成: {len(users)} 个用户 (Admin: {current_user.mobile})")
  317. return {"success": True, "count": len(users)}
  318. @router.put("/{user_id}", response_model=UserSchema, summary="更新用户")
  319. def update_user(
  320. *,
  321. db: Session = Depends(deps.get_db),
  322. user_id: int,
  323. user_in: UserUpdate,
  324. request: Request,
  325. background_tasks: BackgroundTasks,
  326. current_user: User = Depends(deps.get_current_active_user),
  327. ):
  328. """
  329. 更新用户信息。超级管理员可以更新任何人,用户只能更新自己。
  330. """
  331. user = db.query(User).filter(User.id == user_id).first()
  332. if not user:
  333. raise HTTPException(status_code=404, detail="用户不存在")
  334. # Permission Check
  335. if current_user.role != "SUPER_ADMIN" and current_user.id != user_id:
  336. raise HTTPException(status_code=403, detail="权限不足")
  337. update_data = user_in.model_dump(exclude_unset=True)
  338. # Track actions for logging
  339. actions = []
  340. # Only Super Admin can change mobile
  341. if "mobile" in update_data:
  342. if current_user.role != "SUPER_ADMIN":
  343. del update_data["mobile"]
  344. else:
  345. # Require admin password for mobile change
  346. if not user_in.admin_password or not security.verify_password(user_in.admin_password, current_user.password_hash):
  347. logger.warning(f"修改用户手机号失败: 管理员密码错误 (Target: {user.mobile})")
  348. raise HTTPException(status_code=403, detail="管理员密码错误")
  349. # Check uniqueness
  350. existing_user = db.query(User).filter(User.mobile == update_data["mobile"]).first()
  351. if existing_user and existing_user.id != user_id:
  352. raise HTTPException(status_code=400, detail="该手机号已存在")
  353. if user.mobile != update_data["mobile"]:
  354. old_mobile = user.mobile
  355. new_mobile = update_data["mobile"]
  356. # Update Mappings first (logic: user mobile change should propagate to mappings)
  357. # Find mappings where mapped_key matches old_mobile
  358. mappings = db.query(AppUserMapping).filter(
  359. AppUserMapping.user_id == user.id,
  360. AppUserMapping.mapped_key == old_mobile
  361. ).all()
  362. for mapping in mappings:
  363. mapping.mapped_key = new_mobile
  364. db.add(mapping)
  365. try:
  366. db.flush()
  367. except IntegrityError:
  368. db.rollback()
  369. logger.error(f"修改手机号失败: 映射冲突 ({old_mobile} -> {new_mobile})")
  370. raise HTTPException(status_code=400, detail="修改失败:新手机号在某些应用中已存在映射关联")
  371. actions.append((ActionType.UPDATE, {"field": "mobile", "old": old_mobile, "new": new_mobile}))
  372. # Only Super Admin can change status or role
  373. if "status" in update_data:
  374. if current_user.role != "SUPER_ADMIN":
  375. del update_data["status"]
  376. elif update_data["status"] == "DISABLED" and user_id == current_user.id:
  377. raise HTTPException(status_code=400, detail="超级管理员不能禁用自己")
  378. else:
  379. # Require admin password for status change
  380. if not user_in.admin_password or not security.verify_password(user_in.admin_password, current_user.password_hash):
  381. logger.warning(f"修改用户状态失败: 密码错误")
  382. raise HTTPException(status_code=403, detail="管理员密码错误")
  383. # Add Log Action
  384. action_type = ActionType.DISABLE if update_data["status"] == "DISABLED" else ActionType.ENABLE
  385. actions.append((action_type, {"old": user.status, "new": update_data["status"]}))
  386. if "role" in update_data:
  387. if current_user.role != "SUPER_ADMIN":
  388. del update_data["role"]
  389. elif user_id == current_user.id and update_data["role"] != "SUPER_ADMIN":
  390. # Prevent demoting self
  391. raise HTTPException(status_code=400, detail="超级管理员不能降级自己")
  392. else:
  393. # Require admin password for role change
  394. if not user_in.admin_password or not security.verify_password(user_in.admin_password, current_user.password_hash):
  395. logger.warning(f"修改用户角色失败: 密码错误")
  396. raise HTTPException(status_code=403, detail="管理员密码错误")
  397. actions.append((ActionType.CHANGE_ROLE, {"old": user.role, "new": update_data["role"]}))
  398. if "organization_id" in update_data:
  399. if current_user.role != "SUPER_ADMIN":
  400. del update_data["organization_id"]
  401. else:
  402. if not user_in.admin_password or not security.verify_password(user_in.admin_password, current_user.password_hash):
  403. logger.warning(f"修改用户组织失败: 密码错误")
  404. raise HTTPException(status_code=403, detail="管理员密码错误")
  405. oid = update_data["organization_id"]
  406. if oid is not None:
  407. org = db.query(Organization).filter(Organization.id == oid).first()
  408. if not org:
  409. raise HTTPException(status_code=400, detail="组织不存在")
  410. actions.append(
  411. (
  412. ActionType.UPDATE,
  413. {"field": "organization_id", "old": user.organization_id, "new": oid},
  414. )
  415. )
  416. if "admin_password" in update_data:
  417. del update_data["admin_password"]
  418. # 删除用户请使用 POST /users/{id}/delete(密码+短信),不再接受通过更新接口软删除
  419. if "is_deleted" in update_data:
  420. del update_data["is_deleted"]
  421. if "password" in update_data:
  422. password = update_data["password"]
  423. if password:
  424. password = password.strip()
  425. hashed_password = security.get_password_hash(password)
  426. del update_data["password"]
  427. user.password_hash = hashed_password
  428. # Auto-generate english_name if name changed and english_name not provided
  429. if "name" in update_data and update_data["name"]:
  430. update_data["name"] = update_data["name"].replace(" ", "")
  431. if "english_name" not in update_data or not update_data["english_name"]:
  432. update_data["english_name"] = generate_english_name(update_data["name"])
  433. for field, value in update_data.items():
  434. setattr(user, field, value)
  435. db.add(user)
  436. # 用户禁用时关闭其全部应用映射;重新启用(含审核通过)时恢复为启用
  437. if "status" in update_data:
  438. if update_data["status"] == "DISABLED":
  439. n = (
  440. db.query(AppUserMapping)
  441. .filter(AppUserMapping.user_id == user.id)
  442. .update({"is_active": False}, synchronize_session=False)
  443. )
  444. if n:
  445. logger.info(f"用户禁用:已关闭 {n} 条应用映射 (user_id={user.id})")
  446. elif update_data["status"] == "ACTIVE":
  447. n = (
  448. db.query(AppUserMapping)
  449. .filter(AppUserMapping.user_id == user.id)
  450. .update({"is_active": True}, synchronize_session=False)
  451. )
  452. if n:
  453. logger.info(f"用户启用:已恢复 {n} 条应用映射 (user_id={user.id})")
  454. db.commit()
  455. db.refresh(user)
  456. user = (
  457. db.query(User)
  458. .options(joinedload(User.organization))
  459. .filter(User.id == user.id)
  460. .first()
  461. )
  462. # Trigger Webhook
  463. event_type = "UPDATE"
  464. if user.status == "DISABLED":
  465. event_type = "DISABLE"
  466. background_tasks.add_task(WebhookService.trigger_user_event, db, user.id, event_type)
  467. # Record Logs
  468. if current_user.role == "SUPER_ADMIN" and actions:
  469. for action_type, details in actions:
  470. LogService.create_log(
  471. db=db,
  472. operator_id=current_user.id,
  473. action_type=action_type,
  474. target_user_id=user.id,
  475. target_mobile=user.mobile,
  476. ip_address=get_client_ip(request),
  477. details=details
  478. )
  479. logger.info(f"更新用户信息成功: {user.mobile} (ID: {user.id})")
  480. return user
  481. @router.post("/{user_id}/promote", response_model=UserSchema, summary="提升用户权限")
  482. def promote_user(
  483. *,
  484. db: Session = Depends(deps.get_db),
  485. user_id: int,
  486. req: PromoteUserRequest,
  487. request: Request,
  488. current_user: User = Depends(deps.get_current_active_user),
  489. ):
  490. """
  491. 将开发者提升为超级管理员。
  492. 需要验证(管理员密码 + 验证码)。
  493. """
  494. if current_user.role != "SUPER_ADMIN":
  495. raise HTTPException(status_code=403, detail="权限不足")
  496. # 1. Verify Password
  497. if not security.verify_password(req.password, current_user.password_hash):
  498. logger.warning(f"提升权限失败: 密码错误 (Admin: {current_user.mobile})")
  499. raise HTTPException(status_code=403, detail="密码错误")
  500. # 2. Verify Captcha
  501. if not CaptchaService.verify_captcha(req.captcha_id, req.captcha_code):
  502. logger.warning(f"提升权限失败: 验证码错误")
  503. raise HTTPException(status_code=400, detail="验证码错误")
  504. user = db.query(User).filter(User.id == user_id).first()
  505. if not user:
  506. raise HTTPException(status_code=404, detail="用户不存在")
  507. old_role = user.role
  508. user.role = "SUPER_ADMIN"
  509. user.status = "ACTIVE" # Ensure they are active
  510. db.add(user)
  511. db.commit()
  512. db.refresh(user)
  513. # Log Operation
  514. LogService.create_log(
  515. db=db,
  516. operator_id=current_user.id,
  517. action_type=ActionType.CHANGE_ROLE,
  518. target_user_id=user.id,
  519. target_mobile=user.mobile,
  520. ip_address=get_client_ip(request),
  521. details={"old": old_role, "new": "SUPER_ADMIN"}
  522. )
  523. logger.info(f"提升用户为超管: {user.mobile}")
  524. return user
  525. @router.post("/{user_id}/delete", response_model=UserSchema, summary="删除普通用户(软删除)")
  526. def delete_user(
  527. *,
  528. db: Session = Depends(deps.get_db),
  529. user_id: int,
  530. body: DeleteUserRequest,
  531. request: Request,
  532. current_user: User = Depends(deps.get_current_active_user),
  533. background_tasks: BackgroundTasks,
  534. ):
  535. """
  536. 软删除普通用户(ORDINARY_USER)。仅限超级管理员。
  537. 需验证操作者登录密码与发送至操作者手机的短信验证码。不提供用户自助销户。
  538. """
  539. if current_user.role != "SUPER_ADMIN":
  540. raise HTTPException(status_code=403, detail="权限不足")
  541. if user_id == current_user.id:
  542. raise HTTPException(status_code=400, detail="不能删除自己")
  543. user = db.query(User).filter(User.id == user_id).first()
  544. if not user or user.is_deleted != 0:
  545. raise HTTPException(status_code=404, detail="用户不存在")
  546. # 管理员与开发者账号不可删除(仅允许删除普通用户)
  547. if user.role in (UserRole.SUPER_ADMIN, "SUPER_ADMIN"):
  548. raise HTTPException(status_code=403, detail="不能删除超级管理员")
  549. if user.role in (UserRole.DEVELOPER, "DEVELOPER"):
  550. raise HTTPException(status_code=403, detail="不能删除开发者")
  551. if user.role not in (UserRole.ORDINARY_USER, "ORDINARY_USER"):
  552. raise HTTPException(status_code=400, detail="仅可删除普通用户")
  553. if not security.verify_password(body.password, current_user.password_hash):
  554. logger.warning(f"删除用户失败: 密码错误 (Admin: {current_user.mobile})")
  555. raise HTTPException(status_code=403, detail="密码错误")
  556. if not SmsService.verify_code(current_user.mobile, body.sms_code):
  557. logger.warning(f"删除用户失败: 短信验证码错误 (Admin: {current_user.mobile})")
  558. raise HTTPException(status_code=400, detail="验证码错误或已过期")
  559. user.is_deleted = 1
  560. user.status = "DISABLED"
  561. db.add(user)
  562. db.commit()
  563. background_tasks.add_task(WebhookService.trigger_user_event, db, user.id, "DELETE")
  564. LogService.create_log(
  565. db=db,
  566. operator_id=current_user.id,
  567. action_type=ActionType.DELETE,
  568. target_user_id=user.id,
  569. target_mobile=user.mobile,
  570. ip_address=get_client_ip(request),
  571. details={"status": "DISABLED"}
  572. )
  573. logger.info(f"删除用户成功: {user.mobile}")
  574. return user
  575. @router.get("/me", response_model=UserSchema, summary="获取当前用户信息")
  576. def read_user_me(
  577. current_user: User = Depends(deps.get_current_active_user),
  578. db: Session = Depends(deps.get_db),
  579. ):
  580. """
  581. 获取当前登录用户的信息。
  582. """
  583. user = (
  584. db.query(User)
  585. .options(joinedload(User.organization))
  586. .filter(User.id == current_user.id)
  587. .first()
  588. )
  589. return user
  590. @router.get("/{user_id}", response_model=UserSchema, summary="根据ID获取用户")
  591. def read_user_by_id(
  592. user_id: int,
  593. current_user: User = Depends(deps.get_current_active_user),
  594. db: Session = Depends(deps.get_db),
  595. ):
  596. """
  597. 根据用户ID获取特定用户信息。
  598. """
  599. user = (
  600. db.query(User)
  601. .options(joinedload(User.organization))
  602. .filter(User.id == user_id)
  603. .first()
  604. )
  605. if not user:
  606. raise HTTPException(
  607. status_code=404,
  608. detail="该用户ID在系统中不存在",
  609. )
  610. return user