calendar.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  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. # 颜色 (varchar(10))
  43. color = db.Column(db.String(10), nullable=True, comment='颜色')
  44. def __init__(self, **kwargs):
  45. super().__init__(**kwargs)
  46. def __repr__(self):
  47. return f"<CalendarInfo(id={self.id}, yangli='{self.yangli}', yinli='{self.yinli}')>"
  48. def to_dict(self) -> dict:
  49. """
  50. 将模型对象转换为字典
  51. Returns:
  52. dict: 包含所有字段的字典
  53. """
  54. return {
  55. 'id': self.id,
  56. 'yangli': self.yangli.isoformat() if self.yangli is not None else None,
  57. 'yinli': self.yinli,
  58. 'wuxing': self.wuxing,
  59. 'chongsha': self.chongsha,
  60. 'baiji': self.baiji,
  61. 'jishen': self.jishen,
  62. 'yi': self.yi,
  63. 'xiongshen': self.xiongshen,
  64. 'ji': self.ji,
  65. 'color': self.color
  66. }
  67. def to_json(self) -> str:
  68. """
  69. 将模型对象转换为JSON字符串
  70. Returns:
  71. str: JSON格式的字符串
  72. """
  73. return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
  74. @classmethod
  75. def from_dict(cls, data: dict) -> 'CalendarInfo':
  76. """
  77. 从字典创建模型对象
  78. Args:
  79. data (dict): 包含字段数据的字典
  80. Returns:
  81. CalendarInfo: 新创建的模型对象
  82. """
  83. # 处理日期字段
  84. yangli = data.get('yangli')
  85. if isinstance(yangli, str):
  86. try:
  87. yangli = date.fromisoformat(yangli)
  88. except ValueError:
  89. yangli = None
  90. # 从wuxing字段中判断五行元素并设置对应的颜色值
  91. wuxing = data.get('wuxing', '') or ''
  92. color = data.get('color') # 先获取字典中的color值
  93. # 如果字典中没有color值,则根据wuxing字段判断五行元素设置颜色
  94. if not color:
  95. if '金' in wuxing:
  96. color = '白'
  97. elif '水' in wuxing:
  98. color = '黑'
  99. elif '木' in wuxing:
  100. color = '绿'
  101. elif '火' in wuxing:
  102. color = '红'
  103. elif '土' in wuxing:
  104. color = '黄'
  105. return cls(
  106. yangli=yangli, # type: ignore
  107. yinli=data.get('yinli'), # type: ignore
  108. wuxing=wuxing, # type: ignore
  109. chongsha=data.get('chongsha'), # type: ignore
  110. baiji=data.get('baiji'), # type: ignore
  111. jishen=data.get('jishen'), # type: ignore
  112. yi=data.get('yi'), # type: ignore
  113. xiongshen=data.get('xiongshen'), # type: ignore
  114. ji=data.get('ji'), # type: ignore
  115. color=color # type: ignore
  116. )
  117. class CalendarService:
  118. """
  119. 黄历信息服务类
  120. 提供黄历信息的增删改查操作
  121. """
  122. def __init__(self, engine=None):
  123. """
  124. 初始化服务
  125. Args:
  126. engine: SQLAlchemy引擎对象,如果为None则使用Flask-SQLAlchemy的db.session
  127. """
  128. self.engine = engine
  129. def create_calendar_info(self, calendar_data: dict) -> CalendarInfo:
  130. """
  131. 创建新的黄历信息记录
  132. Args:
  133. calendar_data (dict): 黄历信息数据
  134. Returns:
  135. CalendarInfo: 创建的黄历信息对象
  136. """
  137. calendar_info = CalendarInfo.from_dict(calendar_data)
  138. if self.engine:
  139. with Session(self.engine) as session:
  140. session.add(calendar_info)
  141. session.commit()
  142. session.refresh(calendar_info)
  143. else:
  144. # 使用Flask-SQLAlchemy的db.session
  145. db.session.add(calendar_info)
  146. db.session.commit()
  147. db.session.refresh(calendar_info)
  148. return calendar_info
  149. def get_calendar_by_date(self, yangli_date: date) -> Optional[CalendarInfo]:
  150. """
  151. 根据阳历日期查询黄历信息
  152. Args:
  153. yangli_date (date): 阳历日期
  154. Returns:
  155. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  156. """
  157. if self.engine:
  158. with Session(self.engine) as session:
  159. return session.query(CalendarInfo).filter(
  160. CalendarInfo.yangli == yangli_date
  161. ).first()
  162. else:
  163. # 使用Flask-SQLAlchemy的db.session
  164. return CalendarInfo.query.filter(
  165. CalendarInfo.yangli == yangli_date
  166. ).first()
  167. def get_calendar_by_id(self, calendar_id: int) -> Optional[CalendarInfo]:
  168. """
  169. 根据ID查询黄历信息
  170. Args:
  171. calendar_id (int): 黄历信息ID
  172. Returns:
  173. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  174. """
  175. if self.engine:
  176. with Session(self.engine) as session:
  177. return session.query(CalendarInfo).filter(
  178. CalendarInfo.id == calendar_id
  179. ).first()
  180. else:
  181. # 使用Flask-SQLAlchemy的db.session
  182. return CalendarInfo.query.filter(
  183. CalendarInfo.id == calendar_id
  184. ).first()
  185. def update_calendar_info(self, calendar_id: int, update_data: dict) -> Optional[CalendarInfo]:
  186. """
  187. 更新黄历信息
  188. Args:
  189. calendar_id (int): 黄历信息ID
  190. update_data (dict): 要更新的数据
  191. Returns:
  192. Optional[CalendarInfo]: 更新后的黄历信息对象,如果不存在则返回None
  193. """
  194. if self.engine:
  195. with Session(self.engine) as session:
  196. calendar_info = session.query(CalendarInfo).filter(
  197. CalendarInfo.id == calendar_id
  198. ).first()
  199. if not calendar_info:
  200. return None
  201. # 更新字段
  202. for key, value in update_data.items():
  203. if hasattr(calendar_info, key):
  204. if key == 'yangli' and isinstance(value, str):
  205. try:
  206. value = date.fromisoformat(value)
  207. except ValueError:
  208. continue
  209. setattr(calendar_info, key, value)
  210. session.commit()
  211. session.refresh(calendar_info)
  212. else:
  213. # 使用Flask-SQLAlchemy的db.session
  214. calendar_info = CalendarInfo.query.filter(
  215. CalendarInfo.id == calendar_id
  216. ).first()
  217. if not calendar_info:
  218. return None
  219. # 更新字段
  220. for key, value in update_data.items():
  221. if hasattr(calendar_info, key):
  222. if key == 'yangli' and isinstance(value, str):
  223. try:
  224. value = date.fromisoformat(value)
  225. except ValueError:
  226. continue
  227. setattr(calendar_info, key, value)
  228. db.session.commit()
  229. db.session.refresh(calendar_info)
  230. return calendar_info
  231. def delete_calendar_info(self, calendar_id: int) -> bool:
  232. """
  233. 删除黄历信息
  234. Args:
  235. calendar_id (int): 黄历信息ID
  236. Returns:
  237. bool: 删除成功返回True,否则返回False
  238. """
  239. if self.engine:
  240. with Session(self.engine) as session:
  241. calendar_info = session.query(CalendarInfo).filter(
  242. CalendarInfo.id == calendar_id
  243. ).first()
  244. if not calendar_info:
  245. return False
  246. session.delete(calendar_info)
  247. session.commit()
  248. else:
  249. # 使用Flask-SQLAlchemy的db.session
  250. calendar_info = CalendarInfo.query.filter(
  251. CalendarInfo.id == calendar_id
  252. ).first()
  253. if not calendar_info:
  254. return False
  255. db.session.delete(calendar_info)
  256. db.session.commit()
  257. return True
  258. def get_calendar_list(self, limit: int = 100, offset: int = 0) -> list[CalendarInfo]:
  259. """
  260. 获取黄历信息列表
  261. Args:
  262. limit (int): 限制返回数量,默认100
  263. offset (int): 偏移量,默认0
  264. Returns:
  265. list[CalendarInfo]: 黄历信息对象列表
  266. """
  267. if self.engine:
  268. with Session(self.engine) as session:
  269. return session.query(CalendarInfo).order_by(
  270. CalendarInfo.yangli.desc()
  271. ).offset(offset).limit(limit).all()
  272. else:
  273. # 使用Flask-SQLAlchemy的db.session
  274. return CalendarInfo.query.order_by(
  275. CalendarInfo.yangli.desc()
  276. ).offset(offset).limit(limit).all()
  277. def search_calendar_by_keyword(self, keyword: str, limit: int = 100) -> list[CalendarInfo]:
  278. """
  279. 根据关键词搜索黄历信息
  280. Args:
  281. keyword (str): 搜索关键词
  282. limit (int): 限制返回数量,默认100
  283. Returns:
  284. list[CalendarInfo]: 匹配的黄历信息对象列表
  285. """
  286. if self.engine:
  287. with Session(self.engine) as session:
  288. return session.query(CalendarInfo).filter(
  289. (CalendarInfo.yinli.contains(keyword)) |
  290. (CalendarInfo.wuxing.contains(keyword)) |
  291. (CalendarInfo.chongsha.contains(keyword)) |
  292. (CalendarInfo.baiji.contains(keyword)) |
  293. (CalendarInfo.jishen.contains(keyword)) |
  294. (CalendarInfo.yi.contains(keyword)) |
  295. (CalendarInfo.xiongshen.contains(keyword)) |
  296. (CalendarInfo.ji.contains(keyword)) |
  297. (CalendarInfo.color.contains(keyword))
  298. ).limit(limit).all()
  299. else:
  300. # 使用Flask-SQLAlchemy的db.session
  301. return CalendarInfo.query.filter(
  302. (CalendarInfo.yinli.contains(keyword)) |
  303. (CalendarInfo.wuxing.contains(keyword)) |
  304. (CalendarInfo.chongsha.contains(keyword)) |
  305. (CalendarInfo.baiji.contains(keyword)) |
  306. (CalendarInfo.jishen.contains(keyword)) |
  307. (CalendarInfo.yi.contains(keyword)) |
  308. (CalendarInfo.xiongshen.contains(keyword)) |
  309. (CalendarInfo.ji.contains(keyword)) |
  310. (CalendarInfo.color.contains(keyword))
  311. ).limit(limit).all()
  312. def fetch_calendar_from_api(self, yangli_date: date) -> Optional[dict]:
  313. """
  314. 从外部API获取黄历信息
  315. Args:
  316. yangli_date (date): 阳历日期
  317. Returns:
  318. Optional[dict]: API返回的黄历信息,如果失败则返回None
  319. """
  320. try:
  321. # 从配置文件获取API配置
  322. api_url = CALENDAR_API_CONFIG['url']
  323. api_key = CALENDAR_API_CONFIG['key']
  324. timeout = CALENDAR_API_CONFIG['timeout']
  325. # 格式化日期为YYYYMMDD格式
  326. date_str = yangli_date.strftime('%Y%m%d')
  327. # 请求参数
  328. request_params = {
  329. 'key': api_key,
  330. 'date': date_str,
  331. }
  332. # 发起API请求
  333. response = requests.get(api_url, params=request_params, timeout=timeout)
  334. if response.status_code == 200:
  335. response_result = response.json()
  336. # 检查API返回结果
  337. if response_result.get('error_code') == 0 and response_result.get('reason') == 'successed':
  338. return response_result.get('result')
  339. else:
  340. print(f"API返回错误: {response_result}")
  341. return None
  342. else:
  343. print(f"API请求失败,状态码: {response.status_code}")
  344. return None
  345. except requests.exceptions.RequestException as e:
  346. print(f"API请求异常: {e}")
  347. return None
  348. except Exception as e:
  349. print(f"获取API数据时发生错误: {e}")
  350. return None
  351. def save_calendar_from_api(self, api_data: dict) -> Optional[CalendarInfo]:
  352. """
  353. 将API返回的黄历信息保存到数据库
  354. Args:
  355. api_data (dict): API返回的黄历信息数据
  356. Returns:
  357. Optional[CalendarInfo]: 保存后的黄历信息对象,如果失败则返回None
  358. """
  359. try:
  360. # 解析API数据
  361. yangli_str = api_data.get('yangli')
  362. if not yangli_str:
  363. print("API数据中缺少阳历日期")
  364. return None
  365. # 解析日期
  366. try:
  367. yangli_date = date.fromisoformat(yangli_str)
  368. except ValueError:
  369. print(f"无效的日期格式: {yangli_str}")
  370. return None
  371. # 从wuxing字段中判断五行元素并设置对应的颜色值
  372. wuxing = api_data.get('wuxing', '') or ''
  373. color = api_data.get('color') # 先获取API中的color值
  374. # 如果API中没有color值,则根据wuxing字段判断五行元素设置颜色
  375. if not color:
  376. if '金' in wuxing:
  377. color = '白'
  378. elif '水' in wuxing:
  379. color = '黑'
  380. elif '木' in wuxing:
  381. color = '绿'
  382. elif '火' in wuxing:
  383. color = '红'
  384. elif '土' in wuxing:
  385. color = '黄'
  386. # 创建CalendarInfo对象
  387. calendar_info = CalendarInfo(
  388. yangli=yangli_date, # type: ignore
  389. yinli=api_data.get('yinli', ''), # type: ignore
  390. wuxing=wuxing, # type: ignore
  391. chongsha=api_data.get('chongsha'), # type: ignore
  392. baiji=api_data.get('baiji'), # type: ignore
  393. jishen=api_data.get('jishen'), # type: ignore
  394. yi=api_data.get('yi'), # type: ignore
  395. xiongshen=api_data.get('xionshen'), # type: ignore # 注意API返回的是xionshen
  396. ji=api_data.get('ji'), # type: ignore
  397. color=color # type: ignore
  398. )
  399. # 保存到数据库
  400. if self.engine:
  401. with Session(self.engine) as session:
  402. session.add(calendar_info)
  403. session.commit()
  404. session.refresh(calendar_info)
  405. else:
  406. # 使用Flask-SQLAlchemy的db.session
  407. db.session.add(calendar_info)
  408. db.session.commit()
  409. db.session.refresh(calendar_info)
  410. print(f"成功保存黄历信息到数据库,ID: {calendar_info.id}")
  411. return calendar_info
  412. except Exception as e:
  413. print(f"保存API数据到数据库时发生错误: {e}")
  414. return None
  415. # 便捷函数
  416. def create_calendar_info(calendar_data: dict, engine=None) -> CalendarInfo:
  417. """
  418. 创建黄历信息的便捷函数
  419. Args:
  420. calendar_data (dict): 黄历信息数据
  421. engine: SQLAlchemy引擎对象
  422. Returns:
  423. CalendarInfo: 创建的黄历信息对象
  424. """
  425. service = CalendarService(engine)
  426. return service.create_calendar_info(calendar_data)
  427. def get_calendar_by_date(yangli_date: str, engine=None) -> dict:
  428. """
  429. 根据阳历日期查询黄历信息的便捷函数
  430. Args:
  431. yangli_date (str): 阳历日期,格式为YYYY-MM-DD
  432. engine: SQLAlchemy引擎对象
  433. Returns:
  434. dict: 包含查询结果的JSON格式数据
  435. """
  436. try:
  437. # 验证日期格式
  438. if not isinstance(yangli_date, str) or len(yangli_date) != 10:
  439. return {
  440. "reason": "failed",
  441. "return_code": 400,
  442. "result": None,
  443. "error": "日期格式错误,请使用YYYY-MM-DD格式"
  444. }
  445. # 解析日期字符串
  446. try:
  447. parsed_date = date.fromisoformat(yangli_date)
  448. except ValueError:
  449. return {
  450. "reason": "failed",
  451. "return_code": 400,
  452. "result": None,
  453. "error": "无效的日期格式"
  454. }
  455. # 查询数据库
  456. service = CalendarService(engine)
  457. calendar_info = service.get_calendar_by_date(parsed_date)
  458. if calendar_info:
  459. # 查询成功,返回指定格式的JSON数据
  460. return {
  461. "reason": "successed",
  462. "return_code": 200,
  463. "result": {
  464. "id": str(calendar_info.id),
  465. "yangli": calendar_info.yangli.isoformat() if calendar_info.yangli is not None else None,
  466. "yinli": calendar_info.yinli,
  467. "wuxing": calendar_info.wuxing,
  468. "chongsha": calendar_info.chongsha,
  469. "baiji": calendar_info.baiji,
  470. "jishen": calendar_info.jishen,
  471. "yi": calendar_info.yi,
  472. "xiongshen": calendar_info.xiongshen,
  473. "ji": calendar_info.ji,
  474. "color": calendar_info.color
  475. }
  476. }
  477. else:
  478. # 数据库中没有找到记录,尝试从API获取
  479. print(f"数据库中没有找到日期 {yangli_date} 的黄历信息,尝试从API获取...")
  480. # 从API获取数据
  481. api_data = service.fetch_calendar_from_api(parsed_date)
  482. if api_data:
  483. # API获取成功,保存到数据库
  484. print("API获取数据成功,正在保存到数据库...")
  485. saved_calendar = service.save_calendar_from_api(api_data)
  486. if saved_calendar:
  487. # 保存成功,返回数据
  488. return {
  489. "reason": "successed",
  490. "return_code": 200,
  491. "result": {
  492. "id": str(saved_calendar.id),
  493. "yangli": saved_calendar.yangli.isoformat() if saved_calendar.yangli is not None else None,
  494. "yinli": saved_calendar.yinli,
  495. "wuxing": saved_calendar.wuxing,
  496. "chongsha": saved_calendar.chongsha,
  497. "baiji": saved_calendar.baiji,
  498. "jishen": saved_calendar.jishen,
  499. "yi": saved_calendar.yi,
  500. "xiongshen": saved_calendar.xiongshen,
  501. "ji": saved_calendar.ji,
  502. "color": saved_calendar.color
  503. }
  504. }
  505. else:
  506. # 保存到数据库失败
  507. return {
  508. "reason": "failed",
  509. "return_code": 500,
  510. "result": None,
  511. "error": f"API获取数据成功但保存到数据库失败"
  512. }
  513. else:
  514. # API获取失败
  515. return {
  516. "reason": "failed",
  517. "return_code": 404,
  518. "result": None,
  519. "error": f"未找到日期 {yangli_date} 的黄历信息,且API获取失败"
  520. }
  521. except Exception as e:
  522. # 发生异常
  523. return {
  524. "reason": "failed",
  525. "return_code": 500,
  526. "result": None,
  527. "error": f"查询过程中发生错误: {str(e)}"
  528. }
  529. def get_calendar_by_id(calendar_id: int, engine=None) -> Optional[CalendarInfo]:
  530. """
  531. 根据ID查询黄历信息的便捷函数
  532. Args:
  533. calendar_id (int): 黄历信息ID
  534. engine: SQLAlchemy引擎对象
  535. Returns:
  536. Optional[CalendarInfo]: 黄历信息对象
  537. """
  538. service = CalendarService(engine)
  539. return service.get_calendar_by_id(calendar_id)
  540. # 导出主要类和函数
  541. __all__ = [
  542. 'CalendarInfo',
  543. 'CalendarService',
  544. 'create_calendar_info',
  545. 'get_calendar_by_date',
  546. 'get_calendar_by_id'
  547. ]