calendar.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451
  1. """
  2. 黄历信息数据模型
  3. 基于 create_calendar_info.sql 中的DDL定义创建
  4. """
  5. from datetime import date, datetime
  6. from typing import Optional
  7. from sqlalchemy import Column, Integer, Date, Text, String, Boolean, DateTime
  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 WechatUser(db.Model):
  118. """
  119. 微信用户信息表数据模型
  120. 对应数据库表: public.wechat_users
  121. 表注释: 微信用户信息表
  122. """
  123. __tablename__ = 'wechat_users'
  124. __table_args__ = {'schema': 'public'}
  125. # 主键ID (serial primary key)
  126. id = db.Column(db.Integer, primary_key=True, autoincrement=True, comment='主键ID')
  127. # 微信授权码/openid (varchar(255) not null unique)
  128. wechat_code = db.Column(db.String(255), nullable=False, unique=True, comment='微信授权码/openid,唯一标识')
  129. # 用户手机号码 (varchar(20))
  130. phone_number = db.Column(db.String(20), nullable=True, comment='用户手机号码')
  131. # 用户身份证号码 (varchar(18))
  132. id_card_number = db.Column(db.String(18), nullable=True, comment='用户身份证号码')
  133. # 当前登录状态 (boolean default false not null)
  134. login_status = db.Column(db.Boolean, nullable=False, default=False, comment='当前登录状态,true表示已登录,false表示未登录')
  135. # 最后登录时间 (timestamp with time zone)
  136. login_time = db.Column(db.DateTime(timezone=True), nullable=True, comment='最后登录时间')
  137. # 用户账户状态 (varchar(20) default 'active' not null)
  138. user_status = db.Column(db.String(20), nullable=False, default='active', comment='用户账户状态:active-活跃,inactive-非活跃,suspended-暂停,deleted-已删除')
  139. # 账户创建时间 (timestamp with time zone default current_timestamp not null)
  140. created_at = db.Column(db.DateTime(timezone=True), nullable=False, default=datetime.utcnow, comment='账户创建时间')
  141. # 信息更新时间 (timestamp with time zone default current_timestamp not null)
  142. updated_at = db.Column(db.DateTime(timezone=True), nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment='信息更新时间')
  143. def __init__(self, **kwargs):
  144. super().__init__(**kwargs)
  145. def __repr__(self):
  146. return f"<WechatUser(id={self.id}, wechat_code='{self.wechat_code}', user_status='{self.user_status}')>"
  147. def to_dict(self) -> dict:
  148. """
  149. 将模型对象转换为字典
  150. Returns:
  151. dict: 包含所有字段的字典
  152. """
  153. return {
  154. 'id': self.id,
  155. 'wechat_code': self.wechat_code,
  156. 'phone_number': self.phone_number,
  157. 'id_card_number': self.id_card_number,
  158. 'login_status': self.login_status,
  159. 'login_time': self.login_time.isoformat() if self.login_time is not None else None,
  160. 'user_status': self.user_status,
  161. 'created_at': self.created_at.isoformat() if self.created_at is not None else None,
  162. 'updated_at': self.updated_at.isoformat() if self.updated_at is not None else None
  163. }
  164. def to_json(self) -> str:
  165. """
  166. 将模型对象转换为JSON字符串
  167. Returns:
  168. str: JSON格式的字符串
  169. """
  170. return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
  171. @classmethod
  172. def from_dict(cls, data: dict) -> 'WechatUser':
  173. """
  174. 从字典创建模型对象
  175. Args:
  176. data (dict): 包含字段数据的字典
  177. Returns:
  178. WechatUser: 新创建的模型对象
  179. """
  180. # 处理日期时间字段
  181. login_time = data.get('login_time')
  182. if isinstance(login_time, str):
  183. try:
  184. login_time = datetime.fromisoformat(login_time.replace('Z', '+00:00'))
  185. except ValueError:
  186. login_time = None
  187. created_at = data.get('created_at')
  188. if isinstance(created_at, str):
  189. try:
  190. created_at = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
  191. except ValueError:
  192. created_at = datetime.utcnow()
  193. elif created_at is None:
  194. created_at = datetime.utcnow()
  195. updated_at = data.get('updated_at')
  196. if isinstance(updated_at, str):
  197. try:
  198. updated_at = datetime.fromisoformat(updated_at.replace('Z', '+00:00'))
  199. except ValueError:
  200. updated_at = datetime.utcnow()
  201. elif updated_at is None:
  202. updated_at = datetime.utcnow()
  203. return cls(
  204. wechat_code=data.get('wechat_code'), # type: ignore
  205. phone_number=data.get('phone_number'), # type: ignore
  206. id_card_number=data.get('id_card_number'), # type: ignore
  207. login_status=data.get('login_status', False), # type: ignore
  208. login_time=login_time, # type: ignore
  209. user_status=data.get('user_status', 'active'), # type: ignore
  210. created_at=created_at, # type: ignore
  211. updated_at=updated_at # type: ignore
  212. )
  213. class CalendarService:
  214. """
  215. 黄历信息服务类
  216. 提供黄历信息的增删改查操作
  217. """
  218. def __init__(self, engine=None):
  219. """
  220. 初始化服务
  221. Args:
  222. engine: SQLAlchemy引擎对象,如果为None则使用Flask-SQLAlchemy的db.session
  223. """
  224. self.engine = engine
  225. def create_calendar_info(self, calendar_data: dict) -> CalendarInfo:
  226. """
  227. 创建新的黄历信息记录
  228. Args:
  229. calendar_data (dict): 黄历信息数据
  230. Returns:
  231. CalendarInfo: 创建的黄历信息对象
  232. """
  233. calendar_info = CalendarInfo.from_dict(calendar_data)
  234. if self.engine:
  235. with Session(self.engine) as session:
  236. session.add(calendar_info)
  237. session.commit()
  238. session.refresh(calendar_info)
  239. else:
  240. # 使用Flask-SQLAlchemy的db.session
  241. db.session.add(calendar_info)
  242. db.session.commit()
  243. db.session.refresh(calendar_info)
  244. return calendar_info
  245. def get_calendar_by_date(self, yangli_date: date) -> Optional[CalendarInfo]:
  246. """
  247. 根据阳历日期查询黄历信息
  248. Args:
  249. yangli_date (date): 阳历日期
  250. Returns:
  251. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  252. """
  253. if self.engine:
  254. with Session(self.engine) as session:
  255. return session.query(CalendarInfo).filter(
  256. CalendarInfo.yangli == yangli_date
  257. ).first()
  258. else:
  259. # 使用Flask-SQLAlchemy的db.session
  260. return CalendarInfo.query.filter(
  261. CalendarInfo.yangli == yangli_date
  262. ).first()
  263. def get_calendar_by_id(self, calendar_id: int) -> Optional[CalendarInfo]:
  264. """
  265. 根据ID查询黄历信息
  266. Args:
  267. calendar_id (int): 黄历信息ID
  268. Returns:
  269. Optional[CalendarInfo]: 黄历信息对象,如果不存在则返回None
  270. """
  271. if self.engine:
  272. with Session(self.engine) as session:
  273. return session.query(CalendarInfo).filter(
  274. CalendarInfo.id == calendar_id
  275. ).first()
  276. else:
  277. # 使用Flask-SQLAlchemy的db.session
  278. return CalendarInfo.query.filter(
  279. CalendarInfo.id == calendar_id
  280. ).first()
  281. def update_calendar_info(self, calendar_id: int, update_data: dict) -> Optional[CalendarInfo]:
  282. """
  283. 更新黄历信息
  284. Args:
  285. calendar_id (int): 黄历信息ID
  286. update_data (dict): 要更新的数据
  287. Returns:
  288. Optional[CalendarInfo]: 更新后的黄历信息对象,如果不存在则返回None
  289. """
  290. if self.engine:
  291. with Session(self.engine) as session:
  292. calendar_info = session.query(CalendarInfo).filter(
  293. CalendarInfo.id == calendar_id
  294. ).first()
  295. if not calendar_info:
  296. return None
  297. # 更新字段
  298. for key, value in update_data.items():
  299. if hasattr(calendar_info, key):
  300. if key == 'yangli' and isinstance(value, str):
  301. try:
  302. value = date.fromisoformat(value)
  303. except ValueError:
  304. continue
  305. setattr(calendar_info, key, value)
  306. session.commit()
  307. session.refresh(calendar_info)
  308. else:
  309. # 使用Flask-SQLAlchemy的db.session
  310. calendar_info = CalendarInfo.query.filter(
  311. CalendarInfo.id == calendar_id
  312. ).first()
  313. if not calendar_info:
  314. return None
  315. # 更新字段
  316. for key, value in update_data.items():
  317. if hasattr(calendar_info, key):
  318. if key == 'yangli' and isinstance(value, str):
  319. try:
  320. value = date.fromisoformat(value)
  321. except ValueError:
  322. continue
  323. setattr(calendar_info, key, value)
  324. db.session.commit()
  325. db.session.refresh(calendar_info)
  326. return calendar_info
  327. def delete_calendar_info(self, calendar_id: int) -> bool:
  328. """
  329. 删除黄历信息
  330. Args:
  331. calendar_id (int): 黄历信息ID
  332. Returns:
  333. bool: 删除成功返回True,否则返回False
  334. """
  335. if self.engine:
  336. with Session(self.engine) as session:
  337. calendar_info = session.query(CalendarInfo).filter(
  338. CalendarInfo.id == calendar_id
  339. ).first()
  340. if not calendar_info:
  341. return False
  342. session.delete(calendar_info)
  343. session.commit()
  344. else:
  345. # 使用Flask-SQLAlchemy的db.session
  346. calendar_info = CalendarInfo.query.filter(
  347. CalendarInfo.id == calendar_id
  348. ).first()
  349. if not calendar_info:
  350. return False
  351. db.session.delete(calendar_info)
  352. db.session.commit()
  353. return True
  354. def get_calendar_list(self, limit: int = 100, offset: int = 0) -> list[CalendarInfo]:
  355. """
  356. 获取黄历信息列表
  357. Args:
  358. limit (int): 限制返回数量,默认100
  359. offset (int): 偏移量,默认0
  360. Returns:
  361. list[CalendarInfo]: 黄历信息对象列表
  362. """
  363. if self.engine:
  364. with Session(self.engine) as session:
  365. return session.query(CalendarInfo).order_by(
  366. CalendarInfo.yangli.desc()
  367. ).offset(offset).limit(limit).all()
  368. else:
  369. # 使用Flask-SQLAlchemy的db.session
  370. return CalendarInfo.query.order_by(
  371. CalendarInfo.yangli.desc()
  372. ).offset(offset).limit(limit).all()
  373. def search_calendar_by_keyword(self, keyword: str, limit: int = 100) -> list[CalendarInfo]:
  374. """
  375. 根据关键词搜索黄历信息
  376. Args:
  377. keyword (str): 搜索关键词
  378. limit (int): 限制返回数量,默认100
  379. Returns:
  380. list[CalendarInfo]: 匹配的黄历信息对象列表
  381. """
  382. if self.engine:
  383. with Session(self.engine) as session:
  384. return session.query(CalendarInfo).filter(
  385. (CalendarInfo.yinli.contains(keyword)) |
  386. (CalendarInfo.wuxing.contains(keyword)) |
  387. (CalendarInfo.chongsha.contains(keyword)) |
  388. (CalendarInfo.baiji.contains(keyword)) |
  389. (CalendarInfo.jishen.contains(keyword)) |
  390. (CalendarInfo.yi.contains(keyword)) |
  391. (CalendarInfo.xiongshen.contains(keyword)) |
  392. (CalendarInfo.ji.contains(keyword)) |
  393. (CalendarInfo.color.contains(keyword))
  394. ).limit(limit).all()
  395. else:
  396. # 使用Flask-SQLAlchemy的db.session
  397. return CalendarInfo.query.filter(
  398. (CalendarInfo.yinli.contains(keyword)) |
  399. (CalendarInfo.wuxing.contains(keyword)) |
  400. (CalendarInfo.chongsha.contains(keyword)) |
  401. (CalendarInfo.baiji.contains(keyword)) |
  402. (CalendarInfo.jishen.contains(keyword)) |
  403. (CalendarInfo.yi.contains(keyword)) |
  404. (CalendarInfo.xiongshen.contains(keyword)) |
  405. (CalendarInfo.ji.contains(keyword)) |
  406. (CalendarInfo.color.contains(keyword))
  407. ).limit(limit).all()
  408. def fetch_calendar_from_api(self, yangli_date: date) -> Optional[dict]:
  409. """
  410. 从外部API获取黄历信息
  411. Args:
  412. yangli_date (date): 阳历日期
  413. Returns:
  414. Optional[dict]: API返回的黄历信息,如果失败则返回None
  415. """
  416. try:
  417. # 从配置文件获取API配置
  418. api_url = CALENDAR_API_CONFIG['url']
  419. api_key = CALENDAR_API_CONFIG['key']
  420. timeout = CALENDAR_API_CONFIG['timeout']
  421. # 格式化日期为YYYYMMDD格式
  422. date_str = yangli_date.strftime('%Y%m%d')
  423. # 请求参数
  424. request_params = {
  425. 'key': api_key,
  426. 'date': date_str,
  427. }
  428. # 发起API请求
  429. response = requests.get(api_url, params=request_params, timeout=timeout)
  430. if response.status_code == 200:
  431. response_result = response.json()
  432. # 检查API返回结果
  433. if response_result.get('error_code') == 0 and response_result.get('reason') == 'successed':
  434. return response_result.get('result')
  435. else:
  436. print(f"API返回错误: {response_result}")
  437. return None
  438. else:
  439. print(f"API请求失败,状态码: {response.status_code}")
  440. return None
  441. except requests.exceptions.RequestException as e:
  442. print(f"API请求异常: {e}")
  443. return None
  444. except Exception as e:
  445. print(f"获取API数据时发生错误: {e}")
  446. return None
  447. def save_calendar_from_api(self, api_data: dict) -> Optional[CalendarInfo]:
  448. """
  449. 将API返回的黄历信息保存到数据库
  450. Args:
  451. api_data (dict): API返回的黄历信息数据
  452. Returns:
  453. Optional[CalendarInfo]: 保存后的黄历信息对象,如果失败则返回None
  454. """
  455. try:
  456. # 解析API数据
  457. yangli_str = api_data.get('yangli')
  458. if not yangli_str:
  459. print("API数据中缺少阳历日期")
  460. return None
  461. # 解析日期
  462. try:
  463. yangli_date = date.fromisoformat(yangli_str)
  464. except ValueError:
  465. print(f"无效的日期格式: {yangli_str}")
  466. return None
  467. # 从wuxing字段中判断五行元素并设置对应的颜色值
  468. wuxing = api_data.get('wuxing', '') or ''
  469. color = api_data.get('color') # 先获取API中的color值
  470. # 如果API中没有color值,则根据wuxing字段判断五行元素设置颜色
  471. if not color:
  472. if '金' in wuxing:
  473. color = '白'
  474. elif '水' in wuxing:
  475. color = '黑'
  476. elif '木' in wuxing:
  477. color = '绿'
  478. elif '火' in wuxing:
  479. color = '红'
  480. elif '土' in wuxing:
  481. color = '黄'
  482. # 创建CalendarInfo对象
  483. calendar_info = CalendarInfo(
  484. yangli=yangli_date, # type: ignore
  485. yinli=api_data.get('yinli', ''), # type: ignore
  486. wuxing=wuxing, # type: ignore
  487. chongsha=api_data.get('chongsha'), # type: ignore
  488. baiji=api_data.get('baiji'), # type: ignore
  489. jishen=api_data.get('jishen'), # type: ignore
  490. yi=api_data.get('yi'), # type: ignore
  491. xiongshen=api_data.get('xionshen'), # type: ignore # 注意API返回的是xionshen
  492. ji=api_data.get('ji'), # type: ignore
  493. color=color # type: ignore
  494. )
  495. # 保存到数据库
  496. if self.engine:
  497. with Session(self.engine) as session:
  498. session.add(calendar_info)
  499. session.commit()
  500. session.refresh(calendar_info)
  501. else:
  502. # 使用Flask-SQLAlchemy的db.session
  503. db.session.add(calendar_info)
  504. db.session.commit()
  505. db.session.refresh(calendar_info)
  506. print(f"成功保存黄历信息到数据库,ID: {calendar_info.id}")
  507. return calendar_info
  508. except Exception as e:
  509. print(f"保存API数据到数据库时发生错误: {e}")
  510. return None
  511. class WechatUserService:
  512. """
  513. 微信用户信息服务类
  514. 提供微信用户的注册、登录、状态管理等操作
  515. """
  516. def __init__(self, engine=None):
  517. """
  518. 初始化服务
  519. Args:
  520. engine: SQLAlchemy引擎对象,如果为None则使用Flask-SQLAlchemy的db.session
  521. """
  522. self.engine = engine
  523. def create_user(self, user_data: dict) -> WechatUser:
  524. """
  525. 创建新的微信用户记录
  526. Args:
  527. user_data (dict): 用户信息数据
  528. Returns:
  529. WechatUser: 创建的用户对象
  530. """
  531. user = WechatUser.from_dict(user_data)
  532. if self.engine:
  533. with Session(self.engine) as session:
  534. session.add(user)
  535. session.commit()
  536. session.refresh(user)
  537. else:
  538. # 使用Flask-SQLAlchemy的db.session
  539. db.session.add(user)
  540. db.session.commit()
  541. db.session.refresh(user)
  542. return user
  543. def get_user_by_wechat_code(self, wechat_code: str) -> Optional[WechatUser]:
  544. """
  545. 根据微信授权码查询用户
  546. Args:
  547. wechat_code (str): 微信授权码/openid
  548. Returns:
  549. Optional[WechatUser]: 用户对象,如果不存在则返回None
  550. """
  551. if self.engine:
  552. with Session(self.engine) as session:
  553. return session.query(WechatUser).filter(
  554. WechatUser.wechat_code == wechat_code
  555. ).first()
  556. else:
  557. # 使用Flask-SQLAlchemy的db.session
  558. return WechatUser.query.filter(
  559. WechatUser.wechat_code == wechat_code
  560. ).first()
  561. def get_user_by_id(self, user_id: int) -> Optional[WechatUser]:
  562. """
  563. 根据ID查询用户
  564. Args:
  565. user_id (int): 用户ID
  566. Returns:
  567. Optional[WechatUser]: 用户对象,如果不存在则返回None
  568. """
  569. if self.engine:
  570. with Session(self.engine) as session:
  571. return session.query(WechatUser).filter(
  572. WechatUser.id == user_id
  573. ).first()
  574. else:
  575. # 使用Flask-SQLAlchemy的db.session
  576. return WechatUser.query.filter(
  577. WechatUser.id == user_id
  578. ).first()
  579. def get_user_by_phone(self, phone_number: str) -> Optional[WechatUser]:
  580. """
  581. 根据手机号查询用户
  582. Args:
  583. phone_number (str): 手机号码
  584. Returns:
  585. Optional[WechatUser]: 用户对象,如果不存在则返回None
  586. """
  587. if self.engine:
  588. with Session(self.engine) as session:
  589. return session.query(WechatUser).filter(
  590. WechatUser.phone_number == phone_number
  591. ).first()
  592. else:
  593. # 使用Flask-SQLAlchemy的db.session
  594. return WechatUser.query.filter(
  595. WechatUser.phone_number == phone_number
  596. ).first()
  597. def register_user(self, wechat_code: str, phone_number: Optional[str] = None, id_card_number: Optional[str] = None) -> WechatUser:
  598. """
  599. 注册新用户
  600. Args:
  601. wechat_code (str): 微信授权码/openid
  602. phone_number (str, optional): 手机号码
  603. id_card_number (str, optional): 身份证号码
  604. Returns:
  605. WechatUser: 注册的用户对象
  606. Raises:
  607. ValueError: 如果用户已存在
  608. """
  609. # 检查用户是否已存在
  610. existing_user = self.get_user_by_wechat_code(wechat_code)
  611. if existing_user:
  612. raise ValueError(f"用户已存在,微信授权码: {wechat_code}")
  613. # 创建用户数据
  614. user_data = {
  615. 'wechat_code': wechat_code,
  616. 'phone_number': phone_number,
  617. 'id_card_number': id_card_number,
  618. 'login_status': False,
  619. 'user_status': 'active'
  620. }
  621. return self.create_user(user_data)
  622. def login_user(self, wechat_code: str) -> Optional[WechatUser]:
  623. """
  624. 用户登录
  625. Args:
  626. wechat_code (str): 微信授权码/openid
  627. Returns:
  628. Optional[WechatUser]: 登录成功返回用户对象,否则返回None
  629. """
  630. user = self.get_user_by_wechat_code(wechat_code)
  631. if not user:
  632. return None
  633. # 检查用户状态
  634. if user.user_status != 'active':
  635. return None
  636. # 更新登录状态和登录时间
  637. update_data = {
  638. 'login_status': True,
  639. 'login_time': datetime.utcnow()
  640. }
  641. return self.update_user(user.id, update_data)
  642. def logout_user(self, wechat_code: str) -> bool:
  643. """
  644. 用户登出
  645. Args:
  646. wechat_code (str): 微信授权码/openid
  647. Returns:
  648. bool: 登出成功返回True,否则返回False
  649. """
  650. user = self.get_user_by_wechat_code(wechat_code)
  651. if not user:
  652. return False
  653. # 更新登录状态
  654. update_data = {
  655. 'login_status': False
  656. }
  657. updated_user = self.update_user(user.id, update_data)
  658. return updated_user is not None
  659. def update_user(self, user_id: int, update_data: dict) -> Optional[WechatUser]:
  660. """
  661. 更新用户信息
  662. Args:
  663. user_id (int): 用户ID
  664. update_data (dict): 要更新的数据
  665. Returns:
  666. Optional[WechatUser]: 更新后的用户对象,如果不存在则返回None
  667. """
  668. if self.engine:
  669. with Session(self.engine) as session:
  670. user = session.query(WechatUser).filter(
  671. WechatUser.id == user_id
  672. ).first()
  673. if not user:
  674. return None
  675. # 更新字段
  676. for key, value in update_data.items():
  677. if hasattr(user, key):
  678. if key in ['login_time', 'created_at', 'updated_at'] and isinstance(value, str):
  679. try:
  680. value = datetime.fromisoformat(value.replace('Z', '+00:00'))
  681. except ValueError:
  682. continue
  683. setattr(user, key, value)
  684. session.commit()
  685. session.refresh(user)
  686. else:
  687. # 使用Flask-SQLAlchemy的db.session
  688. user = WechatUser.query.filter(
  689. WechatUser.id == user_id
  690. ).first()
  691. if not user:
  692. return None
  693. # 更新字段
  694. for key, value in update_data.items():
  695. if hasattr(user, key):
  696. if key in ['login_time', 'created_at', 'updated_at'] and isinstance(value, str):
  697. try:
  698. value = datetime.fromisoformat(value.replace('Z', '+00:00'))
  699. except ValueError:
  700. continue
  701. setattr(user, key, value)
  702. db.session.commit()
  703. db.session.refresh(user)
  704. return user
  705. def deactivate_user(self, user_id: int) -> bool:
  706. """
  707. 停用用户账户
  708. Args:
  709. user_id (int): 用户ID
  710. Returns:
  711. bool: 停用成功返回True,否则返回False
  712. """
  713. update_data = {
  714. 'user_status': 'inactive',
  715. 'login_status': False
  716. }
  717. updated_user = self.update_user(user_id, update_data)
  718. return updated_user is not None
  719. def activate_user(self, user_id: int) -> bool:
  720. """
  721. 激活用户账户
  722. Args:
  723. user_id (int): 用户ID
  724. Returns:
  725. bool: 激活成功返回True,否则返回False
  726. """
  727. update_data = {
  728. 'user_status': 'active'
  729. }
  730. updated_user = self.update_user(user_id, update_data)
  731. return updated_user is not None
  732. def delete_user(self, user_id: int) -> bool:
  733. """
  734. 删除用户(软删除,将状态设为deleted)
  735. Args:
  736. user_id (int): 用户ID
  737. Returns:
  738. bool: 删除成功返回True,否则返回False
  739. """
  740. update_data = {
  741. 'user_status': 'deleted',
  742. 'login_status': False
  743. }
  744. updated_user = self.update_user(user_id, update_data)
  745. return updated_user is not None
  746. def get_active_users(self, limit: int = 100, offset: int = 0) -> list[WechatUser]:
  747. """
  748. 获取活跃用户列表
  749. Args:
  750. limit (int): 限制返回数量,默认100
  751. offset (int): 偏移量,默认0
  752. Returns:
  753. list[WechatUser]: 活跃用户对象列表
  754. """
  755. if self.engine:
  756. with Session(self.engine) as session:
  757. return session.query(WechatUser).filter(
  758. WechatUser.user_status == 'active'
  759. ).order_by(
  760. WechatUser.created_at.desc()
  761. ).offset(offset).limit(limit).all()
  762. else:
  763. # 使用Flask-SQLAlchemy的db.session
  764. return WechatUser.query.filter(
  765. WechatUser.user_status == 'active'
  766. ).order_by(
  767. WechatUser.created_at.desc()
  768. ).offset(offset).limit(limit).all()
  769. def get_logged_in_users(self, limit: int = 100) -> list[WechatUser]:
  770. """
  771. 获取当前已登录的用户列表
  772. Args:
  773. limit (int): 限制返回数量,默认100
  774. Returns:
  775. list[WechatUser]: 已登录用户对象列表
  776. """
  777. if self.engine:
  778. with Session(self.engine) as session:
  779. return session.query(WechatUser).filter(
  780. WechatUser.login_status == True,
  781. WechatUser.user_status == 'active'
  782. ).order_by(
  783. WechatUser.login_time.desc()
  784. ).limit(limit).all()
  785. else:
  786. # 使用Flask-SQLAlchemy的db.session
  787. return WechatUser.query.filter(
  788. WechatUser.login_status == True,
  789. WechatUser.user_status == 'active'
  790. ).order_by(
  791. WechatUser.login_time.desc()
  792. ).limit(limit).all()
  793. # 便捷函数
  794. def create_calendar_info(calendar_data: dict, engine=None) -> CalendarInfo:
  795. """
  796. 创建黄历信息的便捷函数
  797. Args:
  798. calendar_data (dict): 黄历信息数据
  799. engine: SQLAlchemy引擎对象
  800. Returns:
  801. CalendarInfo: 创建的黄历信息对象
  802. """
  803. service = CalendarService(engine)
  804. return service.create_calendar_info(calendar_data)
  805. def get_calendar_by_date(yangli_date: str, engine=None) -> dict:
  806. """
  807. 根据阳历日期查询黄历信息的便捷函数
  808. Args:
  809. yangli_date (str): 阳历日期,格式为YYYY-MM-DD
  810. engine: SQLAlchemy引擎对象
  811. Returns:
  812. dict: 包含查询结果的JSON格式数据
  813. """
  814. try:
  815. # 验证日期格式
  816. if not isinstance(yangli_date, str) or len(yangli_date) != 10:
  817. return {
  818. "reason": "failed",
  819. "return_code": 400,
  820. "result": None,
  821. "error": "日期格式错误,请使用YYYY-MM-DD格式"
  822. }
  823. # 解析日期字符串
  824. try:
  825. parsed_date = date.fromisoformat(yangli_date)
  826. except ValueError:
  827. return {
  828. "reason": "failed",
  829. "return_code": 400,
  830. "result": None,
  831. "error": "无效的日期格式"
  832. }
  833. # 查询数据库
  834. service = CalendarService(engine)
  835. calendar_info = service.get_calendar_by_date(parsed_date)
  836. if calendar_info:
  837. # 查询成功,返回指定格式的JSON数据
  838. return {
  839. "reason": "successed",
  840. "return_code": 200,
  841. "result": {
  842. "id": str(calendar_info.id),
  843. "yangli": calendar_info.yangli.isoformat() if calendar_info.yangli is not None else None,
  844. "yinli": calendar_info.yinli,
  845. "wuxing": calendar_info.wuxing,
  846. "chongsha": calendar_info.chongsha,
  847. "baiji": calendar_info.baiji,
  848. "jishen": calendar_info.jishen,
  849. "yi": calendar_info.yi,
  850. "xiongshen": calendar_info.xiongshen,
  851. "ji": calendar_info.ji,
  852. "color": calendar_info.color
  853. }
  854. }
  855. else:
  856. # 数据库中没有找到记录,尝试从API获取
  857. print(f"数据库中没有找到日期 {yangli_date} 的黄历信息,尝试从API获取...")
  858. # 从API获取数据
  859. api_data = service.fetch_calendar_from_api(parsed_date)
  860. if api_data:
  861. # API获取成功,保存到数据库
  862. print("API获取数据成功,正在保存到数据库...")
  863. saved_calendar = service.save_calendar_from_api(api_data)
  864. if saved_calendar:
  865. # 保存成功,返回数据
  866. return {
  867. "reason": "successed",
  868. "return_code": 200,
  869. "result": {
  870. "id": str(saved_calendar.id),
  871. "yangli": saved_calendar.yangli.isoformat() if saved_calendar.yangli is not None else None,
  872. "yinli": saved_calendar.yinli,
  873. "wuxing": saved_calendar.wuxing,
  874. "chongsha": saved_calendar.chongsha,
  875. "baiji": saved_calendar.baiji,
  876. "jishen": saved_calendar.jishen,
  877. "yi": saved_calendar.yi,
  878. "xiongshen": saved_calendar.xiongshen,
  879. "ji": saved_calendar.ji,
  880. "color": saved_calendar.color
  881. }
  882. }
  883. else:
  884. # 保存到数据库失败
  885. return {
  886. "reason": "failed",
  887. "return_code": 500,
  888. "result": None,
  889. "error": f"API获取数据成功但保存到数据库失败"
  890. }
  891. else:
  892. # API获取失败
  893. return {
  894. "reason": "failed",
  895. "return_code": 404,
  896. "result": None,
  897. "error": f"未找到日期 {yangli_date} 的黄历信息,且API获取失败"
  898. }
  899. except Exception as e:
  900. # 发生异常
  901. return {
  902. "reason": "failed",
  903. "return_code": 500,
  904. "result": None,
  905. "error": f"查询过程中发生错误: {str(e)}"
  906. }
  907. def get_calendar_by_id(calendar_id: int, engine=None) -> Optional[CalendarInfo]:
  908. """
  909. 根据ID查询黄历信息的便捷函数
  910. Args:
  911. calendar_id (int): 黄历信息ID
  912. engine: SQLAlchemy引擎对象
  913. Returns:
  914. Optional[CalendarInfo]: 黄历信息对象
  915. """
  916. service = CalendarService(engine)
  917. return service.get_calendar_by_id(calendar_id)
  918. # 微信用户相关便捷函数
  919. def register_wechat_user(wechat_code: str, phone_number: Optional[str] = None, id_card_number: Optional[str] = None, engine=None) -> dict:
  920. """
  921. 注册微信用户的便捷函数
  922. Args:
  923. wechat_code (str): 微信授权码/openid
  924. phone_number (str, optional): 手机号码
  925. id_card_number (str, optional): 身份证号码
  926. engine: SQLAlchemy引擎对象
  927. Returns:
  928. dict: 包含注册结果的JSON格式数据
  929. """
  930. try:
  931. # 验证必填参数
  932. if not wechat_code or not isinstance(wechat_code, str):
  933. return {
  934. "reason": "failed",
  935. "return_code": 400,
  936. "result": None,
  937. "error": "微信授权码不能为空"
  938. }
  939. # 创建服务实例
  940. service = WechatUserService(engine)
  941. # 尝试注册用户
  942. user = service.register_user(wechat_code, phone_number, id_card_number)
  943. # 注册成功
  944. return {
  945. "reason": "successed",
  946. "return_code": 201,
  947. "result": {
  948. "id": str(user.id),
  949. "wechat_code": user.wechat_code,
  950. "phone_number": user.phone_number,
  951. "id_card_number": user.id_card_number,
  952. "login_status": user.login_status,
  953. "user_status": user.user_status,
  954. "created_at": user.created_at.isoformat() if user.created_at is not None else None
  955. }
  956. }
  957. except ValueError as e:
  958. # 用户已存在等业务逻辑错误
  959. return {
  960. "reason": "failed",
  961. "return_code": 409,
  962. "result": None,
  963. "error": str(e)
  964. }
  965. except Exception as e:
  966. # 其他系统错误
  967. return {
  968. "reason": "failed",
  969. "return_code": 500,
  970. "result": None,
  971. "error": f"注册过程中发生错误: {str(e)}"
  972. }
  973. def login_wechat_user(wechat_code: str, engine=None) -> dict:
  974. """
  975. 微信用户登录的便捷函数
  976. Args:
  977. wechat_code (str): 微信授权码/openid
  978. engine: SQLAlchemy引擎对象
  979. Returns:
  980. dict: 包含登录结果的JSON格式数据
  981. """
  982. try:
  983. # 验证必填参数
  984. if not wechat_code or not isinstance(wechat_code, str):
  985. return {
  986. "reason": "failed",
  987. "return_code": 400,
  988. "result": None,
  989. "error": "微信授权码不能为空"
  990. }
  991. # 创建服务实例
  992. service = WechatUserService(engine)
  993. # 尝试登录
  994. user = service.login_user(wechat_code)
  995. if user:
  996. # 登录成功
  997. return {
  998. "reason": "successed",
  999. "return_code": 200,
  1000. "result": {
  1001. "id": str(user.id),
  1002. "wechat_code": user.wechat_code,
  1003. "phone_number": user.phone_number,
  1004. "id_card_number": user.id_card_number,
  1005. "login_status": user.login_status,
  1006. "login_time": user.login_time.isoformat() if user.login_time is not None else None,
  1007. "user_status": user.user_status
  1008. }
  1009. }
  1010. else:
  1011. # 登录失败
  1012. return {
  1013. "reason": "failed",
  1014. "return_code": 401,
  1015. "result": None,
  1016. "error": "用户不存在或账户状态异常"
  1017. }
  1018. except Exception as e:
  1019. # 系统错误
  1020. return {
  1021. "reason": "failed",
  1022. "return_code": 500,
  1023. "result": None,
  1024. "error": f"登录过程中发生错误: {str(e)}"
  1025. }
  1026. def logout_wechat_user(wechat_code: str, engine=None) -> dict:
  1027. """
  1028. 微信用户登出的便捷函数
  1029. Args:
  1030. wechat_code (str): 微信授权码/openid
  1031. engine: SQLAlchemy引擎对象
  1032. Returns:
  1033. dict: 包含登出结果的JSON格式数据
  1034. """
  1035. try:
  1036. # 验证必填参数
  1037. if not wechat_code or not isinstance(wechat_code, str):
  1038. return {
  1039. "reason": "failed",
  1040. "return_code": 400,
  1041. "result": None,
  1042. "error": "微信授权码不能为空"
  1043. }
  1044. # 创建服务实例
  1045. service = WechatUserService(engine)
  1046. # 尝试登出
  1047. success = service.logout_user(wechat_code)
  1048. if success:
  1049. # 登出成功
  1050. return {
  1051. "reason": "successed",
  1052. "return_code": 200,
  1053. "result": {
  1054. "message": "用户已成功登出"
  1055. }
  1056. }
  1057. else:
  1058. # 登出失败
  1059. return {
  1060. "reason": "failed",
  1061. "return_code": 404,
  1062. "result": None,
  1063. "error": "用户不存在"
  1064. }
  1065. except Exception as e:
  1066. # 系统错误
  1067. return {
  1068. "reason": "failed",
  1069. "return_code": 500,
  1070. "result": None,
  1071. "error": f"登出过程中发生错误: {str(e)}"
  1072. }
  1073. def get_wechat_user_info(wechat_code: str, engine=None) -> dict:
  1074. """
  1075. 获取微信用户信息的便捷函数
  1076. Args:
  1077. wechat_code (str): 微信授权码/openid
  1078. engine: SQLAlchemy引擎对象
  1079. Returns:
  1080. dict: 包含用户信息的JSON格式数据
  1081. """
  1082. try:
  1083. # 验证必填参数
  1084. if not wechat_code or not isinstance(wechat_code, str):
  1085. return {
  1086. "reason": "failed",
  1087. "return_code": 400,
  1088. "result": None,
  1089. "error": "微信授权码不能为空"
  1090. }
  1091. # 创建服务实例
  1092. service = WechatUserService(engine)
  1093. # 查询用户信息
  1094. user = service.get_user_by_wechat_code(wechat_code)
  1095. if user:
  1096. # 查询成功
  1097. return {
  1098. "reason": "successed",
  1099. "return_code": 200,
  1100. "result": {
  1101. "id": str(user.id),
  1102. "wechat_code": user.wechat_code,
  1103. "phone_number": user.phone_number,
  1104. "id_card_number": user.id_card_number,
  1105. "login_status": user.login_status,
  1106. "login_time": user.login_time.isoformat() if user.login_time is not None else None,
  1107. "user_status": user.user_status,
  1108. "created_at": user.created_at.isoformat() if user.created_at is not None else None,
  1109. "updated_at": user.updated_at.isoformat() if user.updated_at is not None else None
  1110. }
  1111. }
  1112. else:
  1113. # 用户不存在
  1114. return {
  1115. "reason": "failed",
  1116. "return_code": 404,
  1117. "result": None,
  1118. "error": f"未找到微信授权码为 {wechat_code} 的用户"
  1119. }
  1120. except Exception as e:
  1121. # 系统错误
  1122. return {
  1123. "reason": "failed",
  1124. "return_code": 500,
  1125. "result": None,
  1126. "error": f"查询过程中发生错误: {str(e)}"
  1127. }
  1128. def update_wechat_user_info(wechat_code: str, update_data: dict, engine=None) -> dict:
  1129. """
  1130. 更新微信用户信息的便捷函数
  1131. Args:
  1132. wechat_code (str): 微信授权码/openid
  1133. update_data (dict): 要更新的数据
  1134. engine: SQLAlchemy引擎对象
  1135. Returns:
  1136. dict: 包含更新结果的JSON格式数据
  1137. """
  1138. try:
  1139. # 验证必填参数
  1140. if not wechat_code or not isinstance(wechat_code, str):
  1141. return {
  1142. "reason": "failed",
  1143. "return_code": 400,
  1144. "result": None,
  1145. "error": "微信授权码不能为空"
  1146. }
  1147. if not update_data or not isinstance(update_data, dict):
  1148. return {
  1149. "reason": "failed",
  1150. "return_code": 400,
  1151. "result": None,
  1152. "error": "更新数据不能为空"
  1153. }
  1154. # 创建服务实例
  1155. service = WechatUserService(engine)
  1156. # 先查找用户
  1157. user = service.get_user_by_wechat_code(wechat_code)
  1158. if not user:
  1159. return {
  1160. "reason": "failed",
  1161. "return_code": 404,
  1162. "result": None,
  1163. "error": f"未找到微信授权码为 {wechat_code} 的用户"
  1164. }
  1165. # 更新用户信息
  1166. updated_user = service.update_user(user.id, update_data)
  1167. if updated_user:
  1168. # 更新成功
  1169. return {
  1170. "reason": "successed",
  1171. "return_code": 200,
  1172. "result": {
  1173. "id": str(updated_user.id),
  1174. "wechat_code": updated_user.wechat_code,
  1175. "phone_number": updated_user.phone_number,
  1176. "id_card_number": updated_user.id_card_number,
  1177. "login_status": updated_user.login_status,
  1178. "login_time": updated_user.login_time.isoformat() if updated_user.login_time is not None else None,
  1179. "user_status": updated_user.user_status,
  1180. "created_at": updated_user.created_at.isoformat() if updated_user.created_at is not None else None,
  1181. "updated_at": updated_user.updated_at.isoformat() if updated_user.updated_at is not None else None
  1182. }
  1183. }
  1184. else:
  1185. # 更新失败
  1186. return {
  1187. "reason": "failed",
  1188. "return_code": 500,
  1189. "result": None,
  1190. "error": "更新用户信息失败"
  1191. }
  1192. except Exception as e:
  1193. # 系统错误
  1194. return {
  1195. "reason": "failed",
  1196. "return_code": 500,
  1197. "result": None,
  1198. "error": f"更新过程中发生错误: {str(e)}"
  1199. }
  1200. # 导出主要类和函数
  1201. __all__ = [
  1202. 'CalendarInfo',
  1203. 'WechatUser',
  1204. 'CalendarService',
  1205. 'WechatUserService',
  1206. 'create_calendar_info',
  1207. 'get_calendar_by_date',
  1208. 'get_calendar_by_id',
  1209. 'register_wechat_user',
  1210. 'login_wechat_user',
  1211. 'logout_wechat_user',
  1212. 'get_wechat_user_info',
  1213. 'update_wechat_user_info'
  1214. ]