calendar.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  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.orm import Session
  9. from sqlalchemy import create_engine, text
  10. import json
  11. import requests
  12. from app import db
  13. from .calendar_config import CALENDAR_API_CONFIG
  14. class CalendarInfo(db.Model):
  15. """
  16. 黄历信息表数据模型
  17. 对应数据库表: public.calendar_info
  18. 表注释: 黄历信息表
  19. """
  20. __tablename__ = 'calendar_info'
  21. __table_args__ = {'schema': 'public'}
  22. # 主键ID (serial primary key)
  23. id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键ID')
  24. # 阳历日期 (date not null)
  25. yangli = db.Column(db.Date, nullable=False, comment='阳历日期')
  26. # 阴历日期 (text not null)
  27. yinli = db.Column(db.Text, nullable=False, comment='阴历日期')
  28. # 五行 (text)
  29. wuxing = db.Column(db.Text, nullable=True, comment='五行')
  30. # 冲煞 (text)
  31. chongsha = db.Column(db.Text, nullable=True, comment='冲煞')
  32. # 彭祖百忌 (text)
  33. baiji = db.Column(db.Text, nullable=True, comment='彭祖百忌')
  34. # 吉神宜趋 (text)
  35. jishen = db.Column(db.Text, nullable=True, comment='吉神宜趋')
  36. # 宜 (text)
  37. yi = db.Column(db.Text, nullable=True, comment='宜')
  38. # 凶神宜忌 (text)
  39. xiongshen = db.Column(db.Text, nullable=True, comment='凶神宜忌')
  40. # 忌 (text)
  41. ji = db.Column(db.Text, nullable=True, comment='忌')
  42. def __init__(self, **kwargs):
  43. super().__init__(**kwargs)
  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, # type: ignore
  89. yinli=data.get('yinli'), # type: ignore
  90. wuxing=data.get('wuxing'), # type: ignore
  91. chongsha=data.get('chongsha'), # type: ignore
  92. baiji=data.get('baiji'), # type: ignore
  93. jishen=data.get('jishen'), # type: ignore
  94. yi=data.get('yi'), # type: ignore
  95. xiongshen=data.get('xiongshen'), # type: ignore
  96. ji=data.get('ji') # type: ignore
  97. )
  98. class CalendarService:
  99. """
  100. 黄历信息服务类
  101. 提供黄历信息的增删改查操作
  102. """
  103. def __init__(self, engine=None):
  104. """
  105. 初始化服务
  106. Args:
  107. engine: SQLAlchemy引擎对象,如果为None则使用Flask-SQLAlchemy的db.session
  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. if self.engine:
  120. with Session(self.engine) as session:
  121. session.add(calendar_info)
  122. session.commit()
  123. session.refresh(calendar_info)
  124. else:
  125. # 使用Flask-SQLAlchemy的db.session
  126. db.session.add(calendar_info)
  127. db.session.commit()
  128. db.session.refresh(calendar_info)
  129. return calendar_info
  130. def get_calendar_by_date(self, yangli_date: date) -> Optional[CalendarInfo]:
  131. """
  132. 根据阳历日期查询黄历信息
  133. Args:
  134. yangli_date (date): 阳历日期
  135. Returns:
  136. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  137. """
  138. if self.engine:
  139. with Session(self.engine) as session:
  140. return session.query(CalendarInfo).filter(
  141. CalendarInfo.yangli == yangli_date
  142. ).first()
  143. else:
  144. # 使用Flask-SQLAlchemy的db.session
  145. return CalendarInfo.query.filter(
  146. CalendarInfo.yangli == yangli_date
  147. ).first()
  148. def get_calendar_by_id(self, calendar_id: int) -> Optional[CalendarInfo]:
  149. """
  150. 根据ID查询黄历信息
  151. Args:
  152. calendar_id (int): 黄历信息ID
  153. Returns:
  154. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  155. """
  156. if self.engine:
  157. with Session(self.engine) as session:
  158. return session.query(CalendarInfo).filter(
  159. CalendarInfo.id == calendar_id
  160. ).first()
  161. else:
  162. # 使用Flask-SQLAlchemy的db.session
  163. return CalendarInfo.query.filter(
  164. CalendarInfo.id == calendar_id
  165. ).first()
  166. def update_calendar_info(self, calendar_id: int, update_data: dict) -> Optional[CalendarInfo]:
  167. """
  168. 更新黄历信息
  169. Args:
  170. calendar_id (int): 黄历信息ID
  171. update_data (dict): 要更新的数据
  172. Returns:
  173. Optional[CalendarInfo]: 更新后的黄历信息对象,如果不存在则返回None
  174. """
  175. if self.engine:
  176. with Session(self.engine) as session:
  177. calendar_info = session.query(CalendarInfo).filter(
  178. CalendarInfo.id == calendar_id
  179. ).first()
  180. if not calendar_info:
  181. return None
  182. # 更新字段
  183. for key, value in update_data.items():
  184. if hasattr(calendar_info, key):
  185. if key == 'yangli' and isinstance(value, str):
  186. try:
  187. value = date.fromisoformat(value)
  188. except ValueError:
  189. continue
  190. setattr(calendar_info, key, value)
  191. session.commit()
  192. session.refresh(calendar_info)
  193. else:
  194. # 使用Flask-SQLAlchemy的db.session
  195. calendar_info = CalendarInfo.query.filter(
  196. CalendarInfo.id == calendar_id
  197. ).first()
  198. if not calendar_info:
  199. return None
  200. # 更新字段
  201. for key, value in update_data.items():
  202. if hasattr(calendar_info, key):
  203. if key == 'yangli' and isinstance(value, str):
  204. try:
  205. value = date.fromisoformat(value)
  206. except ValueError:
  207. continue
  208. setattr(calendar_info, key, value)
  209. db.session.commit()
  210. db.session.refresh(calendar_info)
  211. return calendar_info
  212. def delete_calendar_info(self, calendar_id: int) -> bool:
  213. """
  214. 删除黄历信息
  215. Args:
  216. calendar_id (int): 黄历信息ID
  217. Returns:
  218. bool: 删除成功返回True,否则返回False
  219. """
  220. if self.engine:
  221. with Session(self.engine) as session:
  222. calendar_info = session.query(CalendarInfo).filter(
  223. CalendarInfo.id == calendar_id
  224. ).first()
  225. if not calendar_info:
  226. return False
  227. session.delete(calendar_info)
  228. session.commit()
  229. else:
  230. # 使用Flask-SQLAlchemy的db.session
  231. calendar_info = CalendarInfo.query.filter(
  232. CalendarInfo.id == calendar_id
  233. ).first()
  234. if not calendar_info:
  235. return False
  236. db.session.delete(calendar_info)
  237. db.session.commit()
  238. return True
  239. def get_calendar_list(self, limit: int = 100, offset: int = 0) -> list[CalendarInfo]:
  240. """
  241. 获取黄历信息列表
  242. Args:
  243. limit (int): 限制返回数量,默认100
  244. offset (int): 偏移量,默认0
  245. Returns:
  246. list[CalendarInfo]: 黄历信息对象列表
  247. """
  248. if self.engine:
  249. with Session(self.engine) as session:
  250. return session.query(CalendarInfo).order_by(
  251. CalendarInfo.yangli.desc()
  252. ).offset(offset).limit(limit).all()
  253. else:
  254. # 使用Flask-SQLAlchemy的db.session
  255. return CalendarInfo.query.order_by(
  256. CalendarInfo.yangli.desc()
  257. ).offset(offset).limit(limit).all()
  258. def search_calendar_by_keyword(self, keyword: str, limit: int = 100) -> list[CalendarInfo]:
  259. """
  260. 根据关键词搜索黄历信息
  261. Args:
  262. keyword (str): 搜索关键词
  263. limit (int): 限制返回数量,默认100
  264. Returns:
  265. list[CalendarInfo]: 匹配的黄历信息对象列表
  266. """
  267. if self.engine:
  268. with Session(self.engine) as session:
  269. return session.query(CalendarInfo).filter(
  270. (CalendarInfo.yinli.contains(keyword)) |
  271. (CalendarInfo.wuxing.contains(keyword)) |
  272. (CalendarInfo.chongsha.contains(keyword)) |
  273. (CalendarInfo.baiji.contains(keyword)) |
  274. (CalendarInfo.jishen.contains(keyword)) |
  275. (CalendarInfo.yi.contains(keyword)) |
  276. (CalendarInfo.xiongshen.contains(keyword)) |
  277. (CalendarInfo.ji.contains(keyword))
  278. ).limit(limit).all()
  279. else:
  280. # 使用Flask-SQLAlchemy的db.session
  281. return CalendarInfo.query.filter(
  282. (CalendarInfo.yinli.contains(keyword)) |
  283. (CalendarInfo.wuxing.contains(keyword)) |
  284. (CalendarInfo.chongsha.contains(keyword)) |
  285. (CalendarInfo.baiji.contains(keyword)) |
  286. (CalendarInfo.jishen.contains(keyword)) |
  287. (CalendarInfo.yi.contains(keyword)) |
  288. (CalendarInfo.xiongshen.contains(keyword)) |
  289. (CalendarInfo.ji.contains(keyword))
  290. ).limit(limit).all()
  291. def fetch_calendar_from_api(self, yangli_date: date) -> Optional[dict]:
  292. """
  293. 从外部API获取黄历信息
  294. Args:
  295. yangli_date (date): 阳历日期
  296. Returns:
  297. Optional[dict]: API返回的黄历信息,如果失败则返回None
  298. """
  299. try:
  300. # 从配置文件获取API配置
  301. api_url = CALENDAR_API_CONFIG['url']
  302. api_key = CALENDAR_API_CONFIG['key']
  303. timeout = CALENDAR_API_CONFIG['timeout']
  304. # 格式化日期为YYYYMMDD格式
  305. date_str = yangli_date.strftime('%Y%m%d')
  306. # 请求参数
  307. request_params = {
  308. 'key': api_key,
  309. 'date': date_str,
  310. }
  311. # 发起API请求
  312. response = requests.get(api_url, params=request_params, timeout=timeout)
  313. if response.status_code == 200:
  314. response_result = response.json()
  315. # 检查API返回结果
  316. if response_result.get('error_code') == 0 and response_result.get('reason') == 'successed':
  317. return response_result.get('result')
  318. else:
  319. print(f"API返回错误: {response_result}")
  320. return None
  321. else:
  322. print(f"API请求失败,状态码: {response.status_code}")
  323. return None
  324. except requests.exceptions.RequestException as e:
  325. print(f"API请求异常: {e}")
  326. return None
  327. except Exception as e:
  328. print(f"获取API数据时发生错误: {e}")
  329. return None
  330. def save_calendar_from_api(self, api_data: dict) -> Optional[CalendarInfo]:
  331. """
  332. 将API返回的黄历信息保存到数据库
  333. Args:
  334. api_data (dict): API返回的黄历信息数据
  335. Returns:
  336. Optional[CalendarInfo]: 保存后的黄历信息对象,如果失败则返回None
  337. """
  338. try:
  339. # 解析API数据
  340. yangli_str = api_data.get('yangli')
  341. if not yangli_str:
  342. print("API数据中缺少阳历日期")
  343. return None
  344. # 解析日期
  345. try:
  346. yangli_date = date.fromisoformat(yangli_str)
  347. except ValueError:
  348. print(f"无效的日期格式: {yangli_str}")
  349. return None
  350. # 创建CalendarInfo对象
  351. calendar_info = CalendarInfo(
  352. yangli=yangli_date, # type: ignore
  353. yinli=api_data.get('yinli', ''), # type: ignore
  354. wuxing=api_data.get('wuxing'), # type: ignore
  355. chongsha=api_data.get('chongsha'), # type: ignore
  356. baiji=api_data.get('baiji'), # type: ignore
  357. jishen=api_data.get('jishen'), # type: ignore
  358. yi=api_data.get('yi'), # type: ignore
  359. xiongshen=api_data.get('xionshen'), # type: ignore # 注意API返回的是xionshen
  360. ji=api_data.get('ji') # type: ignore
  361. )
  362. # 保存到数据库
  363. if self.engine:
  364. with Session(self.engine) as session:
  365. session.add(calendar_info)
  366. session.commit()
  367. session.refresh(calendar_info)
  368. else:
  369. # 使用Flask-SQLAlchemy的db.session
  370. db.session.add(calendar_info)
  371. db.session.commit()
  372. db.session.refresh(calendar_info)
  373. print(f"成功保存黄历信息到数据库,ID: {calendar_info.id}")
  374. return calendar_info
  375. except Exception as e:
  376. print(f"保存API数据到数据库时发生错误: {e}")
  377. return None
  378. # 便捷函数
  379. def create_calendar_info(calendar_data: dict, engine=None) -> CalendarInfo:
  380. """
  381. 创建黄历信息的便捷函数
  382. Args:
  383. calendar_data (dict): 黄历信息数据
  384. engine: SQLAlchemy引擎对象
  385. Returns:
  386. CalendarInfo: 创建的黄历信息对象
  387. """
  388. service = CalendarService(engine)
  389. return service.create_calendar_info(calendar_data)
  390. def get_calendar_by_date(yangli_date: str, engine=None) -> dict:
  391. """
  392. 根据阳历日期查询黄历信息的便捷函数
  393. Args:
  394. yangli_date (str): 阳历日期,格式为YYYY-MM-DD
  395. engine: SQLAlchemy引擎对象
  396. Returns:
  397. dict: 包含查询结果的JSON格式数据
  398. """
  399. try:
  400. # 验证日期格式
  401. if not isinstance(yangli_date, str) or len(yangli_date) != 10:
  402. return {
  403. "reason": "failed",
  404. "return_code": 400,
  405. "result": None,
  406. "error": "日期格式错误,请使用YYYY-MM-DD格式"
  407. }
  408. # 解析日期字符串
  409. try:
  410. parsed_date = date.fromisoformat(yangli_date)
  411. except ValueError:
  412. return {
  413. "reason": "failed",
  414. "return_code": 400,
  415. "result": None,
  416. "error": "无效的日期格式"
  417. }
  418. # 查询数据库
  419. service = CalendarService(engine)
  420. calendar_info = service.get_calendar_by_date(parsed_date)
  421. if calendar_info:
  422. # 查询成功,返回指定格式的JSON数据
  423. return {
  424. "reason": "successed",
  425. "return_code": 200,
  426. "result": {
  427. "id": str(calendar_info.id),
  428. "yangli": calendar_info.yangli.isoformat() if calendar_info.yangli is not None else None,
  429. "yinli": calendar_info.yinli,
  430. "wuxing": calendar_info.wuxing,
  431. "chongsha": calendar_info.chongsha,
  432. "baiji": calendar_info.baiji,
  433. "jishen": calendar_info.jishen,
  434. "yi": calendar_info.yi,
  435. "xiongshen": calendar_info.xiongshen,
  436. "ji": calendar_info.ji
  437. }
  438. }
  439. else:
  440. # 数据库中没有找到记录,尝试从API获取
  441. print(f"数据库中没有找到日期 {yangli_date} 的黄历信息,尝试从API获取...")
  442. # 从API获取数据
  443. api_data = service.fetch_calendar_from_api(parsed_date)
  444. if api_data:
  445. # API获取成功,保存到数据库
  446. print("API获取数据成功,正在保存到数据库...")
  447. saved_calendar = service.save_calendar_from_api(api_data)
  448. if saved_calendar:
  449. # 保存成功,返回数据
  450. return {
  451. "reason": "successed",
  452. "return_code": 200,
  453. "result": {
  454. "id": str(saved_calendar.id),
  455. "yangli": saved_calendar.yangli.isoformat() if saved_calendar.yangli is not None else None,
  456. "yinli": saved_calendar.yinli,
  457. "wuxing": saved_calendar.wuxing,
  458. "chongsha": saved_calendar.chongsha,
  459. "baiji": saved_calendar.baiji,
  460. "jishen": saved_calendar.jishen,
  461. "yi": saved_calendar.yi,
  462. "xiongshen": saved_calendar.xiongshen,
  463. "ji": saved_calendar.ji
  464. }
  465. }
  466. else:
  467. # 保存到数据库失败
  468. return {
  469. "reason": "failed",
  470. "return_code": 500,
  471. "result": None,
  472. "error": f"API获取数据成功但保存到数据库失败"
  473. }
  474. else:
  475. # API获取失败
  476. return {
  477. "reason": "failed",
  478. "return_code": 404,
  479. "result": None,
  480. "error": f"未找到日期 {yangli_date} 的黄历信息,且API获取失败"
  481. }
  482. except Exception as e:
  483. # 发生异常
  484. return {
  485. "reason": "failed",
  486. "return_code": 500,
  487. "result": None,
  488. "error": f"查询过程中发生错误: {str(e)}"
  489. }
  490. def get_calendar_by_id(calendar_id: int, engine=None) -> Optional[CalendarInfo]:
  491. """
  492. 根据ID查询黄历信息的便捷函数
  493. Args:
  494. calendar_id (int): 黄历信息ID
  495. engine: SQLAlchemy引擎对象
  496. Returns:
  497. Optional[CalendarInfo]: 黄历信息对象
  498. """
  499. service = CalendarService(engine)
  500. return service.get_calendar_by_id(calendar_id)
  501. # 导出主要类和函数
  502. __all__ = [
  503. 'CalendarInfo',
  504. 'CalendarService',
  505. 'create_calendar_info',
  506. 'get_calendar_by_date',
  507. 'get_calendar_by_id'
  508. ]