user_route.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. import random
  2. import time
  3. from datetime import datetime
  4. from zoneinfo import ZoneInfo
  5. from flask import request, jsonify, current_app
  6. from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, create_refresh_token
  7. from werkzeug.security import generate_password_hash, check_password_hash
  8. from app.constants import OperationType, UserRole, UserStatus
  9. from app.decorators import login_required
  10. from app.models import Operation, User, db
  11. from app.routes import user_routes
  12. from app.utils import is_valid_email, is_valid_avatar_file, is_valid_phone, handle_operation_failure, \
  13. handle_operation_success, handle_file_upload, get_pagination_params, adjust_page_if_needed, rate_limit, \
  14. user_rate_limit
  15. @user_routes.route('/register', methods=['POST'])
  16. def register():
  17. start_time = time.time() # 记录操作开始时间
  18. # 获取请求中的表单数据
  19. username = request.form.get('username')
  20. email = request.form.get('email')
  21. password = request.form.get('password')
  22. first_name = request.form.get('first_name', '名字')
  23. last_name = request.form.get('last_name', '姓氏')
  24. role = request.form.get('role', 'user').lower()
  25. avatar_file = request.files.get('avatar_file')
  26. phone = request.form.get('phone')
  27. # 创建一个新的操作记录
  28. new_operation = Operation(
  29. operation_type=OperationType.CREATE,
  30. description="用户注册",
  31. ip_address=request.remote_addr,
  32. device_info=request.user_agent.string,
  33. )
  34. # 根据用户名、邮箱、手机号查找用户(因为这三个字段具有唯一性)
  35. user = User.query.filter((User.username == username) | (User.email == email) | (User.phone == phone)).first()
  36. # 校验字段
  37. validation_checks = [
  38. (not username or not email or not password, "【注册失败】用户名、邮箱或密码为空", 400),
  39. (user and user.status == UserStatus.BANNED,
  40. f"【注册失败】您的账号已被封禁,如有疑问请联系管理员/开发人员", 403),
  41. (user and (user.status == UserStatus.DELETED or user.deleted_at),
  42. f"【注册失败】您的账号已注销,若要重新注册请联系管理员/开发人员", 403),
  43. (user and (user.status != UserStatus.DELETED or not user.deleted_at), f"【注册失败】您已注册过,请直接登录", 400),
  44. (not is_valid_email(email), f"【注册失败】无效的邮箱格式:{email}", 400),
  45. (role not in UserRole.list(), f"【注册失败】无效的角色:{role},只限 'admin', 'developer', 'user'", 400),
  46. (avatar_file and not is_valid_avatar_file(avatar_file), "【注册失败】头像文件不合规", 400),
  47. (phone and not is_valid_phone(phone), f"【注册失败】无效的手机号格式:{phone}", 400),
  48. ]
  49. for condition, message, code in validation_checks:
  50. if condition:
  51. new_operation = handle_operation_failure(new_operation, start_time, message)
  52. current_app.logger.warning(message)
  53. return jsonify({
  54. 'operation': new_operation.to_dict(),
  55. }), code
  56. # 新用户,创建新记录
  57. user = User(
  58. username=username,
  59. email=email,
  60. password=generate_password_hash(password),
  61. first_name=first_name,
  62. last_name=last_name,
  63. role=UserRole(role),
  64. avatar_path=handle_file_upload(avatar_file, 'avatars'),
  65. phone=phone,
  66. )
  67. db.session.add(user)
  68. db.session.commit()
  69. # 记录操作
  70. new_operation = handle_operation_success(new_operation, start_time, user.user_id)
  71. current_app.logger.info(f"【注册成功】new_user: {user}")
  72. return jsonify({
  73. 'operation': new_operation.to_dict(),
  74. 'new_user': user.to_dict(),
  75. }), 201
  76. @user_routes.route('/login', methods=['POST'])
  77. def login():
  78. start_time = time.time() # 记录操作开始时间
  79. # 获取请求中的表单数据
  80. username_or_email = request.form.get('username_or_email')
  81. password = request.form.get('password')
  82. # 创建一个新的操作记录
  83. new_operation = Operation(
  84. operation_type=OperationType.AUTHENTICATE,
  85. description="用户登录",
  86. ip_address=request.remote_addr,
  87. device_info=request.user_agent.string,
  88. )
  89. # 根据用户名或邮箱查找用户
  90. user = User.query.filter((User.username == username_or_email) | (User.email == username_or_email)).first()
  91. user_id = user.user_id if user is not None else None
  92. # 校验字段
  93. validation_checks = [
  94. (not username_or_email or not password, "【登录失败】用户名或邮箱和密码是必填项", 400),
  95. (not user, f"【登录失败】用户 {username_or_email} 尚未注册,请先注册", 400),
  96. (user and user.status == UserStatus.BANNED, f"【登录失败】您已被封禁,如有疑问请联系管理员/开发人员", 403),
  97. (user and (user.status == UserStatus.DELETED or user.deleted_at),
  98. f"【登录失败】您的账号已注销,若需重新注册请联系管理员/开发人员", 403),
  99. (user and not check_password_hash(user.password, password), "【登录失败】密码错误", 400),
  100. ]
  101. for condition, message, code in validation_checks:
  102. if condition:
  103. new_operation = handle_operation_failure(new_operation, start_time, message, user_id)
  104. current_app.logger.warning(message)
  105. return jsonify({
  106. 'operation': new_operation.to_dict(),
  107. }), code
  108. # 更新用户的最后登录时间和状态
  109. user.last_login = datetime.now(ZoneInfo("Asia/Shanghai"))
  110. user.status = UserStatus.ACTIVE
  111. db.session.commit()
  112. # 根据 user_id 创建 JWT 令牌
  113. # PyJWT 要求 JWT「sub」為字串,identity 不可傳整數
  114. uid = str(user.user_id)
  115. access_token = create_access_token(identity=uid)
  116. refresh_token = create_refresh_token(identity=uid)
  117. # 记录操作
  118. new_operation = handle_operation_success(new_operation, start_time, user.user_id)
  119. current_app.logger.info(
  120. f"【登录成功】login_user: {user}, access_token: {access_token}, refresh_token: {refresh_token}")
  121. return jsonify({
  122. 'operation': new_operation.to_dict(),
  123. 'login_user': user.to_dict(),
  124. 'access_token': access_token,
  125. 'refresh_token': refresh_token,
  126. }), 200
  127. @user_routes.route('/logout', methods=['POST'])
  128. @jwt_required()
  129. @login_required
  130. def logout():
  131. start_time = time.time() # 记录操作开始时间
  132. # 创建一个新的操作记录
  133. new_operation = Operation(
  134. operation_type=OperationType.AUTHENTICATE,
  135. description="用户登出",
  136. ip_address=request.remote_addr,
  137. device_info=request.user_agent.string,
  138. )
  139. # 获取当前用户身份(使用 access token)
  140. current_user_id = get_jwt_identity()
  141. current_user = User.query.get(current_user_id)
  142. # 更新用户的最后登录时间和状态
  143. current_user.last_login = datetime.now(ZoneInfo("Asia/Shanghai"))
  144. current_user.status = UserStatus.INACTIVE
  145. db.session.commit()
  146. # 记录操作
  147. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  148. current_app.logger.info(f"【登出成功】logout_user: {current_user}")
  149. return jsonify({
  150. 'operation': new_operation.to_dict(),
  151. 'logout_user': current_user.to_dict(),
  152. }), 200
  153. @user_routes.route('/refresh', methods=['POST'])
  154. @jwt_required(refresh=True)
  155. def refresh():
  156. start_time = time.time() # 记录操作开始时间
  157. # 创建一个新的操作记录
  158. new_operation = Operation(
  159. operation_type=OperationType.AUTHENTICATE,
  160. description="刷新用户 token",
  161. ip_address=request.remote_addr,
  162. device_info=request.user_agent.string,
  163. )
  164. # 获取当前用户身份(使用 refresh token)
  165. current_user_id = get_jwt_identity()
  166. current_user = User.query.get(current_user_id)
  167. # 生成新的 access token
  168. access_token = create_access_token(identity=str(current_user_id))
  169. # 记录操作
  170. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  171. current_app.logger.info(f"【刷新 token 成功】current_user: {current_user}, access_token: {access_token}")
  172. return jsonify({
  173. 'operation': new_operation.to_dict(),
  174. 'access_token': access_token,
  175. }), 200
  176. @user_routes.route('/profile', methods=['GET'])
  177. @jwt_required()
  178. @login_required
  179. def profile():
  180. # 获取当前用户身份(使用 access token)
  181. current_user_id = get_jwt_identity()
  182. current_user = User.query.get(current_user_id)
  183. return jsonify({
  184. 'current_user': current_user.to_dict(),
  185. }), 200
  186. @user_routes.route('/detail/<int:user_id>', methods=['GET'])
  187. @jwt_required()
  188. @login_required
  189. def detail(user_id):
  190. # 获取当前用户身份(使用 access token)
  191. current_user_id = get_jwt_identity()
  192. current_user = User.query.get(current_user_id)
  193. # 获取指定用户
  194. user = User.query.get(user_id)
  195. # 校验字段
  196. validation_checks = [
  197. (not user, f"【获取用户 ID={user_id} 详情失败】该用户不存在", 404),
  198. (user and user.user_id != current_user_id and current_user.role != UserRole.ADMIN
  199. and current_user.role != UserRole.DEVELOPER,
  200. f"【获取用户 ID={user_id} 详情失败】当前登录用户非管理员/开发人员,无权查看其他用户信息", 403),
  201. ]
  202. for condition, message, code in validation_checks:
  203. if condition:
  204. current_app.logger.warning(message + f', operator: {current_user}')
  205. return jsonify({
  206. 'failure_message': message,
  207. }), code
  208. return jsonify({
  209. 'user': user.to_dict(),
  210. }), 200
  211. @user_routes.route('/update', methods=['PUT'])
  212. @jwt_required()
  213. @login_required
  214. def update():
  215. start_time = time.time() # 记录操作开始时间
  216. # 获取请求中的更新数据
  217. username = request.form.get('username')
  218. email = request.form.get('email')
  219. first_name = request.form.get('first_name')
  220. last_name = request.form.get('last_name')
  221. avatar_file = request.files.get('avatar_file')
  222. phone = request.form.get('phone')
  223. # 创建一个新的操作记录
  224. new_operation = Operation(
  225. operation_type=OperationType.UPDATE,
  226. description="更新用户资料",
  227. ip_address=request.remote_addr,
  228. device_info=request.user_agent.string,
  229. )
  230. # 获取当前用户身份(使用 access token)
  231. current_user_id = get_jwt_identity()
  232. current_user = User.query.get(current_user_id)
  233. # 校验字段
  234. validation_checks = [
  235. (not username or not email, "【更新用户资料失败】用户名或邮箱为空", 400),
  236. (not is_valid_email(email), f"【更新用户资料失败】无效的邮箱格式:{email}", 400),
  237. (avatar_file and not is_valid_avatar_file(avatar_file), "【更新用户资料失败】头像文件类型或大小不合规", 400),
  238. (phone and not is_valid_phone(phone), f"【更新用户资料失败】无效的手机号格式:{phone}", 400),
  239. (current_user.username != username and User.query.filter_by(username=username).first(),
  240. f"【更新用户资料失败】用户名 {username} 已注册过", 400),
  241. (current_user.email != email and User.query.filter_by(email=email).first(),
  242. f"【更新用户资料失败】邮箱 {email} 已注册过", 400),
  243. (phone and current_user.phone != phone and User.query.filter_by(phone=phone).first(),
  244. f"【更新用户资料失败】手机号 {phone} 已注册过", 400),
  245. ]
  246. for condition, message, code in validation_checks:
  247. if condition:
  248. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  249. current_app.logger.warning(message + f', operator: {current_user}')
  250. return jsonify({
  251. 'operation': new_operation.to_dict(),
  252. }), code
  253. # 更新用户信息
  254. current_user.username = username
  255. current_user.email = email
  256. current_user.first_name = first_name if first_name else current_user.first_name
  257. current_user.last_name = last_name if last_name else current_user.last_name
  258. if avatar_file: # 如果有头像文件,则上传并更新头像路径;如果没有,则说明用户不需要更新头像
  259. current_user.avatar_path = handle_file_upload(avatar_file, 'avatars')
  260. current_user.phone = phone if phone else current_user.phone
  261. db.session.commit()
  262. # 记录操作
  263. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  264. current_app.logger.info(f"【更新用户资料成功】updated_user: {current_user}")
  265. return jsonify({
  266. 'operation': new_operation.to_dict(),
  267. 'updated_user': current_user.to_dict(),
  268. }), 200
  269. @user_routes.route('/update/<int:user_id>', methods=['PUT'])
  270. @jwt_required()
  271. @login_required
  272. def update_user(user_id):
  273. start_time = time.time() # 记录操作开始时间
  274. # 获取请求中的更新数据
  275. username = request.form.get('username')
  276. email = request.form.get('email')
  277. password = request.form.get('password')
  278. first_name = request.form.get('first_name')
  279. last_name = request.form.get('last_name')
  280. role = request.form.get('role').lower()
  281. avatar_file = request.files.get('avatar_file')
  282. phone = request.form.get('phone')
  283. # 创建一个新的操作记录
  284. new_operation = Operation(
  285. operation_type=OperationType.UPDATE,
  286. description=f"更新用户 ID={user_id} 资料",
  287. ip_address=request.remote_addr,
  288. device_info=request.user_agent.string,
  289. )
  290. # 获取当前用户身份(使用 access token)
  291. current_user_id = get_jwt_identity()
  292. current_user = User.query.get(current_user_id)
  293. # 获取指定用户
  294. updated_user = User.query.get(user_id)
  295. # 校验字段
  296. validation_checks = [
  297. (current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER,
  298. f"【更新用户 ID={user_id} 资料失败】您非管理员/开发人员,无权修改其他用户信息", 403),
  299. (not username or not email or not role, f"【更新用户 ID={user_id} 资料失败】用户名、邮箱或角色为空", 400),
  300. (not is_valid_email(email), f"【更新用户 ID={user_id} 资料失败】无效的邮箱格式:{email}", 400),
  301. (avatar_file and not is_valid_avatar_file(avatar_file),
  302. f"【更新用户 ID={user_id} 资料失败】头像文件类型或大小不合规", 400),
  303. (phone and not is_valid_phone(phone), f"【更新用户 ID={user_id} 资料失败】无效的手机号格式:{phone}", 400),
  304. (not updated_user, f"【更新用户 ID={user_id} 资料失败】该用户不存在", 404),
  305. (updated_user.username != username and User.query.filter_by(username=username).first(),
  306. f"【更新用户 ID={user_id} 资料失败】用户名 {username} 已注册过", 400),
  307. (updated_user.email != email and User.query.filter_by(email=email).first(),
  308. f"【更新用户 ID={user_id} 资料失败】邮箱 {email} 已注册过", 400),
  309. (phone and updated_user.phone != phone and User.query.filter_by(phone=phone).first(),
  310. f"【更新用户 ID={user_id} 资料失败】手机号 {phone} 已注册过", 400),
  311. ]
  312. for condition, message, code in validation_checks:
  313. if condition:
  314. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  315. current_app.logger.warning(message + f', operator: {current_user}')
  316. return jsonify({
  317. 'operation': new_operation.to_dict(),
  318. }), code
  319. # 更新用户信息
  320. updated_user.username = username
  321. updated_user.email = email
  322. if password: # 如果有密码,则更新密码;如果没有,则说明用户不需要更新密码
  323. updated_user.password = generate_password_hash(password)
  324. updated_user.first_name = first_name if first_name else updated_user.first_name
  325. updated_user.last_name = last_name if last_name else updated_user.last_name
  326. updated_user.role = UserRole(role)
  327. if avatar_file: # 如果有头像文件,则上传并更新头像路径;如果没有,则说明用户不需要更新头像
  328. updated_user.avatar_path = handle_file_upload(avatar_file, 'avatars')
  329. updated_user.phone = phone if phone else updated_user.phone
  330. db.session.commit()
  331. # 记录操作
  332. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  333. current_app.logger.info(f"【更新用户 ID={user_id} 资料成功】updated_user: {updated_user}, operator: {current_user}")
  334. return jsonify({
  335. 'operation': new_operation.to_dict(),
  336. 'updated_user': updated_user.to_dict(),
  337. }), 200
  338. @user_routes.route('/change_password', methods=['PUT'])
  339. @jwt_required()
  340. @login_required
  341. def change_password():
  342. start_time = time.time() # 记录操作开始时间
  343. # 获取请求中的当前密码和新密码
  344. current_password = request.form.get('current_password')
  345. new_password = request.form.get('new_password')
  346. # 创建一个新的操作记录
  347. new_operation = Operation(
  348. operation_type=OperationType.UPDATE,
  349. description="修改密码",
  350. ip_address=request.remote_addr,
  351. device_info=request.user_agent.string,
  352. )
  353. # 获取当前用户身份(使用 access token)
  354. current_user_id = get_jwt_identity()
  355. current_user = User.query.get(current_user_id)
  356. # 校验字段
  357. validation_checks = [
  358. (not current_password or not new_password, "【修改密码失败】当前密码或新密码为空", 400),
  359. (not check_password_hash(current_user.password, current_password), "【修改密码失败】当前密码错误", 400),
  360. ]
  361. for condition, message, code in validation_checks:
  362. if condition:
  363. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  364. current_app.logger.warning(message + f', operator: {current_user}')
  365. return jsonify({
  366. 'operation': new_operation.to_dict(),
  367. }), code
  368. # 更新密码
  369. current_user.password = generate_password_hash(new_password)
  370. db.session.commit()
  371. # 记录操作
  372. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  373. current_app.logger.info(f"【修改密码成功】current_user: {current_user}, old_password: {current_password}")
  374. return jsonify({
  375. 'operation': new_operation.to_dict(),
  376. 'current_user': current_user.to_dict(),
  377. "old_password": current_password,
  378. }), 200
  379. @user_routes.route('/delete', methods=['DELETE'])
  380. @jwt_required()
  381. @login_required
  382. def delete():
  383. start_time = time.time() # 记录操作开始时间
  384. # 创建一个新的操作记录
  385. new_operation = Operation(
  386. operation_type=OperationType.DELETE,
  387. description="注销账户",
  388. ip_address=request.remote_addr,
  389. device_info=request.user_agent.string,
  390. )
  391. # 获取当前用户身份(使用 access token)
  392. current_user_id = get_jwt_identity()
  393. current_user = User.query.get(current_user_id)
  394. # 软删除用户
  395. current_user.deleted_at = datetime.now(ZoneInfo("Asia/Shanghai"))
  396. current_user.status = UserStatus.DELETED
  397. db.session.commit()
  398. # 记录操作
  399. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  400. current_app.logger.info(f"【注销账户成功】deleted_user: {current_user}")
  401. return jsonify({
  402. 'operation': new_operation.to_dict(),
  403. 'deleted_user': current_user.to_dict(),
  404. }), 200
  405. @user_routes.route('/delete/<int:user_id>', methods=['DELETE'])
  406. @jwt_required()
  407. @login_required
  408. def delete_user(user_id):
  409. start_time = time.time() # 记录操作开始时间
  410. # 创建一个新的操作记录
  411. new_operation = Operation(
  412. operation_type=OperationType.DELETE,
  413. description=f"注销用户 ID={user_id}",
  414. ip_address=request.remote_addr,
  415. device_info=request.user_agent.string,
  416. )
  417. # 获取当前用户身份(使用 access token)
  418. current_user_id = get_jwt_identity()
  419. current_user = User.query.get(current_user_id)
  420. # 获取指定用户
  421. deleted_user = User.query.get(user_id)
  422. # 校验字段
  423. validation_checks = [
  424. (current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER,
  425. f"【注销用户 ID={user_id} 失败】您非管理员/开发人员,无权注销其他用户", 403),
  426. (not deleted_user, f"【注销用户 ID={user_id} 失败】该用户不存在", 404),
  427. (current_user.role != UserRole.DEVELOPER and (deleted_user.role == UserRole.ADMIN
  428. or deleted_user.role == UserRole.DEVELOPER),
  429. f"【注销用户 ID={user_id} 失败】您非开发人员,无权注销管理员/开发人员", 400),
  430. (deleted_user.status == UserStatus.DELETED or deleted_user.deleted_at,
  431. f"【注销用户 ID={user_id} 失败】该用户已注销", 400),
  432. ]
  433. for condition, message, code in validation_checks:
  434. if condition:
  435. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  436. current_app.logger.warning(message + f', operator: {current_user}')
  437. return jsonify({
  438. 'operation': new_operation.to_dict(),
  439. }), code
  440. # 软删除用户
  441. deleted_user.deleted_at = datetime.now(ZoneInfo("Asia/Shanghai"))
  442. deleted_user.status = UserStatus.DELETED
  443. db.session.commit()
  444. # 记录操作
  445. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  446. current_app.logger.info(f"【注销用户 ID={user_id} 成功】deleted_user: {deleted_user}, operator: {current_user}")
  447. return jsonify({
  448. 'operation': new_operation.to_dict(),
  449. 'deleted_user': deleted_user.to_dict(),
  450. }), 200
  451. @user_routes.route('/undelete/<int:user_id>', methods=['PUT'])
  452. @jwt_required()
  453. @login_required
  454. def undelete_user(user_id):
  455. start_time = time.time() # 记录操作开始时间
  456. # 创建一个新的操作记录
  457. new_operation = Operation(
  458. operation_type=OperationType.DELETE,
  459. description=f"恢复注销用户 ID={user_id}",
  460. ip_address=request.remote_addr,
  461. device_info=request.user_agent.string,
  462. )
  463. # 获取当前用户身份(使用 access token)
  464. current_user_id = get_jwt_identity()
  465. current_user = User.query.get(current_user_id)
  466. # 获取指定用户
  467. undeleted_user = User.query.get(user_id)
  468. # 校验字段
  469. validation_checks = [
  470. (current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER,
  471. f"【恢复注销用户 ID={user_id} 失败】您非管理员/开发人员,无权恢复注销其他用户", 403),
  472. (not undeleted_user, f"【恢复注销用户 ID={user_id} 失败】该用户不存在", 404),
  473. (current_user.role != UserRole.DEVELOPER and (undeleted_user.role == UserRole.ADMIN
  474. or undeleted_user.role == UserRole.DEVELOPER),
  475. f"【恢复注销用户 ID={user_id} 失败】您非开发人员,无权恢复注销管理员/开发人员", 400),
  476. (undeleted_user.status != UserStatus.DELETED or not undeleted_user.deleted_at,
  477. f"【恢复注销用户 ID={user_id} 失败】该用户未注销", 400),
  478. ]
  479. for condition, message, code in validation_checks:
  480. if condition:
  481. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  482. current_app.logger.warning(message + f', operator: {current_user}')
  483. return jsonify({
  484. 'operation': new_operation.to_dict(),
  485. }), code
  486. # 恢复注销用户
  487. undeleted_user.deleted_at = None
  488. undeleted_user.status = UserStatus.INACTIVE
  489. db.session.commit()
  490. # 记录操作
  491. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  492. current_app.logger.info(
  493. f"【恢复注销用户 ID={user_id} 成功】undeleted_user: {undeleted_user}, operator: {current_user}")
  494. return jsonify({
  495. 'operation': new_operation.to_dict(),
  496. 'undeleted_user': undeleted_user.to_dict(),
  497. }), 200
  498. @user_routes.route('/ban/<int:user_id>', methods=['PUT'])
  499. @jwt_required()
  500. @login_required
  501. def ban(user_id):
  502. start_time = time.time() # 记录操作开始时间
  503. # 创建一个新的操作记录
  504. new_operation = Operation(
  505. operation_type=OperationType.UPDATE,
  506. description=f"封禁用户 ID={user_id}",
  507. ip_address=request.remote_addr,
  508. device_info=request.user_agent.string,
  509. )
  510. # 获取当前用户身份(使用 access token)
  511. current_user_id = get_jwt_identity()
  512. current_user = User.query.get(current_user_id)
  513. # 获取指定用户
  514. baned_user = User.query.get(user_id)
  515. # 校验字段
  516. validation_checks = [
  517. (current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER,
  518. f"【封禁用户 ID={user_id} 失败】您非管理员/开发人员,无权封禁其他用户", 403),
  519. (not baned_user, f"【封禁用户 ID={user_id} 失败】该用户不存在", 404),
  520. (current_user.role != UserRole.DEVELOPER and (baned_user.role == UserRole.ADMIN
  521. or baned_user.role == UserRole.DEVELOPER),
  522. f"【封禁用户 ID={user_id} 失败】您非开发人员,无权封禁管理员/开发人员", 400),
  523. (baned_user.status == UserStatus.BANNED, f"【封禁用户 ID={user_id} 失败】该用户已被封禁", 400),
  524. (baned_user.status == UserStatus.DELETED, f"【封禁用户 ID={user_id} 失败】该用户已注销", 400),
  525. ]
  526. for condition, message, code in validation_checks:
  527. if condition:
  528. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  529. current_app.logger.warning(message + f', operator: {current_user}')
  530. return jsonify({
  531. 'operation': new_operation.to_dict(),
  532. }), code
  533. # 封禁用户
  534. baned_user.status = UserStatus.BANNED
  535. db.session.commit()
  536. # 记录操作
  537. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  538. current_app.logger.info(f"【封禁用户 ID={user_id} 成功】baned_user: {baned_user}, operator: {current_user}")
  539. return jsonify({
  540. 'operation': new_operation.to_dict(),
  541. 'baned_user': baned_user.to_dict(),
  542. }), 200
  543. @user_routes.route('/unban/<int:user_id>', methods=['PUT'])
  544. @jwt_required()
  545. @login_required
  546. def unban(user_id):
  547. start_time = time.time() # 记录操作开始时间
  548. # 创建一个新的操作记录
  549. new_operation = Operation(
  550. operation_type=OperationType.UPDATE,
  551. description=f"解封用户 ID={user_id}",
  552. ip_address=request.remote_addr,
  553. device_info=request.user_agent.string,
  554. )
  555. # 获取当前用户身份(使用 access token)
  556. current_user_id = get_jwt_identity()
  557. current_user = User.query.get(current_user_id)
  558. # 获取指定用户
  559. unbaned_user = User.query.get(user_id)
  560. # 校验字段
  561. validation_checks = [
  562. (current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER,
  563. f"【解封用户 ID={user_id} 失败】您非管理员/开发人员,无权解封其他用户", 403),
  564. (not unbaned_user, f"【解封用户 ID={user_id} 失败】该用户不存在", 404),
  565. (current_user.role != UserRole.DEVELOPER and (unbaned_user.role == UserRole.ADMIN
  566. or unbaned_user.role == UserRole.DEVELOPER),
  567. f"【解封用户 ID={user_id} 失败】您非开发人员,无权解封管理员/开发人员", 400),
  568. (unbaned_user.status != UserStatus.BANNED, f"【解封用户 ID={user_id} 失败】该用户未被封禁", 400),
  569. (unbaned_user.status == UserStatus.DELETED, f"【解封用户 ID={user_id} 失败】该用户已注销", 400),
  570. ]
  571. for condition, message, code in validation_checks:
  572. if condition:
  573. new_operation = handle_operation_failure(new_operation, start_time, message, current_user_id)
  574. current_app.logger.warning(message + f', operator: {current_user}')
  575. return jsonify({
  576. 'operation': new_operation.to_dict(),
  577. }), code
  578. # 解封用户
  579. unbaned_user.status = UserStatus.INACTIVE
  580. db.session.commit()
  581. # 记录操作
  582. new_operation = handle_operation_success(new_operation, start_time, current_user_id)
  583. current_app.logger.info(f"【解封用户 ID={user_id} 成功】unbaned_user: {unbaned_user}, operator: {current_user}")
  584. return jsonify({
  585. 'operation': new_operation.to_dict(),
  586. 'unbaned_user': unbaned_user.to_dict(),
  587. }), 200
  588. @user_routes.route('/users/all', methods=['GET'])
  589. @jwt_required()
  590. @login_required
  591. def all_users():
  592. # 获取分页参数(从请求中获取,默认为第 1 页,每页 5 条记录)
  593. default_page = request.args.get('page', 1, type=int)
  594. default_per_page = request.args.get('per_page', 5, type=int)
  595. page, per_page = get_pagination_params(default_page, default_per_page)
  596. # 获取当前用户身份(使用 access token)
  597. current_user_id = get_jwt_identity()
  598. current_user = User.query.get(current_user_id)
  599. if current_user.role != UserRole.ADMIN and current_user.role != UserRole.DEVELOPER:
  600. failure_message = f"【获取所有用户失败】您非管理员/开发人员,权限不足"
  601. current_app.logger.warning(failure_message + f', operator: {current_user}')
  602. return jsonify({
  603. 'failure_message': failure_message,
  604. }), 403
  605. # 查询所有用户
  606. query = User.query.order_by(User.user_id.asc())
  607. page, users_total, pages = adjust_page_if_needed(query, page, per_page)
  608. users = query.paginate(page=page, per_page=per_page, error_out=False)
  609. current_app.logger.info(
  610. f"【获取所有用户成功】total: {users_total}, per_page: {per_page}, page: {page}, pages: {pages}, users: {[user for user in users]}, operator: {current_user}")
  611. return jsonify({
  612. 'users': [user.to_dict() for user in users],
  613. 'total': users_total,
  614. 'per_page': per_page,
  615. 'page': page,
  616. 'pages': pages,
  617. }), 200
  618. @user_routes.route('/admin_info', methods=['GET'])
  619. def get_admin_info():
  620. # 查询所有管理员用户
  621. admins = User.query.filter(User.role == UserRole.ADMIN, User.status != UserStatus.BANNED,
  622. User.status != UserStatus.DELETED).all()
  623. # 如果没有管理员,返回 None
  624. if not admins:
  625. return jsonify({
  626. 'admin_info': None,
  627. }), 200
  628. # 随机选择一个管理员
  629. random_admin = random.choice(admins)
  630. # 提取管理员的基本信息(只包含必要的联系信息)
  631. admin_info = {
  632. 'username': random_admin.username,
  633. 'email': random_admin.email,
  634. 'phone': random_admin.phone,
  635. 'role': random_admin.role.name,
  636. 'first_name': random_admin.first_name,
  637. 'last_name': random_admin.last_name,
  638. }
  639. # 将单个管理员信息放入列表中返回,保持 API 兼容性
  640. return jsonify({
  641. 'admin_info': admin_info,
  642. }), 200
  643. @user_routes.route('/developer_info', methods=['GET'])
  644. def get_developer_info():
  645. # 查询所有开发人员用户
  646. developers = User.query.filter(User.role == UserRole.DEVELOPER, User.status != UserStatus.BANNED,
  647. User.status != UserStatus.DELETED).all()
  648. # 如果没有开发人员,返回 None
  649. if not developers:
  650. return jsonify({
  651. 'developer_info': None,
  652. }), 200
  653. # 随机选择一个开发人员
  654. random_developer = random.choice(developers)
  655. # 提取开发人员的基本信息(只包含必要的联系信息)
  656. developer_info = {
  657. 'username': random_developer.username,
  658. 'email': random_developer.email,
  659. 'phone': random_developer.phone,
  660. 'role': random_developer.role.name,
  661. 'first_name': random_developer.first_name,
  662. 'last_name': random_developer.last_name,
  663. }
  664. # 将单个开发人员信息放入列表中返回,保持 API 兼容性
  665. return jsonify({
  666. 'developer_info': developer_info,
  667. }), 200
  668. @user_routes.route('/statistics', methods=['GET'])
  669. @jwt_required()
  670. @login_required
  671. def statistics():
  672. # 查询用户总数(不包括软删除的用户)
  673. total_users = User.query.filter(User.status != UserStatus.DELETED).count()
  674. # 查询不同角色的用户数量(不包括软删除的用户)
  675. admin_users = User.query.filter(User.role == UserRole.ADMIN, User.status != UserStatus.DELETED).count()
  676. developer_users = User.query.filter(User.role == UserRole.DEVELOPER, User.status != UserStatus.DELETED).count()
  677. normal_users = User.query.filter(User.role == UserRole.USER, User.status != UserStatus.DELETED).count()
  678. # 构建统计数据
  679. users_statistics = {
  680. 'total': total_users,
  681. 'admin': admin_users,
  682. 'developer': developer_users,
  683. 'user': normal_users,
  684. }
  685. return jsonify({
  686. "users_statistics": users_statistics,
  687. }), 200