HelpLinkManageController.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. """
  2. @File : HelpLinkManageController.py
  3. @Time : 2025/7/30 8:52
  4. @Author : Zhuo
  5. @Email : zhuojiaxuan@zosi.life
  6. @Software: PyCharm
  7. """
  8. import csv
  9. import datetime
  10. import json
  11. import time
  12. from io import StringIO
  13. from django.core.paginator import Paginator, EmptyPage
  14. from django.db import transaction, IntegrityError
  15. from django.db.models import Q, Case, When
  16. from django.http import QueryDict, HttpResponse
  17. from django.views import View
  18. from Ansjer.config import LOGGER
  19. from Model.models import HelpLink
  20. from Object.Enums.RedisKeyConstant import RedisKeyConstant
  21. from Object.RedisObject import RedisObject
  22. from Object.ResponseObject import ResponseObject
  23. from Object.TokenObject import TokenObject
  24. class HelpLinkManageView(View):
  25. def get(self, request, *args, **kwargs):
  26. request.encoding = 'utf-8'
  27. operation = kwargs.get('operation')
  28. return self.validation(request.GET, request, operation)
  29. def post(self, request, *args, **kwargs):
  30. request.encoding = 'utf-8'
  31. operation = kwargs.get('operation')
  32. return self.validation(request.POST, request, operation)
  33. def delete(self, request, *args, **kwargs):
  34. request.encoding = 'utf-8'
  35. operation = kwargs.get('operation')
  36. delete = QueryDict(request.body)
  37. if not delete:
  38. delete = request.GET
  39. return self.validation(delete, request, operation)
  40. def put(self, request, *args, **kwargs):
  41. request.encoding = 'utf-8'
  42. operation = kwargs.get('operation')
  43. put = QueryDict(request.body)
  44. return self.validation(put, request, operation)
  45. def validation(self, request_dict, request, operation):
  46. """请求验证路由"""
  47. # 初始化响应对象
  48. language = request_dict.get('language', 'en')
  49. response = ResponseObject(language, 'pc')
  50. # Token验证
  51. try:
  52. tko = TokenObject(
  53. request.META.get('HTTP_AUTHORIZATION'),
  54. returntpye='pc')
  55. if tko.code != 0:
  56. return response.json(tko.code)
  57. response.lang = tko.lang
  58. user_id = tko.userID
  59. except Exception as e:
  60. LOGGER.error(f"Token验证失败: {str(e)}")
  61. return response.json(444)
  62. # 操作路由映射
  63. operation_handlers = {
  64. 'queryList': self.query_list, # 查询
  65. 'add': self.add_help_link, # 添加
  66. 'edit': self.edit_help_link, # 编辑
  67. 'delete': self.delete_help_link, # 删除
  68. 'getByDeviceType': self.get_by_device_type, # 根据设备类型获取方案
  69. 'batchDelete': self.batch_delete, # 批量删除
  70. 'export': self.export_help_links, # 导出帮助链接
  71. }
  72. handler = operation_handlers.get(operation)
  73. if not handler:
  74. return response.json(444, 'operation')
  75. try:
  76. return handler(user_id, request_dict, response)
  77. except Exception as e:
  78. LOGGER.error(f"操作{operation}执行异常:{repr(e)}")
  79. return response.json(500, "服务器内部错误")
  80. def query_list(self,user_id, request_dict, response):
  81. """查询帮助链接列表"""
  82. # 参数处理与验证
  83. try:
  84. page = int(request_dict.get('page', 1))
  85. page_size = min(int(request_dict.get('pageSize', 10)), 100) # 限制最大分页大小
  86. except ValueError:
  87. return response.json(444, "分页参数错误")
  88. # 构建查询条件
  89. query = Q()
  90. # 特殊字段处理
  91. if device_type := request_dict.get('deviceType'):
  92. query &= Q(device_type=int(device_type))
  93. if lang := request_dict.get('lang'):
  94. query &= Q(lang=lang)
  95. if title := request_dict.get('title'):
  96. query &= Q(title__icontains=title)
  97. if url := request_dict.get('url'):
  98. query &= Q(url__icontains=url)
  99. # 使用分页器
  100. queryset = HelpLink.objects.filter(query).order_by('-id')
  101. paginator = Paginator(queryset, page_size)
  102. try:
  103. page_obj = paginator.page(page)
  104. except EmptyPage:
  105. return response.json(444, "页码超出范围")
  106. # 序列化数据
  107. data = [self._help_link_to_dict(help_link) for help_link in page_obj]
  108. return response.json(0, {
  109. 'list': data,
  110. 'total': paginator.count,
  111. 'currentPage': page_obj.number,
  112. 'totalPages': paginator.num_pages
  113. })
  114. def add_help_link(self, user_id, request_dict, response):
  115. """新增帮助链接"""
  116. try:
  117. # 构造帮助链接数据
  118. help_link_data = {
  119. 'device_type': int(request_dict.get('deviceType', 0)),
  120. 'lang': request_dict.get('lang'),
  121. 'url': request_dict.get('url', ''),
  122. 'title': request_dict.get('title', ''),
  123. 'description': request_dict.get('description', ''),
  124. 'is_active' : request_dict.get('isActive', 1),
  125. 'created_time': int(time.time()),
  126. 'updated_time': int(time.time()),
  127. }
  128. help_link = HelpLink.objects.create(**help_link_data)
  129. return response.json(0, {
  130. 'id': help_link.id
  131. })
  132. except IntegrityError as e:
  133. LOGGER.error(f"用户 {user_id} 创建帮助链接时发生唯一性冲突: {str(e)}")
  134. return response.json(173, "数据已存在")
  135. except ValueError as e:
  136. return response.json(444, "参数类型错误")
  137. except Exception as e:
  138. LOGGER.exception(f"用户 {user_id} 添加帮助链接异常:{repr(e)}")
  139. return response.json(500, "添加失败")
  140. def edit_help_link(self, user_id, request_dict, response):
  141. """编辑帮助链接"""
  142. help_link_id = request_dict.get('helpLinkId',None)
  143. device_type = request_dict.get('deviceType', None)
  144. lang = request_dict.get('lang', None)
  145. url = request_dict.get('url', None)
  146. title = request_dict.get('title', None)
  147. description = request_dict.get('description', None)
  148. is_active = request_dict.get('isActive', None)
  149. if not all([help_link_id, device_type, lang, url]):
  150. return response.json(444)
  151. try:
  152. now_time = int(time.time())
  153. help_link_qs = HelpLink.objects.filter(id=help_link_id).values('device_type', 'lang')
  154. if not help_link_qs.exists():
  155. return response.json(173)
  156. # 保存修改前的信息,用于清除缓存
  157. old_device_type = help_link_qs[0]['device_type']
  158. old_lang = help_link_qs[0]['lang']
  159. # 更新数据
  160. update_data = {
  161. 'device_type': int(device_type),
  162. 'lang': lang,
  163. 'url': url,
  164. 'title': title,
  165. 'description': description,
  166. 'updated_time': now_time
  167. }
  168. # 只有当is_active参数存在时才更新该字段
  169. if is_active is not None:
  170. update_data['is_active'] = bool(is_active)
  171. HelpLink.objects.filter(id=help_link_id).update(**update_data)
  172. # 清除相关缓存
  173. self._clear_help_link_cache(old_device_type, old_lang)
  174. # 如果设备类型或语言发生变化,也需要清除新缓存
  175. if int(device_type) != old_device_type or lang != old_lang:
  176. self._clear_help_link_cache(int(device_type), lang)
  177. return response.json(0)
  178. except Exception as e:
  179. LOGGER.exception(f"用户 {user_id} 编辑帮助链接异常: {repr(e)}")
  180. return response.json(500, f'error_line:{e.__traceback__.tb_lineno}, error_msg:{repr(e)}')
  181. def delete_help_link(self, user_id, request_dict, response):
  182. """删除帮助链接"""
  183. # 参数校验
  184. if 'id' not in request_dict:
  185. return response.json(444, "缺少链接ID")
  186. try:
  187. # 先获取帮助链接信息,用于清除缓存
  188. help_link = HelpLink.objects.get(id=request_dict['id'])
  189. device_type = help_link.device_type
  190. lang = help_link.lang
  191. # 直接删除记录
  192. deleted_count, _ = HelpLink.objects.filter(id=request_dict['id']).delete()
  193. if deleted_count == 0:
  194. return response.json(173, "帮助链接不存在")
  195. # 清除相关缓存
  196. self._clear_help_link_cache(device_type, lang)
  197. # 记录操作日志
  198. LOGGER.info(f"用户 {user_id} 删除了帮助链接,ID: {request_dict['id']}")
  199. return response.json(0)
  200. except HelpLink.DoesNotExist:
  201. return response.json(173, "帮助链接不存在")
  202. except ValueError:
  203. return response.json(500, "链接ID格式错误")
  204. def get_by_device_type(self,user_id, request_dict, response):
  205. """根据设备类型和语言获取帮助链接"""
  206. try:
  207. device_type = int(request_dict.get('deviceType',0))
  208. lang = request_dict.get('lang')
  209. except(ValueError, TypeError):
  210. return response.json(444, '参数错误')
  211. try:
  212. # 构建缓存键
  213. cache_key = RedisKeyConstant.HELP_LINK_TYPE.value + f'{device_type}:{lang}'
  214. redis = RedisObject("help_link")
  215. # 先尝试从缓存获取
  216. cached_data = redis.get_data(cache_key)
  217. if cached_data:
  218. cached_data = json.loads(cached_data)
  219. return response.json(0, cached_data)
  220. # 优化数据库查询 - 单次查询获取结果
  221. help_link = HelpLink.objects.filter(
  222. Q(device_type=device_type) | Q(device_type=-1),
  223. lang=lang,
  224. is_active=True
  225. ).order_by(
  226. # 优先匹配指定设备类型,其次匹配通用类型(-1)
  227. Case(
  228. When(device_type=device_type, then=0),
  229. default=1,
  230. )
  231. ).first()
  232. if not help_link:
  233. return response.json(173)
  234. # 构建返回数据
  235. data = {
  236. 'url': help_link.url,
  237. 'title': help_link.title,
  238. 'description': help_link.description
  239. }
  240. # 设置缓存,过期时间30天
  241. try:
  242. redis.set_data(cache_key, json.dumps(data), RedisKeyConstant.EXPIRE_TIME_30_DAYS.value)
  243. except Exception as e:
  244. LOGGER.error(f"设置Redis缓存出错: {repr(e)}")
  245. # 缓存失败不影响主流程
  246. return response.json(0, data)
  247. except Exception as e:
  248. LOGGER.error(f"查询帮助链接出错: {repr(e)}")
  249. return response.json(500)
  250. def batch_delete(self, user_id, request_dict, response):
  251. """批量删除帮助链接"""
  252. try:
  253. ids = request_dict.get('ids')
  254. if not ids:
  255. return response.json(444, "参数错误")
  256. # 解析ID列表
  257. if isinstance(ids, str):
  258. ids = json.loads(ids)
  259. if not isinstance(ids, list) or not ids:
  260. return response.json(444, "ID列表格式错误")
  261. # 查询要删除的帮助链接信息,并收集缓存清理信息
  262. help_links = HelpLink.objects.filter(id__in=ids)
  263. if not help_links.exists():
  264. return response.json(173, "没有找到可删除的帮助链接")
  265. deleted_links_info = list(help_links.values('device_type', 'lang'))
  266. # 批量删除
  267. deleted_count, _ = help_links.delete()
  268. # 清除相关缓存
  269. for link_info in deleted_links_info:
  270. self._clear_help_link_cache(link_info['device_type'], link_info['lang'])
  271. # 记录操作日志
  272. LOGGER.info(f"用户 {user_id} 批量删除了 {deleted_count} 条帮助链接,ID列表: {ids}")
  273. return response.json(0, f"成功删除{deleted_count}条记录")
  274. except json.JSONDecodeError:
  275. return response.json(444, "ID列表格式错误")
  276. except Exception as e:
  277. LOGGER.error(f"用户 {user_id} 批量删除帮助链接失败: {str(e)}")
  278. return response.json(500, "批量删除失败")
  279. def export_help_links(self, user_id, request_dict, response):
  280. """导出帮助链接为CSV格式"""
  281. try:
  282. # 获取所有帮助链接
  283. queryset = HelpLink.objects.filter(is_active=True).order_by('-id')
  284. # 创建CSV内容
  285. output = StringIO()
  286. writer = csv.writer(output)
  287. # 写入表头
  288. writer.writerow(['ID', '设备类型', '语言', '链接标题', 'URL', '描述', '是否启用', '创建时间', '更新时间'])
  289. # 写入每一行数据
  290. for link in queryset:
  291. # 确保时间戳是整数
  292. created_ts = int(link.created_time) if link.created_time else None
  293. updated_ts = int(link.updated_time) if link.updated_time else None
  294. created_time = datetime.datetime.fromtimestamp(created_ts).strftime(
  295. '%Y-%m-%d %H:%M:%S') if created_ts is not None else ''
  296. updated_time = datetime.datetime.fromtimestamp(updated_ts).strftime(
  297. '%Y-%m-%d %H:%M:%S') if updated_ts is not None else ''
  298. writer.writerow([
  299. link.id,
  300. link.device_type,
  301. link.lang,
  302. link.title,
  303. link.url,
  304. link.description,
  305. '是' if link.is_active else '否',
  306. created_time,
  307. updated_time
  308. ])
  309. LOGGER.info(f"用户 {user_id} 导出了 {queryset.count()} 条帮助链接")
  310. # 准备响应
  311. csv_content = output.getvalue()
  312. output.close()
  313. # 创建HTTP响应
  314. response_http = HttpResponse(
  315. csv_content,
  316. content_type='text/csv',
  317. headers={
  318. 'Content-Disposition': 'attachment; filename="help_links_export.csv"'
  319. }
  320. )
  321. return response_http
  322. except Exception as e:
  323. LOGGER.error(f"用户 {user_id} 导出帮助链接失败: {str(e)}")
  324. return ResponseObject('cn', 'pc').json(500, "导出失败")
  325. def _help_link_to_dict(self, help_link):
  326. """帮助链接对象序列化"""
  327. return {
  328. 'id': help_link.id,
  329. 'deviceType': help_link.device_type,
  330. 'lang': help_link.lang,
  331. 'url': help_link.url,
  332. 'title': help_link.title,
  333. 'description': help_link.description,
  334. 'isActive': help_link.is_active,
  335. 'createdTime': help_link.created_time,
  336. 'updatedTime': help_link.updated_time,
  337. }
  338. def _clear_help_link_cache(self, device_type, lang):
  339. """
  340. 清除帮助链接缓存
  341. """
  342. try:
  343. cache_key = RedisKeyConstant.HELP_LINK_TYPE.value + f'{device_type}:{lang}'
  344. redis = RedisObject("help_link")
  345. redis.del_data(cache_key)
  346. # 同时清除通用类型(-1)的缓存
  347. cache_key_general = f"help_link:-1:{lang}"
  348. redis.del_data(cache_key_general)
  349. except Exception as e:
  350. LOGGER.error(f"清除帮助链接缓存失败: {repr(e)}")