views.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. logger = logging.getLogger(__name__)
  13. # 初始化调度器
  14. scheduler = BackgroundScheduler()
  15. def scheduled_backup():
  16. """定时备份任务"""
  17. try:
  18. backup_path = perform_base_backup()
  19. logger.info(f"定时备份完成: {backup_path}")
  20. # 更新托盘分类任务(如果存在)
  21. try:
  22. from container.utils import update_container_categories_task,reconcile_material_history
  23. update_container_categories_task()
  24. reconcile_material_history()
  25. logger.info(f"定时更新托盘分类完成")
  26. except ImportError:
  27. logger.warning("更新托盘分类模块未找到,跳过更新")
  28. except Exception as e:
  29. logger.error(f"定时备份失败: {str(e)}")
  30. # 启动定时备份(每6小时执行一次)
  31. if not scheduler.running:
  32. scheduler.add_job(
  33. scheduled_backup,
  34. 'cron',
  35. hour='*/6', # 每6小时执行一次
  36. minute=0, # 在0分钟时执行
  37. id='db_backup_job'
  38. )
  39. scheduler.start()
  40. logger.info("定时备份任务已启动")
  41. def get_backup_files(page=1, page_size=5):
  42. """获取备份文件列表(带分页)"""
  43. backup_dir = "E:/code/backup/postgres"
  44. all_backups = []
  45. # 遍历备份目录
  46. for root, dirs, files in os.walk(backup_dir):
  47. for file in files:
  48. if file.endswith(".backup"):
  49. file_path = os.path.join(root, file)
  50. file_size = os.path.getsize(file_path)
  51. timestamp = os.path.getmtime(file_path)
  52. all_backups.append({
  53. "name": file,
  54. "path": file_path,
  55. "size": f"{file_size / (1024 * 1024):.2f} MB",
  56. "date": datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
  57. })
  58. # 按时间倒序排序
  59. all_backups.sort(key=lambda x: x["date"], reverse=True)
  60. # 分页处理
  61. total_items = len(all_backups)
  62. total_pages = math.ceil(total_items / page_size)
  63. start_index = (page - 1) * page_size
  64. end_index = min(start_index + page_size, total_items)
  65. return {
  66. "backups": all_backups[start_index:end_index],
  67. "page": page,
  68. "page_size": page_size,
  69. "total_items": total_items,
  70. "total_pages": total_pages
  71. }
  72. def get_base_backups(page=1, page_size=5):
  73. """获取基础备份列表(带分页)"""
  74. base_backup_dir = "E:/code/backup/postgres/base_backup"
  75. all_backups = []
  76. # 遍历基础备份目录
  77. for dir_name in os.listdir(base_backup_dir):
  78. dir_path = os.path.join(base_backup_dir, dir_name)
  79. if os.path.isdir(dir_path):
  80. timestamp = os.path.getmtime(dir_path)
  81. all_backups.append({
  82. "name": dir_name,
  83. "path": dir_path,
  84. "date": datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
  85. })
  86. # 按时间倒序排序
  87. all_backups.sort(key=lambda x: x["date"], reverse=True)
  88. # 分页处理
  89. total_items = len(all_backups)
  90. total_pages = math.ceil(total_items / page_size)
  91. start_index = (page - 1) * page_size
  92. end_index = min(start_index + page_size, total_items)
  93. return {
  94. "backups": all_backups[start_index:end_index],
  95. "page": page,
  96. "page_size": page_size,
  97. "total_items": total_items,
  98. "total_pages": total_pages
  99. }
  100. @csrf_exempt
  101. @require_POST
  102. def trigger_backup(request):
  103. """手动触发备份的API接口"""
  104. try:
  105. backup_path = perform_base_backup()
  106. return JsonResponse({
  107. 'status': 'success',
  108. 'message': '数据库备份完成',
  109. 'path': backup_path
  110. })
  111. except Exception as e:
  112. return JsonResponse({
  113. 'status': 'error',
  114. 'message': str(e)
  115. }, status=500)
  116. @csrf_exempt
  117. @require_POST
  118. def list_backups(request):
  119. """获取备份文件列表API(带分页)"""
  120. try:
  121. data = json.loads(request.body)
  122. backup_type = data.get('type', 'file')
  123. page = data.get('page', 1)
  124. page_size = data.get('page_size', 5)
  125. if backup_type == 'file':
  126. result = get_backup_files(page, page_size)
  127. elif backup_type == 'base':
  128. result = get_base_backups(page, page_size)
  129. else:
  130. return JsonResponse({
  131. 'status': 'error',
  132. 'message': '无效的备份类型'
  133. }, status=400)
  134. return JsonResponse({
  135. 'status': 'success',
  136. 'data': result
  137. })
  138. except Exception as e:
  139. logger.error(f"获取备份列表失败: {str(e)}")
  140. return JsonResponse({
  141. 'status': 'error',
  142. 'message': str(e)
  143. }, status=500)
  144. @csrf_exempt
  145. @require_POST
  146. def restore_to_point(request):
  147. """执行时间点恢复API"""
  148. try:
  149. data = json.loads(request.body)
  150. base_backup = data.get('base_backup')
  151. if not base_backup or not os.path.exists(base_backup):
  152. return JsonResponse({
  153. 'status': 'error',
  154. 'message': '无效的基础备份路径'
  155. }, status=400)
  156. # 暂停定时备份任务
  157. scheduler.pause_job('db_backup_job')
  158. logger.info("定时备份任务已暂停")
  159. # 执行时间点恢复
  160. restore_to_base_backup( base_backup)
  161. # 恢复定时备份任务
  162. scheduler.resume_job('db_backup_job')
  163. logger.info("定时备份任务已恢复")
  164. return JsonResponse({
  165. 'status': 'success',
  166. 'message': f'已成功恢复到{base_backup}'
  167. })
  168. except Exception as e:
  169. logger.error(f"时间点恢复失败: {str(e)}")
  170. # 确保恢复定时备份任务
  171. if scheduler.get_job('db_backup_job') and scheduler.get_job('db_backup_job').next_run_time is None:
  172. scheduler.resume_job('db_backup_job')
  173. logger.info("恢复失败后定时备份任务已恢复")
  174. return JsonResponse({
  175. 'status': 'error',
  176. 'message': str(e)
  177. }, status=500)