redis_store.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import json
  2. import logging
  3. import time
  4. from typing import Any, Optional
  5. import redis
  6. from app.config import settings
  7. from app.repository.record_interface import RecordInterface
  8. from app.repository.task_record import TaskRecord
  9. logger = logging.getLogger(__name__)
  10. TASK_KEY_PREFIX = "task:"
  11. class RedisRecordStore(RecordInterface):
  12. def __init__(self):
  13. self._client = redis.Redis(
  14. host=settings.redis_host,
  15. port=settings.redis_port,
  16. password=settings.redis_password,
  17. db=settings.redis_db,
  18. socket_timeout=settings.redis_timeout / 1000,
  19. decode_responses=True,
  20. )
  21. def _on_save(self, record: TaskRecord):
  22. try:
  23. self._client.set(
  24. TASK_KEY_PREFIX + record.task_id, json.dumps(record.to_dict())
  25. )
  26. except Exception as e:
  27. logger.error("Failed to save task record: %s", e)
  28. def _on_get(self, task_id: str) -> Optional[TaskRecord]:
  29. try:
  30. data = self._client.get(TASK_KEY_PREFIX + task_id)
  31. if data:
  32. return TaskRecord.from_dict(json.loads(data))
  33. return None
  34. except Exception as e:
  35. logger.error("Failed to get task record: %s", e)
  36. return None
  37. def _on_get_all(self) -> dict[str, TaskRecord]:
  38. result: dict[str, TaskRecord] = {}
  39. try:
  40. keys = self._client.keys(TASK_KEY_PREFIX + "*")
  41. for key in keys or []:
  42. task_id = key[len(TASK_KEY_PREFIX):]
  43. data = self._client.get(key)
  44. if data:
  45. result[task_id] = TaskRecord.from_dict(json.loads(data))
  46. except Exception as e:
  47. logger.error("Failed to get all tasks: %s", e)
  48. return result
  49. def _on_delete(self, max_age_hours: int):
  50. try:
  51. cutoff = int(time.time() * 1000) - max_age_hours * 3600 * 1000
  52. all_records = self._on_get_all()
  53. for task_id, record in all_records.items():
  54. if record.status in ("successes", "failed") and record.update_time < cutoff:
  55. self._client.delete(TASK_KEY_PREFIX + task_id)
  56. except Exception as e:
  57. logger.error("Failed to cleanup completed tasks: %s", e)
  58. def _on_update_status(
  59. self, task_id: str, status: str, message: str, data: Optional[dict]
  60. ):
  61. try:
  62. record = self._on_get(task_id)
  63. if record:
  64. record.status = status
  65. record.message = message
  66. record.touch()
  67. if data is not None:
  68. record.data = data
  69. self._on_save(record)
  70. except Exception as e:
  71. logger.error("Failed to update task status: %s", e)
  72. def _on_publish(self, task_id: str):
  73. if not settings.redis_publish_enabled:
  74. return
  75. try:
  76. record = self._on_get(task_id)
  77. if record:
  78. payload = json.dumps(record.to_dict(), ensure_ascii=False)
  79. receivers = self._client.publish(settings.redis_publish_channel, payload)
  80. logger.info("[%s] Published to channel '%s', receivers=%d",
  81. task_id, settings.redis_publish_channel, receivers)
  82. except Exception as e:
  83. logger.error("[%s] Failed to publish task event: %s", task_id, e)