service.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763
  1. import io
  2. from typing import Any
  3. import pandas as pd
  4. from fastapi import UploadFile
  5. from app.api.v1.module_system.auth.schema import AuthSchema, SmsCodeSchema
  6. from app.api.v1.module_system.dept.crud import DeptCRUD
  7. from app.api.v1.module_system.menu.crud import MenuCRUD
  8. from app.api.v1.module_system.menu.schema import MenuOutSchema
  9. from app.api.v1.module_system.position.crud import PositionCRUD
  10. from app.api.v1.module_system.role.crud import RoleCRUD
  11. from app.core.base_schema import BatchSetAvailable, UploadResponseSchema
  12. from app.core.exceptions import CustomException
  13. from app.core.logger import log
  14. from app.utils.common_util import traversal_to_tree
  15. from app.utils.excel_util import ExcelUtil
  16. from app.utils.hash_bcrpy_util import PwdUtil
  17. from app.utils.upload_util import UploadUtil
  18. from redis.asyncio.client import Redis
  19. from .crud import UserCRUD
  20. from .schema import (
  21. CurrentUserUpdateSchema,
  22. ResetPasswordSchema,
  23. UserChangePasswordSchema,
  24. UserCreateSchema,
  25. UserForgetPasswordSchema,
  26. UserOutSchema,
  27. UserQueryParam,
  28. UserRegisterSchema,
  29. UserUpdateSchema,
  30. )
  31. from ..auth.service import SmsCodeService
  32. from ..tenant.schema import TenantCreateSchema
  33. from ..tenant.service import TenantService
  34. class UserService:
  35. """用户模块服务层"""
  36. @classmethod
  37. async def get_detail_by_id_service(cls, auth: AuthSchema, id: int) -> dict:
  38. """
  39. 根据ID获取用户详情
  40. 参数:
  41. - auth (AuthSchema): 认证信息模型
  42. - id (int): 用户ID
  43. 返回:
  44. - dict: 用户详情字典
  45. """
  46. user = await UserCRUD(auth).get_by_id_crud(id=id)
  47. if not user:
  48. raise CustomException(msg="用户不存在")
  49. # 如果用户绑定了部门,则获取部门名称
  50. if user.dept_id:
  51. dept = await DeptCRUD(auth).get_by_id_crud(id=user.dept_id)
  52. UserOutSchema.dept_name = dept.name if dept else None
  53. else:
  54. UserOutSchema.dept_name = None
  55. return UserOutSchema.model_validate(user).model_dump()
  56. @classmethod
  57. async def get_user_list_service(
  58. cls,
  59. auth: AuthSchema,
  60. search: UserQueryParam | None = None,
  61. order_by: list[dict[str, str]] | None = None,
  62. ) -> list[dict]:
  63. """
  64. 获取用户列表
  65. 参数:
  66. - auth (AuthSchema): 认证信息模型
  67. - search (UserQueryParam | None): 查询参数对象。
  68. - order_by (list[dict[str, str]] | None): 排序参数列表。
  69. 返回:
  70. - list[dict]: 用户详情字典列表
  71. """
  72. user_list = await UserCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
  73. user_dict_list = []
  74. for user in user_list:
  75. user_dict = UserOutSchema.model_validate(user).model_dump()
  76. user_dict_list.append(user_dict)
  77. return user_dict_list
  78. @classmethod
  79. async def get_user_page_service(
  80. cls,
  81. auth: AuthSchema,
  82. page_no: int,
  83. page_size: int,
  84. search: UserQueryParam | None = None,
  85. order_by: list[dict[str, str]] | None = None,
  86. ) -> dict:
  87. """
  88. 分页查询用户(数据库 OFFSET/LIMIT)。
  89. 参数:
  90. - auth (AuthSchema): 认证信息模型
  91. - page_no (int): 页码(从 1 开始)
  92. - page_size (int): 每页条数
  93. - search (UserQueryParam | None): 查询条件
  94. - order_by (list[dict[str, str]] | None): 排序字段列表
  95. 返回:
  96. - dict: 分页结果(结构由 `CRUD.page` 返回约定)
  97. """
  98. offset = (page_no - 1) * page_size
  99. return await UserCRUD(auth).page(
  100. offset=offset,
  101. limit=page_size,
  102. order_by=order_by or [{"id": "asc"}],
  103. search=search.__dict__ if search else {},
  104. out_schema=UserOutSchema,
  105. )
  106. @classmethod
  107. async def create_user_service(cls, data: UserCreateSchema, auth: AuthSchema) -> dict:
  108. """
  109. 创建用户
  110. 参数:
  111. - data (UserCreateSchema): 用户创建信息
  112. - auth (AuthSchema): 认证信息模型
  113. 返回:
  114. - dict: 创建后的用户详情字典
  115. """
  116. if not data.username:
  117. raise CustomException(msg="用户名不能为空")
  118. # 检查是否试图创建超级管理员
  119. if data.is_superuser:
  120. raise CustomException(msg="不允许创建超级管理员")
  121. # 检查用户名是否存在
  122. user = await UserCRUD(auth).get_by_username_crud(username=data.username)
  123. if user:
  124. raise CustomException(msg="已存在相同用户名称的账号")
  125. # 检查部门是否存在
  126. if data.dept_id:
  127. dept = await DeptCRUD(auth).get_by_id_crud(id=data.dept_id)
  128. if not dept:
  129. raise CustomException(msg="部门不存在")
  130. # 创建用户
  131. if data.password:
  132. data.password = PwdUtil.set_password_hash(password=data.password)
  133. user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"})
  134. # 创建用户
  135. new_user = await UserCRUD(auth).create(data=user_dict)
  136. # 设置角色
  137. if data.role_ids and len(data.role_ids) > 0:
  138. await UserCRUD(auth).set_user_roles_crud(user_ids=[new_user.id], role_ids=data.role_ids)
  139. # 设置岗位
  140. if data.position_ids and len(data.position_ids) > 0:
  141. await UserCRUD(auth).set_user_positions_crud(
  142. user_ids=[new_user.id], position_ids=data.position_ids
  143. )
  144. new_user_dict = UserOutSchema.model_validate(new_user).model_dump()
  145. return new_user_dict
  146. @classmethod
  147. async def update_user_service(cls, id: int, data: UserUpdateSchema, auth: AuthSchema) -> dict:
  148. """
  149. 更新用户
  150. 参数:
  151. - id (int): 用户ID
  152. - data (UserUpdateSchema): 用户更新信息
  153. - auth (AuthSchema): 认证信息模型
  154. 返回:
  155. - Dict: 更新后的用户详情字典
  156. """
  157. if not data.username:
  158. raise CustomException(msg="账号不能为空")
  159. # 检查用户是否存在
  160. user = await UserCRUD(auth).get_by_id_crud(id=id)
  161. if not user:
  162. raise CustomException(msg="用户不存在")
  163. # 检查是否尝试修改超级管理员
  164. if user.is_superuser:
  165. raise CustomException(msg="超级管理员不允许修改")
  166. # 检查用户名是否重复
  167. exist_user = await UserCRUD(auth).get_by_username_crud(username=data.username)
  168. if exist_user and exist_user.id != id:
  169. raise CustomException(msg="已存在相同的账号")
  170. # 新增:检查手机号是否重复
  171. if data.mobile:
  172. exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
  173. if exist_mobile_user and exist_mobile_user.id != id:
  174. raise CustomException(msg="更新失败,手机号已存在")
  175. # 新增:检查邮箱是否重复
  176. if data.email:
  177. exist_email_user = await UserCRUD(auth).get(email=data.email)
  178. if exist_email_user and exist_email_user.id != id:
  179. raise CustomException(msg="更新失败,邮箱已存在")
  180. # 检查部门是否存在且可用
  181. if data.dept_id:
  182. dept = await DeptCRUD(auth).get_by_id_crud(id=data.dept_id)
  183. if not dept:
  184. raise CustomException(msg="部门不存在")
  185. if dept.status == "1":
  186. raise CustomException(msg="部门已被禁用")
  187. # 更新用户 - 排除不应被修改的字段, 更新不更新密码
  188. user_dict = data.model_dump(
  189. exclude_unset=True,
  190. exclude={"role_ids", "position_ids", "last_login", "password"},
  191. )
  192. new_user = await UserCRUD(auth).update(id=id, data=user_dict)
  193. # 更新角色和岗位
  194. if data.role_ids and len(data.role_ids) > 0:
  195. # 检查角色是否都存在且可用
  196. roles = await RoleCRUD(auth).get_list_crud(search={"id": ("in", data.role_ids)})
  197. if len(roles) != len(data.role_ids):
  198. raise CustomException(msg="部分角色不存在")
  199. if not all(role.status for role in roles):
  200. raise CustomException(msg="部分角色已被禁用")
  201. await UserCRUD(auth).set_user_roles_crud(user_ids=[id], role_ids=data.role_ids)
  202. if data.position_ids and len(data.position_ids) > 0:
  203. # 检查岗位是否都存在且可用
  204. positions = await PositionCRUD(auth).get_list_crud(
  205. search={"id": ("in", data.position_ids)}
  206. )
  207. if len(positions) != len(data.position_ids):
  208. raise CustomException(msg="部分岗位不存在")
  209. if not all(position.status for position in positions):
  210. raise CustomException(msg="部分岗位已被禁用")
  211. await UserCRUD(auth).set_user_positions_crud(
  212. user_ids=[id], position_ids=data.position_ids
  213. )
  214. user_dict = UserOutSchema.model_validate(new_user).model_dump()
  215. return user_dict
  216. @classmethod
  217. async def delete_user_service(cls, auth: AuthSchema, ids: list[int]) -> None:
  218. """
  219. 删除用户
  220. 参数:
  221. - auth (AuthSchema): 认证信息模型
  222. - ids (list[int]): 用户ID列表
  223. 返回:
  224. - None
  225. """
  226. if len(ids) < 1:
  227. raise CustomException(msg="删除失败,删除对象不能为空")
  228. for id in ids:
  229. user = await UserCRUD(auth).get_by_id_crud(id=id)
  230. if not user:
  231. raise CustomException(msg="用户不存在")
  232. if user.is_superuser:
  233. raise CustomException(msg="超级管理员不能删除")
  234. if user.status == "0":
  235. raise CustomException(msg="用户已启用,不能删除")
  236. if auth.user and auth.user.id == id:
  237. raise CustomException(msg="不能删除当前登陆用户")
  238. # 删除用户角色关联数据
  239. await UserCRUD(auth).set_user_roles_crud(user_ids=ids, role_ids=[])
  240. # 删除用户岗位关联数据
  241. await UserCRUD(auth).set_user_positions_crud(user_ids=ids, position_ids=[])
  242. # 删除用户
  243. await UserCRUD(auth).delete(ids=ids)
  244. @classmethod
  245. async def get_current_user_info_service(cls, auth: AuthSchema) -> dict:
  246. """
  247. 获取当前用户信息
  248. 参数:
  249. - auth (AuthSchema): 认证信息模型
  250. 返回:
  251. - Dict: 当前用户详情字典
  252. """
  253. # 获取用户基本信息
  254. if not auth.user or not auth.user.id:
  255. raise CustomException(msg="用户不存在")
  256. user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id)
  257. # 获取部门名称
  258. if user and user.dept:
  259. UserOutSchema.dept_name = user.dept.name
  260. user_dict = UserOutSchema.model_validate(user).model_dump()
  261. # 获取菜单权限
  262. if auth.user and auth.user.is_superuser:
  263. # 使用树形结构查询,预加载children关系
  264. menu_all = await MenuCRUD(auth).get_tree_list_crud(
  265. search={"type": ("in", [1, 2, 4]), "status": "0"},
  266. order_by=[{"order": "asc"}],
  267. )
  268. menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all]
  269. else:
  270. # 收集用户所有角色的菜单ID,使用列表推导式优化代码
  271. menu_ids = {
  272. menu.id
  273. for role in auth.user.roles or []
  274. for menu in role.menus
  275. if menu.status == "0" and menu.type in [1, 2, 4]
  276. }
  277. # 使用树形结构查询,预加载children关系
  278. menus = (
  279. [
  280. MenuOutSchema.model_validate(menu).model_dump()
  281. for menu in await MenuCRUD(auth).get_tree_list_crud(
  282. search={"id": ("in", list(menu_ids))},
  283. order_by=[{"order": "asc"}],
  284. )
  285. ]
  286. if menu_ids
  287. else []
  288. )
  289. user_dict["menus"] = traversal_to_tree(menus)
  290. # 转换外链菜单(type=4)的路由数据
  291. from app.api.v1.module_system.menu.service import _transform_external_menus
  292. _transform_external_menus(user_dict["menus"])
  293. return user_dict
  294. @classmethod
  295. async def update_current_user_info_service(
  296. cls, auth: AuthSchema, data: CurrentUserUpdateSchema
  297. ) -> dict:
  298. """
  299. 更新当前用户信息
  300. 参数:
  301. - auth (AuthSchema): 认证信息模型
  302. - data (CurrentUserUpdateSchema): 当前用户更新信息
  303. 返回:
  304. - Dict: 更新后的当前用户详情字典
  305. """
  306. if not auth.user or not auth.user.id:
  307. raise CustomException(msg="用户不存在")
  308. user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id)
  309. if not user:
  310. raise CustomException(msg="用户不存在")
  311. if user.is_superuser:
  312. raise CustomException(msg="超级管理员不能修改个人信息")
  313. # 新增:检查手机号是否重复
  314. if data.mobile:
  315. exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
  316. if exist_mobile_user and exist_mobile_user.id != auth.user.id:
  317. raise CustomException(msg="更新失败,手机号已存在")
  318. # 新增:检查邮箱是否重复
  319. if data.email:
  320. exist_email_user = await UserCRUD(auth).get(email=data.email)
  321. if exist_email_user and exist_email_user.id != auth.user.id:
  322. raise CustomException(msg="更新失败,邮箱已存在")
  323. user_update_data = UserUpdateSchema(**data.model_dump())
  324. new_user = await UserCRUD(auth).update(id=auth.user.id, data=user_update_data)
  325. return UserOutSchema.model_validate(new_user).model_dump()
  326. @classmethod
  327. async def set_user_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
  328. """
  329. 设置用户状态
  330. 参数:
  331. - auth (AuthSchema): 认证信息模型
  332. - data (BatchSetAvailable): 批量设置用户状态数据
  333. 返回:
  334. - None
  335. """
  336. for id in data.ids:
  337. user = await UserCRUD(auth).get_by_id_crud(id=id)
  338. if not user:
  339. raise CustomException(msg=f"用户ID {id} 不存在")
  340. if user.is_superuser:
  341. raise CustomException(msg="超级管理员状态不能修改")
  342. await UserCRUD(auth).set_available_crud(ids=data.ids, status=data.status)
  343. @classmethod
  344. async def upload_avatar_service(cls, base_url: str, file: UploadFile) -> dict:
  345. """
  346. 上传用户头像
  347. 参数:
  348. - base_url (str): 基础URL
  349. - file (UploadFile): 上传的文件
  350. 返回:
  351. - Dict: 上传头像响应字典
  352. """
  353. filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
  354. return UploadResponseSchema(
  355. file_path=f"{filepath}",
  356. file_name=filename,
  357. origin_name=file.filename,
  358. file_url=f"{file_url}",
  359. ).model_dump()
  360. @classmethod
  361. async def change_user_password_service(
  362. cls, auth: AuthSchema, data: UserChangePasswordSchema
  363. ) -> dict:
  364. """
  365. 修改用户密码
  366. 参数:
  367. - auth (AuthSchema): 认证信息模型
  368. - data (UserChangePasswordSchema): 用户密码修改数据
  369. 返回:
  370. - Dict: 更新后的当前用户详情字典
  371. """
  372. if not auth.user or not auth.user.id:
  373. raise CustomException(msg="用户不存在")
  374. if not data.old_password or not data.new_password:
  375. raise CustomException(msg="密码不能为空")
  376. # 验证原密码
  377. user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id)
  378. if not user:
  379. raise CustomException(msg="用户不存在")
  380. if not PwdUtil.verify_password(
  381. plain_password=data.old_password, password_hash=user.password
  382. ):
  383. raise CustomException(msg="原密码输入错误")
  384. # 更新密码
  385. new_password_hash = PwdUtil.set_password_hash(password=data.new_password)
  386. new_user = await UserCRUD(auth).change_password_crud(
  387. id=user.id, password_hash=new_password_hash
  388. )
  389. return UserOutSchema.model_validate(new_user).model_dump()
  390. @classmethod
  391. async def reset_user_password_service(cls, auth: AuthSchema, data: ResetPasswordSchema) -> dict:
  392. """
  393. 重置用户密码
  394. 参数:
  395. - auth (AuthSchema): 认证信息模型
  396. - data (ResetPasswordSchema): 用户密码重置数据
  397. 返回:
  398. - Dict: 更新后的当前用户详情字典
  399. """
  400. if not data.password:
  401. raise CustomException(msg="密码不能为空")
  402. # 验证用户
  403. user = await UserCRUD(auth).get_by_id_crud(id=data.id)
  404. if not user:
  405. raise CustomException(msg="用户不存在")
  406. # 检查是否是超级管理员
  407. if user.is_superuser:
  408. raise CustomException(msg="超级管理员密码不能重置")
  409. # 更新密码
  410. new_password_hash = PwdUtil.set_password_hash(password=data.password)
  411. new_user = await UserCRUD(auth).change_password_crud(
  412. id=data.id, password_hash=new_password_hash
  413. )
  414. return UserOutSchema.model_validate(new_user).model_dump()
  415. @classmethod
  416. async def register_user_service(cls, auth: AuthSchema, redis: Redis, data: UserRegisterSchema) -> dict:
  417. """
  418. 用户注册
  419. 参数:
  420. - auth (AuthSchema): 认证信息模型
  421. - data (UserRegisterSchema): 用户注册数据
  422. 返回:
  423. - Dict: 注册后的用户详情字典
  424. """
  425. # if not data.invite_code or data.invite_code != "8888":
  426. # raise CustomException("无效邀请码")
  427. # 检查用户名是否存在
  428. data.username = data.mobile
  429. username_ok = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
  430. if username_ok:
  431. raise CustomException(msg="账号已存在")
  432. verify_result = await SmsCodeService.verify_sms_code_service(
  433. sms_code=SmsCodeSchema(
  434. mobile=data.mobile,
  435. template_name=data.template_name or "verify",
  436. code=data.sms_code,
  437. ),
  438. redis=redis
  439. )
  440. if not verify_result:
  441. raise CustomException("验证码过期或错误")
  442. tenant_data = TenantCreateSchema(
  443. name=data.mobile,
  444. code=data.mobile,
  445. )
  446. return await TenantService.create_service(auth=auth, data=tenant_data, password=data.password,)
  447. # data.password = PwdUtil.set_password_hash(password=data.password)
  448. # data.name = data.username
  449. # create_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"})
  450. #
  451. # # 设置创建人ID
  452. # if auth.user and auth.user.id:
  453. # create_dict["created_id"] = auth.user.id
  454. #
  455. # result = await UserCRUD(auth).create(data=create_dict)
  456. # if data.role_ids:
  457. # await UserCRUD(auth).set_user_roles_crud(user_ids=[result.id], role_ids=data.role_ids)
  458. # return UserOutSchema.model_validate(result).model_dump()
  459. @classmethod
  460. async def forget_password_service(
  461. cls, auth: AuthSchema, data: UserForgetPasswordSchema
  462. ) -> dict:
  463. """
  464. 用户忘记密码
  465. 参数:
  466. - auth (AuthSchema): 认证信息模型
  467. - data (UserForgetPasswordSchema): 用户忘记密码数据
  468. 返回:
  469. - Dict: 更新后的当前用户详情字典
  470. """
  471. user = await UserCRUD(auth).get_by_username_crud(username=data.username)
  472. if not user:
  473. raise CustomException(msg="用户不存在")
  474. if user.status == "1":
  475. raise CustomException(msg="用户已停用")
  476. # 检查是否是超级管理员
  477. if user.is_superuser:
  478. raise CustomException(msg="超级管理员密码不能重置")
  479. new_password_hash = PwdUtil.set_password_hash(password=data.new_password)
  480. new_user = await UserCRUD(auth).forget_password_crud(
  481. id=user.id, password_hash=new_password_hash
  482. )
  483. return UserOutSchema.model_validate(new_user).model_dump()
  484. @classmethod
  485. async def batch_import_user_service(
  486. cls, auth: AuthSchema, file: UploadFile, update_support: bool = False
  487. ) -> str:
  488. """
  489. 批量导入用户
  490. 参数:
  491. - auth (AuthSchema): 认证信息模型
  492. - file (UploadFile): 上传的Excel文件
  493. - update_support (bool, optional): 是否支持更新已存在用户. 默认值为False.
  494. 返回:
  495. - str: 导入结果消息
  496. """
  497. header_dict = {
  498. "部门编号": "dept_id",
  499. "账号": "username",
  500. "昵称": "name",
  501. "邮箱": "email",
  502. "手机号": "mobile",
  503. "性别": "gender",
  504. "状态": "status",
  505. }
  506. try:
  507. # 读取Excel文件
  508. contents = await file.read()
  509. df = pd.read_excel(io.BytesIO(contents))
  510. await file.close()
  511. if df.empty:
  512. raise CustomException(msg="导入文件为空")
  513. # 检查表头是否完整
  514. missing_headers = [header for header in header_dict.keys() if header not in df.columns]
  515. if missing_headers:
  516. raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
  517. # 重命名列名
  518. df.rename(columns=header_dict, inplace=True)
  519. # 验证必填字段
  520. required_fields = ["username", "name", "dept_id"]
  521. errors = []
  522. for field in required_fields:
  523. missing_rows = df[df[field].isnull()].index.tolist()
  524. if missing_rows:
  525. field_name = next(k for k, v in header_dict.items() if v == field)
  526. rows_str = "、".join([str(i + 1) for i in missing_rows])
  527. errors.append(f"{field_name}不能为空,第{rows_str}行")
  528. if errors:
  529. raise CustomException(msg=";".join(errors))
  530. error_msgs = []
  531. success_count = 0
  532. count = 0
  533. # 处理每一行数据
  534. for _index, row in df.iterrows():
  535. try:
  536. count = count + 1
  537. # 数据转换
  538. gender = "1" if row["gender"] == "男" else ("2" if row["gender"] == "女" else "1")
  539. status = "0" if row["status"] == "正常" else "1"
  540. # 构建用户数据
  541. user_data = {
  542. "username": str(row["username"]).strip(),
  543. "name": str(row["name"]).strip(),
  544. "email": str(row["email"]).strip(),
  545. "mobile": str(row["mobile"]).strip(),
  546. "gender": gender,
  547. "status": status,
  548. "dept_id": int(row["dept_id"]),
  549. "password": PwdUtil.set_password_hash(password="123456"), # 设置默认密码
  550. }
  551. # 处理用户导入
  552. exists_user = await UserCRUD(auth).get_by_username_crud(
  553. username=user_data["username"]
  554. )
  555. if exists_user:
  556. # 检查是否是超级管理员
  557. if exists_user.is_superuser:
  558. error_msgs.append(f"第{count}行: 超级管理员不允许修改")
  559. continue
  560. if update_support:
  561. user_update_data = UserUpdateSchema(**user_data)
  562. await UserCRUD(auth).update(id=exists_user.id, data=user_update_data)
  563. success_count += 1
  564. else:
  565. error_msgs.append(f"第{count}行: 用户 {user_data['username']} 已存在")
  566. else:
  567. user_create_schema = UserCreateSchema(**user_data)
  568. user_create_data = user_create_schema.model_dump(
  569. exclude_unset=True, exclude={"role_ids", "position_ids"}
  570. )
  571. new_user = await UserCRUD(auth).create(data=user_create_data)
  572. if user_create_schema.role_ids and len(user_create_schema.role_ids) > 0:
  573. await UserCRUD(auth).set_user_roles_crud(
  574. user_ids=[new_user.id], role_ids=user_create_schema.role_ids
  575. )
  576. if user_create_schema.position_ids and len(user_create_schema.position_ids) > 0:
  577. await UserCRUD(auth).set_user_positions_crud(
  578. user_ids=[new_user.id], position_ids=user_create_schema.position_ids
  579. )
  580. success_count += 1
  581. except Exception as e:
  582. error_msgs.append(f"第{count}行: 异常{e!s}")
  583. continue
  584. # 返回详细的导入结果
  585. result = f"成功导入 {success_count} 条数据"
  586. if error_msgs:
  587. result += "\n错误信息:\n" + "\n".join(error_msgs)
  588. return result
  589. except Exception as e:
  590. log.error(f"批量导入用户失败: {e!s}")
  591. raise CustomException(msg=f"导入失败: {e!s}")
  592. @classmethod
  593. async def get_import_template_user_service(cls) -> bytes:
  594. """
  595. 获取用户导入模板
  596. 返回:
  597. - bytes: Excel文件字节流
  598. """
  599. header_list = [
  600. "部门编号",
  601. "账号",
  602. "昵称",
  603. "邮箱",
  604. "手机号",
  605. "性别",
  606. "状态",
  607. ]
  608. selector_header_list = ["性别", "状态"]
  609. option_list = [
  610. {"性别": ["男", "女", "未知"]},
  611. {"状态": ["正常", "停用"]},
  612. ]
  613. return ExcelUtil.get_excel_template(
  614. header_list=header_list,
  615. selector_header_list=selector_header_list,
  616. option_list=option_list,
  617. )
  618. @classmethod
  619. async def export_user_list_service(cls, user_list: list[dict[str, Any]]) -> bytes:
  620. """
  621. 导出用户列表为Excel文件
  622. 参数:
  623. - user_list (list[dict[str, Any]]): 用户列表
  624. 返回:
  625. - bytes: Excel文件字节流
  626. """
  627. if not user_list:
  628. raise CustomException(msg="没有数据可导出")
  629. # 定义字段映射
  630. mapping_dict = {
  631. "id": "用户编号",
  632. "avatar": "头像",
  633. "username": "用户名称",
  634. "name": "用户昵称",
  635. "dept_name": "部门",
  636. "email": "邮箱",
  637. "mobile": "手机号",
  638. "gender": "性别",
  639. "status": "状态",
  640. "is_superuser": "是否超级管理员",
  641. "last_login": "最后登录时间",
  642. "description": "备注",
  643. "created_time": "创建时间",
  644. "updated_time": "更新时间",
  645. "updated_id": "更新者ID",
  646. }
  647. # 复制数据并转换
  648. # creator = {'id': 1, 'name': '管理员', 'username': 'admin'}
  649. data = user_list.copy()
  650. for item in data:
  651. item["status"] = "启用" if item.get("status") == "0" else "停用"
  652. gender = item.get("gender")
  653. item["gender"] = "男" if gender == "1" else ("女" if gender == "2" else "未知")
  654. item["is_superuser"] = "是" if item.get("is_superuser") else "否"
  655. item["creator"] = (
  656. item.get("creator", {}).get("name", "未知")
  657. if isinstance(item.get("creator"), dict)
  658. else "未知"
  659. )
  660. return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)