views.py 6.5 KB

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