views.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. from django.http import JsonResponse
  2. from django.views.decorators.csrf import csrf_exempt
  3. from django.views.decorators.http import require_POST
  4. from .backup_utils import perform_base_backup, restore_to_base_backup
  5. import os
  6. import json
  7. import logging
  8. from datetime import datetime
  9. from apscheduler.schedulers.background import BackgroundScheduler
  10. from django.conf import settings
  11. import math
  12. from operation_log.views import log_success_operation, log_failure_operation
  13. from operation_log.models import OperationLog
  14. from django.utils import timezone
  15. logger = logging.getLogger(__name__)
  16. # 初始化调度器
  17. scheduler = BackgroundScheduler()
  18. def scheduled_backup():
  19. """定时备份任务"""
  20. try:
  21. backup_path = perform_base_backup()
  22. logger.info(f"定时备份完成: {backup_path}")
  23. # 记录操作日志(定时任务没有request对象,直接创建日志)
  24. try:
  25. OperationLog.objects.create(
  26. operator="系统定时任务",
  27. operation_content=f"定时备份数据库完成,备份路径: {backup_path}",
  28. operation_level="other",
  29. operation_result="success",
  30. ip_address=None,
  31. user_agent="系统定时任务",
  32. request_method="CRON",
  33. request_path="/backup/scheduled",
  34. module_name="系统备份"
  35. )
  36. except Exception as log_error:
  37. logger.error(f"定时备份日志记录失败: {str(log_error)}")
  38. # 更新托盘分类任务(如果存在)
  39. try:
  40. from container.utils import update_container_categories_task,reconcile_material_history
  41. update_container_categories_task()
  42. reconcile_material_history()
  43. logger.info(f"定时更新托盘分类完成")
  44. except ImportError:
  45. logger.warning("更新托盘分类模块未找到,跳过更新")
  46. except Exception as e:
  47. logger.error(f"定时备份失败: {str(e)}")
  48. # 记录失败日志
  49. try:
  50. OperationLog.objects.create(
  51. operator="系统定时任务",
  52. operation_content=f"定时备份数据库失败: {str(e)}",
  53. operation_level="other",
  54. operation_result="failure",
  55. ip_address=None,
  56. user_agent="系统定时任务",
  57. request_method="CRON",
  58. request_path="/backup/scheduled",
  59. module_name="系统备份"
  60. )
  61. except Exception as log_error:
  62. logger.error(f"定时备份失败日志记录失败: {str(log_error)}")
  63. # 启动定时备份(每6小时执行一次)
  64. if not scheduler.running:
  65. scheduler.add_job(
  66. scheduled_backup,
  67. 'cron',
  68. hour='*/6', # 每6小时执行一次
  69. minute=0, # 在0分钟时执行
  70. id='db_backup_job'
  71. )
  72. scheduler.start()
  73. logger.info("定时备份任务已启动")
  74. def get_backup_files(page=1, page_size=5):
  75. """获取备份文件列表(带分页)"""
  76. backup_dir = "E:/code/backup/postgres"
  77. all_backups = []
  78. # 遍历备份目录
  79. for root, dirs, files in os.walk(backup_dir):
  80. for file in files:
  81. if file.endswith(".backup"):
  82. file_path = os.path.join(root, file)
  83. file_size = os.path.getsize(file_path)
  84. timestamp = os.path.getmtime(file_path)
  85. all_backups.append({
  86. "name": file,
  87. "path": file_path,
  88. "size": f"{file_size / (1024 * 1024):.2f} MB",
  89. "date": datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
  90. })
  91. # 按时间倒序排序
  92. all_backups.sort(key=lambda x: x["date"], reverse=True)
  93. # 分页处理
  94. total_items = len(all_backups)
  95. total_pages = math.ceil(total_items / page_size)
  96. start_index = (page - 1) * page_size
  97. end_index = min(start_index + page_size, total_items)
  98. return {
  99. "backups": all_backups[start_index:end_index],
  100. "page": page,
  101. "page_size": page_size,
  102. "total_items": total_items,
  103. "total_pages": total_pages
  104. }
  105. def get_base_backups(page=1, page_size=5):
  106. """获取基础备份列表(带分页)"""
  107. base_backup_dir = "E:/code/backup/postgres/base_backup"
  108. all_backups = []
  109. # 遍历基础备份目录
  110. for dir_name in os.listdir(base_backup_dir):
  111. dir_path = os.path.join(base_backup_dir, dir_name)
  112. if os.path.isdir(dir_path):
  113. timestamp = os.path.getmtime(dir_path)
  114. all_backups.append({
  115. "name": dir_name,
  116. "path": dir_path,
  117. "date": datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
  118. })
  119. # 按时间倒序排序
  120. all_backups.sort(key=lambda x: x["date"], reverse=True)
  121. # 分页处理
  122. total_items = len(all_backups)
  123. total_pages = math.ceil(total_items / page_size)
  124. start_index = (page - 1) * page_size
  125. end_index = min(start_index + page_size, total_items)
  126. return {
  127. "backups": all_backups[start_index:end_index],
  128. "page": page,
  129. "page_size": page_size,
  130. "total_items": total_items,
  131. "total_pages": total_pages
  132. }
  133. @csrf_exempt
  134. @require_POST
  135. def trigger_backup(request):
  136. """手动触发备份的API接口"""
  137. try:
  138. backup_path = perform_base_backup()
  139. # 记录成功日志
  140. try:
  141. log_success_operation(
  142. request=request,
  143. operation_content=f"手动触发数据库备份,备份路径: {backup_path}",
  144. operation_level="other",
  145. module_name="系统备份"
  146. )
  147. except Exception as log_error:
  148. logger.error(f"备份成功日志记录失败: {str(log_error)}")
  149. return JsonResponse({
  150. 'status': 'success',
  151. 'message': '数据库备份完成',
  152. 'path': backup_path
  153. })
  154. except Exception as e:
  155. # 记录失败日志
  156. try:
  157. log_failure_operation(
  158. request=request,
  159. operation_content=f"手动触发数据库备份失败: {str(e)}",
  160. operation_level="other",
  161. module_name="系统备份"
  162. )
  163. except Exception as log_error:
  164. logger.error(f"备份失败日志记录失败: {str(log_error)}")
  165. return JsonResponse({
  166. 'status': 'error',
  167. 'message': str(e)
  168. }, status=500)
  169. @csrf_exempt
  170. @require_POST
  171. def list_backups(request):
  172. """获取备份文件列表API(带分页)"""
  173. try:
  174. data = json.loads(request.body)
  175. backup_type = data.get('type', 'file')
  176. page = data.get('page', 1)
  177. page_size = data.get('page_size', 5)
  178. if backup_type == 'file':
  179. result = get_backup_files(page, page_size)
  180. elif backup_type == 'base':
  181. result = get_base_backups(page, page_size)
  182. else:
  183. return JsonResponse({
  184. 'status': 'error',
  185. 'message': '无效的备份类型'
  186. }, status=400)
  187. return JsonResponse({
  188. 'status': 'success',
  189. 'data': result
  190. })
  191. except Exception as e:
  192. logger.error(f"获取备份列表失败: {str(e)}")
  193. return JsonResponse({
  194. 'status': 'error',
  195. 'message': str(e)
  196. }, status=500)
  197. @csrf_exempt
  198. @require_POST
  199. def restore_to_point(request):
  200. """执行时间点恢复API"""
  201. try:
  202. data = json.loads(request.body)
  203. base_backup = data.get('base_backup')
  204. if not base_backup or not os.path.exists(base_backup):
  205. # 记录失败日志(无效路径)
  206. try:
  207. log_failure_operation(
  208. request=request,
  209. operation_content=f"执行数据库恢复失败: 无效的基础备份路径 - {base_backup}",
  210. operation_level="other",
  211. module_name="系统备份"
  212. )
  213. except Exception as log_error:
  214. logger.error(f"恢复失败日志记录失败: {str(log_error)}")
  215. return JsonResponse({
  216. 'status': 'error',
  217. 'message': '无效的基础备份路径'
  218. }, status=400)
  219. # 暂停定时备份任务
  220. scheduler.pause_job('db_backup_job')
  221. logger.info("定时备份任务已暂停")
  222. # 执行时间点恢复
  223. restore_to_base_backup( base_backup)
  224. # 恢复定时备份任务
  225. scheduler.resume_job('db_backup_job')
  226. logger.info("定时备份任务已恢复")
  227. # 记录成功日志
  228. try:
  229. log_success_operation(
  230. request=request,
  231. operation_content=f"执行数据库时间点恢复,恢复路径: {base_backup}",
  232. operation_level="other",
  233. module_name="系统备份"
  234. )
  235. except Exception as log_error:
  236. logger.error(f"恢复成功日志记录失败: {str(log_error)}")
  237. return JsonResponse({
  238. 'status': 'success',
  239. 'message': f'已成功恢复到{base_backup}'
  240. })
  241. except Exception as e:
  242. logger.error(f"时间点恢复失败: {str(e)}")
  243. # 记录失败日志
  244. try:
  245. log_failure_operation(
  246. request=request,
  247. operation_content=f"执行数据库时间点恢复失败: {str(e)}",
  248. operation_level="other",
  249. module_name="系统备份"
  250. )
  251. except Exception as log_error:
  252. logger.error(f"恢复失败日志记录失败: {str(log_error)}")
  253. # 确保恢复定时备份任务
  254. if scheduler.get_job('db_backup_job') and scheduler.get_job('db_backup_job').next_run_time is None:
  255. scheduler.resume_job('db_backup_job')
  256. logger.info("恢复失败后定时备份任务已恢复")
  257. return JsonResponse({
  258. 'status': 'error',
  259. 'message': str(e)
  260. }, status=500)