service.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. import json
  2. from fastapi import UploadFile
  3. from redis.asyncio.client import Redis
  4. from app.api.v1.module_system.auth.schema import AuthSchema
  5. from app.common.enums import RedisInitKeyConfig
  6. from app.core.base_schema import UploadResponseSchema
  7. from app.core.database import async_db_session
  8. from app.core.exceptions import CustomException
  9. from app.core.logger import log
  10. from app.core.redis_crud import RedisCURD
  11. from app.utils.excel_util import ExcelUtil
  12. from app.utils.upload_util import UploadUtil
  13. from .crud import ParamsCRUD
  14. from .schema import (
  15. ParamsCreateSchema,
  16. ParamsOutSchema,
  17. ParamsQueryParam,
  18. ParamsUpdateSchema,
  19. )
  20. class ParamsService:
  21. """
  22. 配置管理模块服务层
  23. """
  24. @classmethod
  25. async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> dict:
  26. """
  27. 获取配置详情
  28. 参数:
  29. - auth (AuthSchema): 认证信息模型
  30. - id (int): 配置管理型ID
  31. 返回:
  32. - dict: 配置管理型模型实例字典表示
  33. """
  34. obj = await ParamsCRUD(auth).get_obj_by_id_crud(id=id)
  35. return ParamsOutSchema.model_validate(obj).model_dump()
  36. @classmethod
  37. async def get_obj_by_key_service(cls, auth: AuthSchema, config_key: str) -> dict:
  38. """
  39. 根据配置键获取配置详情
  40. 参数:
  41. - auth (AuthSchema): 认证信息模型
  42. - config_key (str): 配置管理型key
  43. 返回:
  44. - Dict: 配置管理型模型实例字典表示
  45. """
  46. obj = await ParamsCRUD(auth).get_obj_by_key_crud(key=config_key)
  47. if not obj:
  48. raise CustomException(msg=f"配置键 {config_key} 不存在")
  49. return ParamsOutSchema.model_validate(obj).model_dump()
  50. @classmethod
  51. async def get_config_value_by_key_service(cls, auth: AuthSchema, config_key: str) -> str | None:
  52. """
  53. 根据配置键获取配置值
  54. 参数:
  55. - auth (AuthSchema): 认证信息模型
  56. - config_key (str): 配置管理型key
  57. 返回:
  58. - str | None: 配置值字符串或None
  59. """
  60. obj = await ParamsCRUD(auth).get_obj_by_key_crud(key=config_key)
  61. if not obj:
  62. raise CustomException(msg=f"配置键 {config_key} 不存在")
  63. return obj.config_value
  64. @classmethod
  65. async def get_obj_list_service(
  66. cls,
  67. auth: AuthSchema,
  68. search: ParamsQueryParam | None = None,
  69. order_by: list[dict] | None = None,
  70. ) -> list[dict]:
  71. """
  72. 获取配置管理型列表
  73. 参数:
  74. - auth (AuthSchema): 认证信息模型
  75. - search (ParamsQueryParam | None): 查询参数对象
  76. - order_by (list[dict] | None): 排序参数列表
  77. 返回:
  78. - list[dict]: 配置管理型模型实例字典列表表示
  79. """
  80. obj_list = None
  81. if search:
  82. obj_list = await ParamsCRUD(auth).get_obj_list_crud(
  83. search=search.__dict__, order_by=order_by
  84. )
  85. else:
  86. obj_list = await ParamsCRUD(auth).get_obj_list_crud()
  87. return [ParamsOutSchema.model_validate(obj).model_dump() for obj in obj_list]
  88. @classmethod
  89. async def get_obj_page_service(
  90. cls,
  91. auth: AuthSchema,
  92. page_no: int,
  93. page_size: int,
  94. search: ParamsQueryParam | None = None,
  95. order_by: list[dict[str, str]] | None = None,
  96. ) -> dict:
  97. """
  98. 分页查询系统参数(数据库 OFFSET/LIMIT)。
  99. 参数:
  100. - auth (AuthSchema): 认证信息模型
  101. - page_no (int): 页码(从 1 开始)
  102. - page_size (int): 每页条数
  103. - search (ParamsQueryParam | None): 查询条件
  104. - order_by (list[dict[str, str]] | None): 排序字段列表
  105. 返回:
  106. - dict: 分页结果(结构由 `CRUD.page` 返回约定)
  107. """
  108. offset = (page_no - 1) * page_size
  109. return await ParamsCRUD(auth).page(
  110. offset=offset,
  111. limit=page_size,
  112. order_by=order_by or [{"id": "asc"}],
  113. search=search.__dict__ if search else {},
  114. out_schema=ParamsOutSchema,
  115. )
  116. @classmethod
  117. async def create_obj_service(
  118. cls, auth: AuthSchema, redis: Redis, data: ParamsCreateSchema
  119. ) -> dict:
  120. """
  121. 创建配置管理型
  122. 参数:
  123. - auth (AuthSchema): 认证信息模型
  124. - redis (Redis): Redis 客户端实例
  125. - data (ParamsCreateSchema): 配置管理型创建模型
  126. 返回:
  127. - dict: 新创建的配置管理型模型实例字典表示
  128. """
  129. exist_obj = await ParamsCRUD(auth).get(config_key=data.config_key)
  130. if exist_obj:
  131. raise CustomException(msg="创建失败,该配置key已存在")
  132. obj = await ParamsCRUD(auth).create_obj_crud(data=data)
  133. new_obj_dict = ParamsOutSchema.model_validate(obj).model_dump()
  134. # 同步redis
  135. redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{data.config_key}"
  136. try:
  137. result = await RedisCURD(redis).set(
  138. key=redis_key,
  139. value="",
  140. )
  141. if not result:
  142. log.error(f"同步配置到缓存失败: {new_obj_dict}")
  143. raise CustomException(msg="同步配置到缓存失败")
  144. except Exception as e:
  145. log.error(f"创建字典类型失败: {e}")
  146. raise CustomException(msg=f"创建字典类型失败 {e}")
  147. return new_obj_dict
  148. @classmethod
  149. async def update_obj_service(
  150. cls, auth: AuthSchema, redis: Redis, id: int, data: ParamsUpdateSchema
  151. ) -> dict:
  152. """
  153. 更新配置管理型
  154. 参数:
  155. - auth (AuthSchema): 认证信息模型
  156. - redis (Redis): Redis 客户端实例
  157. - id (int): 配置管理型ID
  158. - data (ParamsUpdateSchema): 配置管理型更新模型
  159. 返回:
  160. - Dict: 更新后的配置管理型模型实例字典表示
  161. """
  162. exist_obj = await ParamsCRUD(auth).get_obj_by_id_crud(id=id)
  163. if not exist_obj:
  164. raise CustomException(msg="更新失败,该数系统配置不存在")
  165. if exist_obj.config_key != data.config_key:
  166. raise CustomException(msg="更新失败,系统配置key不允许修改")
  167. new_obj = await ParamsCRUD(auth).update_obj_crud(id=id, data=data)
  168. if not new_obj:
  169. raise CustomException(msg="更新失败,系统配置不存在")
  170. out = ParamsOutSchema.model_validate(new_obj)
  171. new_obj_dict = out.model_dump()
  172. redis_payload = out.model_dump(mode="json")
  173. # 同步redis
  174. redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{new_obj.config_key}"
  175. try:
  176. value = json.dumps(redis_payload, ensure_ascii=False)
  177. result = await RedisCURD(redis).set(
  178. key=redis_key,
  179. value=value,
  180. )
  181. if not result:
  182. log.error(f"同步配置到缓存失败: {new_obj_dict}")
  183. raise CustomException(msg="同步配置到缓存失败")
  184. except Exception as e:
  185. log.error(f"更新系统配置失败: {e}")
  186. raise CustomException(msg="更新系统配置失败")
  187. return new_obj_dict
  188. @classmethod
  189. async def delete_obj_service(cls, auth: AuthSchema, redis: Redis, ids: list[int]) -> None:
  190. """
  191. 删除配置管理型
  192. 参数:
  193. - auth (AuthSchema): 认证信息模型
  194. - redis (Redis): Redis 客户端实例
  195. - ids (list[int]): 配置管理型ID列表
  196. 返回:
  197. - None
  198. """
  199. if len(ids) < 1:
  200. raise CustomException(msg="删除失败,删除对象不能为空")
  201. for id in ids:
  202. exist_obj = await ParamsCRUD(auth).get_obj_by_id_crud(id=id)
  203. if not exist_obj:
  204. raise CustomException(msg="删除失败,该数据字典类型不存在")
  205. # 检查是否是否初始化类型
  206. if exist_obj.config_type:
  207. # 如果有字典数据,不能删除
  208. raise CustomException(
  209. msg=f"{exist_obj.config_name} 删除失败,系统初始化配置不可以删除"
  210. )
  211. await ParamsCRUD(auth).delete_obj_crud(ids=ids)
  212. # 同步删除Redis缓存
  213. for id in ids:
  214. exist_obj = await ParamsCRUD(auth).get_obj_by_id_crud(id=id)
  215. if not exist_obj:
  216. continue
  217. redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{exist_obj.config_key}"
  218. try:
  219. await RedisCURD(redis).delete(redis_key)
  220. log.info(f"删除系统配置成功: {id}")
  221. except Exception as e:
  222. log.error(f"删除系统配置失败: {e}")
  223. raise CustomException(msg="删除字典类型失败")
  224. @classmethod
  225. async def export_obj_service(cls, data_list: list[dict]) -> bytes:
  226. """
  227. 导出系统配置列表
  228. 参数:
  229. - data_list (list[dict]): 系统配置模型实例字典列表表示
  230. 返回:
  231. - bytes: Excel文件二进制数据
  232. """
  233. mapping_dict = {
  234. "id": "编号",
  235. "config_name": "参数名称",
  236. "config_key": "参数键名",
  237. "config_value": "参数键值",
  238. "config_type": "系统内置((True:是 False:否))",
  239. "description": "备注",
  240. "created_time": "创建时间",
  241. "updated_time": "更新时间",
  242. "created_id": "创建者ID",
  243. "updated_id": "更新者ID",
  244. }
  245. # 复制数据并转换状态
  246. data = data_list.copy()
  247. for item in data:
  248. # 处理状态
  249. item["config_type"] = "是" if item.get("config_type") else "否"
  250. item["creator"] = (
  251. item.get("creator", {}).get("name", "未知")
  252. if isinstance(item.get("creator"), dict)
  253. else "未知"
  254. )
  255. return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
  256. @classmethod
  257. async def upload_service(cls, base_url: str, file: UploadFile) -> dict:
  258. """
  259. 上传文件
  260. 参数:
  261. - base_url (str): 基础URL
  262. - file (UploadFile): 上传的文件对象
  263. 返回:
  264. - dict: 上传文件的响应模型实例字典表示
  265. """
  266. filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url)
  267. return UploadResponseSchema(
  268. file_path=f"{filepath}",
  269. file_name=filename,
  270. origin_name=file.filename,
  271. file_url=f"{file_url}",
  272. ).model_dump()
  273. @classmethod
  274. async def init_config_service(cls, redis: Redis) -> None:
  275. """
  276. 初始化系统配置
  277. 参数:
  278. - redis (Redis): Redis 客户端实例
  279. 返回:
  280. - None
  281. """
  282. async with async_db_session() as session:
  283. async with session.begin():
  284. # 在初始化过程中,不需要检查数据权限
  285. auth = AuthSchema(db=session, check_data_scope=False)
  286. config_obj = await ParamsCRUD(auth).get_obj_list_crud()
  287. if not config_obj:
  288. raise CustomException(msg="系统配置不存在")
  289. try:
  290. # 保存到Redis并设置过期时间
  291. for config in config_obj:
  292. redis_key = f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:{config.config_key}"
  293. out = ParamsOutSchema.model_validate(config)
  294. config_obj_dict = out.model_dump()
  295. redis_payload = out.model_dump(mode="json")
  296. value = json.dumps(redis_payload, ensure_ascii=False)
  297. result = await RedisCURD(redis).set(
  298. key=redis_key,
  299. value=value,
  300. )
  301. if not result:
  302. log.error(f"❌️ 初始化系统配置失败: {config_obj_dict}")
  303. raise CustomException(msg="初始化系统配置失败")
  304. except Exception as e:
  305. log.error(f"❌️ 初始化系统配置失败: {e}")
  306. raise CustomException(msg="初始化系统配置失败")
  307. @classmethod
  308. async def get_init_config_service(cls, redis: Redis) -> list[dict]:
  309. """
  310. 获取系统配置
  311. 参数:
  312. - redis (Redis): Redis 客户端实例
  313. 返回:
  314. - list[dict]: 系统配置模型实例字典列表表示
  315. """
  316. redis_keys = await RedisCURD(redis).get_keys(f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:*")
  317. redis_configs = await RedisCURD(redis).mget(redis_keys)
  318. configs = []
  319. for config in redis_configs:
  320. if not config:
  321. continue
  322. try:
  323. new_config = json.loads(config)
  324. configs.append(new_config)
  325. except Exception as e:
  326. log.error(f"解析系统配置数据失败: {e}")
  327. continue
  328. return configs
  329. @classmethod
  330. async def get_system_config_for_middleware(cls, redis: Redis) -> dict:
  331. """
  332. 获取中间件所需的系统配置
  333. 参数:
  334. - redis (Redis): Redis 客户端实例
  335. 返回:
  336. - dict: 包含演示模式、IP白名单、API白名单和IP黑名单的配置字典
  337. """
  338. # 定义需要获取的配置键
  339. config_keys = [
  340. f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:demo_enable",
  341. f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:ip_white_list",
  342. f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:white_api_list_path",
  343. f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:ip_black_list",
  344. ]
  345. # 批量获取配置
  346. config_values = await RedisCURD(redis).mget(config_keys)
  347. # 初始化默认配置
  348. config_result = {
  349. "demo_enable": False,
  350. "ip_white_list": [],
  351. "white_api_list_path": [],
  352. "ip_black_list": [],
  353. }
  354. # 解析演示模式配置
  355. if config_values[0]:
  356. try:
  357. demo_config = json.loads(config_values[0])
  358. config_result["demo_enable"] = (
  359. demo_config.get("config_value", False)
  360. if isinstance(demo_config, dict)
  361. else False
  362. )
  363. except json.JSONDecodeError:
  364. log.error("解析演示模式配置失败")
  365. # 解析IP白名单配置
  366. if config_values[1]:
  367. try:
  368. ip_white_config = json.loads(config_values[1])
  369. # 确保是列表类型
  370. config_result["ip_white_list"] = json.loads(ip_white_config.get("config_value", []))
  371. except json.JSONDecodeError:
  372. log.error("解析IP白名单配置失败")
  373. # 解析IP黑名单
  374. # 解析API路径白名单
  375. if config_values[2]:
  376. try:
  377. white_api_config = json.loads(config_values[2])
  378. # 确保是列表类型
  379. config_result["white_api_list_path"] = json.loads(
  380. white_api_config.get("config_value", [])
  381. )
  382. except json.JSONDecodeError:
  383. log.error("解析API白名单配置失败")
  384. # 解析IP黑名单
  385. if config_values[3]:
  386. try:
  387. black_ip_config = json.loads(config_values[3])
  388. # 确保是列表类型
  389. config_result["ip_black_list"] = json.loads(black_ip_config.get("config_value", []))
  390. except json.JSONDecodeError:
  391. log.error("解析IP黑名单配置失败")
  392. return config_result