calendar.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. """
  2. 黄历信息数据模型
  3. 基于 create_calendar_info.sql 中的DDL定义创建
  4. """
  5. from datetime import date
  6. from typing import Optional
  7. from sqlalchemy import Column, Integer, Date, Text, String
  8. from sqlalchemy.ext.declarative import declarative_base
  9. from sqlalchemy.orm import Session
  10. from sqlalchemy import create_engine, text
  11. import json
  12. import requests
  13. from .calendar_config import CALENDAR_API_CONFIG
  14. # 创建基础模型类
  15. Base = declarative_base()
  16. class CalendarInfo(Base):
  17. """
  18. 黄历信息表数据模型
  19. 对应数据库表: public.calendar_info
  20. 表注释: 黄历信息表
  21. """
  22. __tablename__ = 'calendar_info'
  23. __table_args__ = {'schema': 'public'}
  24. # 主键ID (serial primary key)
  25. id = Column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
  26. # 阳历日期 (date not null)
  27. yangli = Column(Date, nullable=False, comment='阳历日期')
  28. # 阴历日期 (text not null)
  29. yinli = Column(Text, nullable=False, comment='阴历日期')
  30. # 五行 (text)
  31. wuxing = Column(Text, nullable=True, comment='五行')
  32. # 冲煞 (text)
  33. chongsha = Column(Text, nullable=True, comment='冲煞')
  34. # 彭祖百忌 (text)
  35. baiji = Column(Text, nullable=True, comment='彭祖百忌')
  36. # 吉神宜趋 (text)
  37. jishen = Column(Text, nullable=True, comment='吉神宜趋')
  38. # 宜 (text)
  39. yi = Column(Text, nullable=True, comment='宜')
  40. # 凶神宜忌 (text)
  41. xiongshen = Column(Text, nullable=True, comment='凶神宜忌')
  42. # 忌 (text)
  43. ji = Column(Text, nullable=True, comment='忌')
  44. def __repr__(self):
  45. return f"<CalendarInfo(id={self.id}, yangli='{self.yangli}', yinli='{self.yinli}')>"
  46. def to_dict(self) -> dict:
  47. """
  48. 将模型对象转换为字典
  49. Returns:
  50. dict: 包含所有字段的字典
  51. """
  52. return {
  53. 'id': self.id,
  54. 'yangli': self.yangli.isoformat() if self.yangli is not None else None,
  55. 'yinli': self.yinli,
  56. 'wuxing': self.wuxing,
  57. 'chongsha': self.chongsha,
  58. 'baiji': self.baiji,
  59. 'jishen': self.jishen,
  60. 'yi': self.yi,
  61. 'xiongshen': self.xiongshen,
  62. 'ji': self.ji
  63. }
  64. def to_json(self) -> str:
  65. """
  66. 将模型对象转换为JSON字符串
  67. Returns:
  68. str: JSON格式的字符串
  69. """
  70. return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
  71. @classmethod
  72. def from_dict(cls, data: dict) -> 'CalendarInfo':
  73. """
  74. 从字典创建模型对象
  75. Args:
  76. data (dict): 包含字段数据的字典
  77. Returns:
  78. CalendarInfo: 新创建的模型对象
  79. """
  80. # 处理日期字段
  81. yangli = data.get('yangli')
  82. if isinstance(yangli, str):
  83. try:
  84. yangli = date.fromisoformat(yangli)
  85. except ValueError:
  86. yangli = None
  87. return cls(
  88. yangli=yangli,
  89. yinli=data.get('yinli'),
  90. wuxing=data.get('wuxing'),
  91. chongsha=data.get('chongsha'),
  92. baiji=data.get('baiji'),
  93. jishen=data.get('jishen'),
  94. yi=data.get('yi'),
  95. xiongshen=data.get('xiongshen'),
  96. ji=data.get('ji')
  97. )
  98. class CalendarService:
  99. """
  100. 黄历信息服务类
  101. 提供黄历信息的增删改查操作
  102. """
  103. def __init__(self, engine=None):
  104. """
  105. 初始化服务
  106. Args:
  107. engine: SQLAlchemy引擎对象,如果为None则使用默认配置
  108. """
  109. self.engine = engine
  110. def create_calendar_info(self, calendar_data: dict) -> CalendarInfo:
  111. """
  112. 创建新的黄历信息记录
  113. Args:
  114. calendar_data (dict): 黄历信息数据
  115. Returns:
  116. CalendarInfo: 创建的黄历信息对象
  117. """
  118. calendar_info = CalendarInfo.from_dict(calendar_data)
  119. with Session(self.engine) as session:
  120. session.add(calendar_info)
  121. session.commit()
  122. session.refresh(calendar_info)
  123. return calendar_info
  124. def get_calendar_by_date(self, yangli_date: date) -> Optional[CalendarInfo]:
  125. """
  126. 根据阳历日期查询黄历信息
  127. Args:
  128. yangli_date (date): 阳历日期
  129. Returns:
  130. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  131. """
  132. with Session(self.engine) as session:
  133. return session.query(CalendarInfo).filter(
  134. CalendarInfo.yangli == yangli_date
  135. ).first()
  136. def get_calendar_by_id(self, calendar_id: int) -> Optional[CalendarInfo]:
  137. """
  138. 根据ID查询黄历信息
  139. Args:
  140. calendar_id (int): 黄历信息ID
  141. Returns:
  142. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  143. """
  144. with Session(self.engine) as session:
  145. return session.query(CalendarInfo).filter(
  146. CalendarInfo.id == calendar_id
  147. ).first()
  148. def update_calendar_info(self, calendar_id: int, update_data: dict) -> Optional[CalendarInfo]:
  149. """
  150. 更新黄历信息
  151. Args:
  152. calendar_id (int): 黄历信息ID
  153. update_data (dict): 要更新的数据
  154. Returns:
  155. Optional[CalendarInfo]: 更新后的黄历信息对象,如果不存在则返回None
  156. """
  157. with Session(self.engine) as session:
  158. calendar_info = session.query(CalendarInfo).filter(
  159. CalendarInfo.id == calendar_id
  160. ).first()
  161. if not calendar_info:
  162. return None
  163. # 更新字段
  164. for key, value in update_data.items():
  165. if hasattr(calendar_info, key):
  166. if key == 'yangli' and isinstance(value, str):
  167. try:
  168. value = date.fromisoformat(value)
  169. except ValueError:
  170. continue
  171. setattr(calendar_info, key, value)
  172. session.commit()
  173. session.refresh(calendar_info)
  174. return calendar_info
  175. def delete_calendar_info(self, calendar_id: int) -> bool:
  176. """
  177. 删除黄历信息
  178. Args:
  179. calendar_id (int): 黄历信息ID
  180. Returns:
  181. bool: 删除成功返回True,否则返回False
  182. """
  183. with Session(self.engine) as session:
  184. calendar_info = session.query(CalendarInfo).filter(
  185. CalendarInfo.id == calendar_id
  186. ).first()
  187. if not calendar_info:
  188. return False
  189. session.delete(calendar_info)
  190. session.commit()
  191. return True
  192. def get_calendar_list(self, limit: int = 100, offset: int = 0) -> list[CalendarInfo]:
  193. """
  194. 获取黄历信息列表
  195. Args:
  196. limit (int): 限制返回数量,默认100
  197. offset (int): 偏移量,默认0
  198. Returns:
  199. list[CalendarInfo]: 黄历信息对象列表
  200. """
  201. with Session(self.engine) as session:
  202. return session.query(CalendarInfo).order_by(
  203. CalendarInfo.yangli.desc()
  204. ).offset(offset).limit(limit).all()
  205. def search_calendar_by_keyword(self, keyword: str, limit: int = 100) -> list[CalendarInfo]:
  206. """
  207. 根据关键词搜索黄历信息
  208. Args:
  209. keyword (str): 搜索关键词
  210. limit (int): 限制返回数量,默认100
  211. Returns:
  212. list[CalendarInfo]: 匹配的黄历信息对象列表
  213. """
  214. with Session(self.engine) as session:
  215. return session.query(CalendarInfo).filter(
  216. (CalendarInfo.yinli.contains(keyword)) |
  217. (CalendarInfo.wuxing.contains(keyword)) |
  218. (CalendarInfo.chongsha.contains(keyword)) |
  219. (CalendarInfo.baiji.contains(keyword)) |
  220. (CalendarInfo.jishen.contains(keyword)) |
  221. (CalendarInfo.yi.contains(keyword)) |
  222. (CalendarInfo.xiongshen.contains(keyword)) |
  223. (CalendarInfo.ji.contains(keyword))
  224. ).limit(limit).all()
  225. def fetch_calendar_from_api(self, yangli_date: date) -> Optional[dict]:
  226. """
  227. 从外部API获取黄历信息
  228. Args:
  229. yangli_date (date): 阳历日期
  230. Returns:
  231. Optional[dict]: API返回的黄历信息,如果失败则返回None
  232. """
  233. try:
  234. # 从配置文件获取API配置
  235. api_url = CALENDAR_API_CONFIG['url']
  236. api_key = CALENDAR_API_CONFIG['key']
  237. timeout = CALENDAR_API_CONFIG['timeout']
  238. # 格式化日期为YYYYMMDD格式
  239. date_str = yangli_date.strftime('%Y%m%d')
  240. # 请求参数
  241. request_params = {
  242. 'key': api_key,
  243. 'date': date_str,
  244. }
  245. # 发起API请求
  246. response = requests.get(api_url, params=request_params, timeout=timeout)
  247. if response.status_code == 200:
  248. response_result = response.json()
  249. # 检查API返回结果
  250. if response_result.get('error_code') == 0 and response_result.get('reason') == 'successed':
  251. return response_result.get('result')
  252. else:
  253. print(f"API返回错误: {response_result}")
  254. return None
  255. else:
  256. print(f"API请求失败,状态码: {response.status_code}")
  257. return None
  258. except requests.exceptions.RequestException as e:
  259. print(f"API请求异常: {e}")
  260. return None
  261. except Exception as e:
  262. print(f"获取API数据时发生错误: {e}")
  263. return None
  264. def save_calendar_from_api(self, api_data: dict) -> Optional[CalendarInfo]:
  265. """
  266. 将API返回的黄历信息保存到数据库
  267. Args:
  268. api_data (dict): API返回的黄历信息数据
  269. Returns:
  270. Optional[CalendarInfo]: 保存后的黄历信息对象,如果失败则返回None
  271. """
  272. try:
  273. # 解析API数据
  274. yangli_str = api_data.get('yangli')
  275. if not yangli_str:
  276. print("API数据中缺少阳历日期")
  277. return None
  278. # 解析日期
  279. try:
  280. yangli_date = date.fromisoformat(yangli_str)
  281. except ValueError:
  282. print(f"无效的日期格式: {yangli_str}")
  283. return None
  284. # 创建CalendarInfo对象
  285. calendar_info = CalendarInfo(
  286. yangli=yangli_date,
  287. yinli=api_data.get('yinli', ''),
  288. wuxing=api_data.get('wuxing'),
  289. chongsha=api_data.get('chongsha'),
  290. baiji=api_data.get('baiji'),
  291. jishen=api_data.get('jishen'),
  292. yi=api_data.get('yi'),
  293. xiongshen=api_data.get('xionshen'), # 注意API返回的是xionshen
  294. ji=api_data.get('ji')
  295. )
  296. # 保存到数据库
  297. with Session(self.engine) as session:
  298. session.add(calendar_info)
  299. session.commit()
  300. session.refresh(calendar_info)
  301. print(f"成功保存黄历信息到数据库,ID: {calendar_info.id}")
  302. return calendar_info
  303. except Exception as e:
  304. print(f"保存API数据到数据库时发生错误: {e}")
  305. return None
  306. # 便捷函数
  307. def create_calendar_info(calendar_data: dict, engine=None) -> CalendarInfo:
  308. """
  309. 创建黄历信息的便捷函数
  310. Args:
  311. calendar_data (dict): 黄历信息数据
  312. engine: SQLAlchemy引擎对象
  313. Returns:
  314. CalendarInfo: 创建的黄历信息对象
  315. """
  316. service = CalendarService(engine)
  317. return service.create_calendar_info(calendar_data)
  318. def get_calendar_by_date(yangli_date: str, engine=None) -> dict:
  319. """
  320. 根据阳历日期查询黄历信息的便捷函数
  321. Args:
  322. yangli_date (str): 阳历日期,格式为YYYY-MM-DD
  323. engine: SQLAlchemy引擎对象
  324. Returns:
  325. dict: 包含查询结果的JSON格式数据
  326. """
  327. try:
  328. # 验证日期格式
  329. if not isinstance(yangli_date, str) or len(yangli_date) != 10:
  330. return {
  331. "reason": "failed",
  332. "return_code": 400,
  333. "result": None,
  334. "error": "日期格式错误,请使用YYYY-MM-DD格式"
  335. }
  336. # 解析日期字符串
  337. try:
  338. parsed_date = date.fromisoformat(yangli_date)
  339. except ValueError:
  340. return {
  341. "reason": "failed",
  342. "return_code": 400,
  343. "result": None,
  344. "error": "无效的日期格式"
  345. }
  346. # 查询数据库
  347. service = CalendarService(engine)
  348. calendar_info = service.get_calendar_by_date(parsed_date)
  349. if calendar_info:
  350. # 查询成功,返回指定格式的JSON数据
  351. return {
  352. "reason": "successed",
  353. "return_code": 200,
  354. "result": {
  355. "id": str(calendar_info.id),
  356. "yangli": calendar_info.yangli.isoformat() if calendar_info.yangli is not None else None,
  357. "yinli": calendar_info.yinli,
  358. "wuxing": calendar_info.wuxing,
  359. "chongsha": calendar_info.chongsha,
  360. "baiji": calendar_info.baiji,
  361. "jishen": calendar_info.jishen,
  362. "yi": calendar_info.yi,
  363. "xiongshen": calendar_info.xiongshen,
  364. "ji": calendar_info.ji
  365. }
  366. }
  367. else:
  368. # 数据库中没有找到记录,尝试从API获取
  369. print(f"数据库中没有找到日期 {yangli_date} 的黄历信息,尝试从API获取...")
  370. # 从API获取数据
  371. api_data = service.fetch_calendar_from_api(parsed_date)
  372. if api_data:
  373. # API获取成功,保存到数据库
  374. print("API获取数据成功,正在保存到数据库...")
  375. saved_calendar = service.save_calendar_from_api(api_data)
  376. if saved_calendar:
  377. # 保存成功,返回数据
  378. return {
  379. "reason": "successed",
  380. "return_code": 200,
  381. "result": {
  382. "id": str(saved_calendar.id),
  383. "yangli": saved_calendar.yangli.isoformat() if saved_calendar.yangli is not None else None,
  384. "yinli": saved_calendar.yinli,
  385. "wuxing": saved_calendar.wuxing,
  386. "chongsha": saved_calendar.chongsha,
  387. "baiji": saved_calendar.baiji,
  388. "jishen": saved_calendar.jishen,
  389. "yi": saved_calendar.yi,
  390. "xiongshen": saved_calendar.xiongshen,
  391. "ji": saved_calendar.ji
  392. }
  393. }
  394. else:
  395. # 保存到数据库失败
  396. return {
  397. "reason": "failed",
  398. "return_code": 500,
  399. "result": None,
  400. "error": f"API获取数据成功但保存到数据库失败"
  401. }
  402. else:
  403. # API获取失败
  404. return {
  405. "reason": "failed",
  406. "return_code": 404,
  407. "result": None,
  408. "error": f"未找到日期 {yangli_date} 的黄历信息,且API获取失败"
  409. }
  410. except Exception as e:
  411. # 发生异常
  412. return {
  413. "reason": "failed",
  414. "return_code": 500,
  415. "result": None,
  416. "error": f"查询过程中发生错误: {str(e)}"
  417. }
  418. def get_calendar_by_id(calendar_id: int, engine=None) -> Optional[CalendarInfo]:
  419. """
  420. 根据ID查询黄历信息的便捷函数
  421. Args:
  422. calendar_id (int): 黄历信息ID
  423. engine: SQLAlchemy引擎对象
  424. Returns:
  425. Optional[CalendarInfo]: 黄历信息对象
  426. """
  427. service = CalendarService(engine)
  428. return service.get_calendar_by_id(calendar_id)
  429. # 导出主要类和函数
  430. __all__ = [
  431. 'CalendarInfo',
  432. 'CalendarService',
  433. 'create_calendar_info',
  434. 'get_calendar_by_date',
  435. 'get_calendar_by_id'
  436. ]