From 2c8426d543fcac16100fac2cf0cf788eee90bbb9 Mon Sep 17 00:00:00 2001 From: konjacpotato Date: Wed, 5 Nov 2025 21:00:19 +0800 Subject: [PATCH] import arlo --- Readme.md | 7 ++ arlo.py | 36 ++++++ channel/__init__.py | 0 channel/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 129 bytes channel/toutiao/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 137 bytes .../__pycache__/toutiao.cpython-312.pyc | Bin 0 -> 4979 bytes channel/toutiao/toutiao.py | 82 +++++++++++++ config/__init__.py | 0 config/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 128 bytes config/__pycache__/config.cpython-312.pyc | Bin 0 -> 189 bytes config/config.py | 4 + database/Readme.md | 25 ++++ database/__init__.py | 0 database/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 130 bytes database/__pycache__/database.cpython-312.pyc | Bin 0 -> 1544 bytes database/database.py | 37 ++++++ database/tcontentdispatch/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 147 bytes .../__pycache__/curd.cpython-312.pyc | Bin 0 -> 4730 bytes .../__pycache__/model.cpython-312.pyc | Bin 0 -> 3051 bytes database/tcontentdispatch/curd.py | 69 +++++++++++ database/tcontentdispatch/model.py | 33 +++++ database/thealthknowledge/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 147 bytes .../health_knowledge.cpython-312.pyc | Bin 0 -> 2661 bytes database/thealthknowledge/health_knowledge.py | 38 ++++++ database/tscheduler/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 141 bytes .../__pycache__/crud.cpython-312.pyc | Bin 0 -> 2622 bytes .../__pycache__/model.cpython-312.pyc | Bin 0 -> 2750 bytes database/tscheduler/crud.py | 35 ++++++ database/tscheduler/model.py | 26 ++++ database/ttaskqueue/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 141 bytes .../__pycache__/curd.cpython-312.pyc | Bin 0 -> 1336 bytes .../__pycache__/model.cpython-312.pyc | Bin 0 -> 1606 bytes database/ttaskqueue/curd.py | 21 ++++ database/ttaskqueue/model.py | 18 +++ log/__init__.py | 0 log/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 125 bytes log/__pycache__/log_manager.cpython-312.pyc | Bin 0 -> 1828 bytes log/log_manager.py | 70 +++++++++++ log/log_prod.config | 22 ++++ mail/__init__.py | 0 mail/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 126 bytes mail/__pycache__/mail_manager.cpython-312.pyc | Bin 0 -> 2489 bytes mail/mail_manager.py | 37 ++++++ requirements.txt | Bin 0 -> 1340 bytes task/__init__.py | 0 task/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 127 bytes task/__pycache__/manager_task.cpython-312.pyc | Bin 0 -> 4659 bytes task/article_publish/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 142 bytes .../article_publish.cpython-312.pyc | Bin 0 -> 2112 bytes task/article_publish/article_publish.py | 38 ++++++ task/health_knowledge/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 170 bytes .../health_knowledge.cpython-312.pyc | Bin 0 -> 1251 bytes task/health_knowledge/health_knowledge.py | 21 ++++ task/manager_task.py | 114 ++++++++++++++++++ task/queue/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 132 bytes .../__pycache__/task_queue.cpython-312.pyc | Bin 0 -> 1462 bytes task/queue/task_queue.py | 29 +++++ task/reference_message/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 144 bytes .../reference_message.cpython-312.pyc | Bin 0 -> 1661 bytes task/reference_message/reference_message.py | 27 +++++ 69 files changed, 789 insertions(+) create mode 100644 Readme.md create mode 100644 arlo.py create mode 100644 channel/__init__.py create mode 100644 channel/__pycache__/__init__.cpython-312.pyc create mode 100644 channel/toutiao/__init__.py create mode 100644 channel/toutiao/__pycache__/__init__.cpython-312.pyc create mode 100644 channel/toutiao/__pycache__/toutiao.cpython-312.pyc create mode 100644 channel/toutiao/toutiao.py create mode 100644 config/__init__.py create mode 100644 config/__pycache__/__init__.cpython-312.pyc create mode 100644 config/__pycache__/config.cpython-312.pyc create mode 100644 config/config.py create mode 100644 database/Readme.md create mode 100644 database/__init__.py create mode 100644 database/__pycache__/__init__.cpython-312.pyc create mode 100644 database/__pycache__/database.cpython-312.pyc create mode 100644 database/database.py create mode 100644 database/tcontentdispatch/__init__.py create mode 100644 database/tcontentdispatch/__pycache__/__init__.cpython-312.pyc create mode 100644 database/tcontentdispatch/__pycache__/curd.cpython-312.pyc create mode 100644 database/tcontentdispatch/__pycache__/model.cpython-312.pyc create mode 100644 database/tcontentdispatch/curd.py create mode 100644 database/tcontentdispatch/model.py create mode 100644 database/thealthknowledge/__init__.py create mode 100644 database/thealthknowledge/__pycache__/__init__.cpython-312.pyc create mode 100644 database/thealthknowledge/__pycache__/health_knowledge.cpython-312.pyc create mode 100644 database/thealthknowledge/health_knowledge.py create mode 100644 database/tscheduler/__init__.py create mode 100644 database/tscheduler/__pycache__/__init__.cpython-312.pyc create mode 100644 database/tscheduler/__pycache__/crud.cpython-312.pyc create mode 100644 database/tscheduler/__pycache__/model.cpython-312.pyc create mode 100644 database/tscheduler/crud.py create mode 100644 database/tscheduler/model.py create mode 100644 database/ttaskqueue/__init__.py create mode 100644 database/ttaskqueue/__pycache__/__init__.cpython-312.pyc create mode 100644 database/ttaskqueue/__pycache__/curd.cpython-312.pyc create mode 100644 database/ttaskqueue/__pycache__/model.cpython-312.pyc create mode 100644 database/ttaskqueue/curd.py create mode 100644 database/ttaskqueue/model.py create mode 100644 log/__init__.py create mode 100644 log/__pycache__/__init__.cpython-312.pyc create mode 100644 log/__pycache__/log_manager.cpython-312.pyc create mode 100644 log/log_manager.py create mode 100644 log/log_prod.config create mode 100644 mail/__init__.py create mode 100644 mail/__pycache__/__init__.cpython-312.pyc create mode 100644 mail/__pycache__/mail_manager.cpython-312.pyc create mode 100644 mail/mail_manager.py create mode 100644 requirements.txt create mode 100644 task/__init__.py create mode 100644 task/__pycache__/__init__.cpython-312.pyc create mode 100644 task/__pycache__/manager_task.cpython-312.pyc create mode 100644 task/article_publish/__init__.py create mode 100644 task/article_publish/__pycache__/__init__.cpython-312.pyc create mode 100644 task/article_publish/__pycache__/article_publish.cpython-312.pyc create mode 100644 task/article_publish/article_publish.py create mode 100644 task/health_knowledge/__init__.py create mode 100644 task/health_knowledge/__pycache__/__init__.cpython-312.pyc create mode 100644 task/health_knowledge/__pycache__/health_knowledge.cpython-312.pyc create mode 100644 task/health_knowledge/health_knowledge.py create mode 100644 task/manager_task.py create mode 100644 task/queue/__init__.py create mode 100644 task/queue/__pycache__/__init__.cpython-312.pyc create mode 100644 task/queue/__pycache__/task_queue.cpython-312.pyc create mode 100644 task/queue/task_queue.py create mode 100644 task/reference_message/__init__.py create mode 100644 task/reference_message/__pycache__/__init__.cpython-312.pyc create mode 100644 task/reference_message/__pycache__/reference_message.cpython-312.pyc create mode 100644 task/reference_message/reference_message.py diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..466f7b3 --- /dev/null +++ b/Readme.md @@ -0,0 +1,7 @@ +# Arlo(阿洛) + +Arlo is a postman responsible for distribution tasks. + +## requirement + +- python: 3.12 diff --git a/arlo.py b/arlo.py new file mode 100644 index 0000000..bd40ff4 --- /dev/null +++ b/arlo.py @@ -0,0 +1,36 @@ +import datetime +from functools import partial + +from apscheduler.events import EVENT_JOB_ERROR +from apscheduler.schedulers.blocking import BlockingScheduler + +from config import config +from log.log_manager import log, logger +from task.manager_task import manager_task + + +def job_error_listener(event): + if event.exception: + logger.error(f"Job {event.job_id} crashed: {str(event.exception)}") + # 可添加邮件/钉钉告警逻辑 + + +if __name__ == '__main__': + scheduler = BlockingScheduler() + # 每隔config.scheduler_interval秒执行一次任务,同时设定第一次执行在程序启动后10秒后执行 + scheduler.add_job( + partial(manager_task, scheduler), + 'interval', + seconds=config.scheduler_interval, + jitter=30, # 添加随机抖动避免任务雪崩 + next_run_time=datetime.datetime.now() + datetime.timedelta(seconds=10) # 替代 date 触发器 + ) + + # 添加任务错误监听器 + scheduler.add_listener(job_error_listener, EVENT_JOB_ERROR) + + try: + log("started successfully.") + scheduler.start() # 阻塞运行 + except (KeyboardInterrupt, SystemExit): + log("Shutting down ...") diff --git a/channel/__init__.py b/channel/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/channel/__pycache__/__init__.cpython-312.pyc b/channel/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..516b400e7628eae44dbcabb40e1259ea61ae7e95 GIT binary patch literal 129 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 Qq3xP})nYNySE+OV2M(Q_ivR!s literal 0 HcmV?d00001 diff --git a/channel/toutiao/toutiao.py b/channel/toutiao/toutiao.py new file mode 100644 index 0000000..3ad4a7d --- /dev/null +++ b/channel/toutiao/toutiao.py @@ -0,0 +1,82 @@ +from DrissionPage import Chromium, ChromiumOptions +from DrissionPage.errors import ElementNotFoundError + +from database.tcontentdispatch.model import TContentDispatch +from log.log_manager import log + + +class Toutiao: + def __init__(self, article: TContentDispatch): + self.article = article + co = ChromiumOptions().auto_port() # auto_port后需要重新登录账号 + # co = ChromiumOptions() + self.browser = Chromium(addr_or_opts=co) + self.tab = self.browser.latest_tab + self.tab.get('https://mp.toutiao.com/') + + def need_login(self): + self.tab.wait(5) + try: + login_dialog = self.tab.ele('.login-wrap') + log(login_dialog.html) + return True + except ElementNotFoundError: + return False + + def login_with_password(self): + try: + # 1. 点击‘密码登录’按钮,切换到密码登录界面 + password_login_btn = self.tab.ele('.web-login-other-login-method__list__item__icon web-login-other-login-method__list__item__icon__account_pwd') + password_login_btn.click() + # 2. 输入账号 + username_input_box = self.tab.ele('.web-login-normal-input__input') + username_input_box.input('17704081680') + # 3. 输入密码 + password_input_box = self.tab.ele('.web-login-button-input__input') + password_input_box.input('G*9dkvm834;.,[') + # 4. 勾选同意协议 + agree_checkbox = self.tab.ele('.web-login-confirm-info__checkbox') + agree_checkbox.click() + # 5. 点击登录按钮 + login_btn = self.tab.ele('.web-login-button') + login_btn.click() + except ElementNotFoundError: + log('尝试进行账号登录,但登录界面元素未找到') + + + def publish(self): + try: + # 1. 处理登录 + if self.need_login(): + self.login_with_password() + # 2. 新建文章 + new_article_btn = self.tab.ele('.byte-menu-inline base_creation_tab').ele('.byte-menu-item') + new_article_btn.click() + # 3. 输入标题 + title_input_box = self.tab.ele('.editor-title autofit-textarea-wrapper').ele('tag:textarea') + title_input_box.input(self.article.title) + # 4. 输入正文 + content_input_area = self.tab.ele('.ProseMirror') + if self.article.ai_content: + content_input_area.input(self.article.ai_content) + else: + content_input_area.input(self.article.content) + # 5. 等待10秒,文章会自动保存到草稿箱 + self.tab.wait(10) + except (ElementNotFoundError, AttributeError): + log('发布文章出现异常') + finally: + # 6. 结束 + self.finish() + + def finish(self): + # 关闭浏览器 + self.browser.quit() + + +if __name__ == '__main__': + article = TContentDispatch() + article.title = '今日新鲜事' + article.content = '当地时间29日,阿塞拜疆总统阿利耶夫称,阿克套空难原因系飞机“遭受来自地面的攻击受损”,飞机在俄境内格罗兹尼附近尾部遭地面射击严重破坏且失去控制。' + toutiao = Toutiao(article) + toutiao.publish() \ No newline at end of file diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config/__pycache__/__init__.cpython-312.pyc b/config/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..596586d68bb627800b99226a88987ddd08afa9fb GIT binary patch literal 128 zcmX@j%ge<81g&@C(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!l8>;8NzPA6jY%xZ z$p^CX(lXOy;^Q;(GE3s)^$IF~aoFVMr^MJV3^Dh7^V1(m-zY;yBK2H6#H09AurP%Ho>J}@&fGCmL#Z{T|%DshoPsE7?H2mo;3GPVE! literal 0 HcmV?d00001 diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..8b1103a --- /dev/null +++ b/config/config.py @@ -0,0 +1,4 @@ +# scheduler name +scheduler_name = 'arlo' +# scheduler interval in seconds +scheduler_interval = 300 \ No newline at end of file diff --git a/database/Readme.md b/database/Readme.md new file mode 100644 index 0000000..3d63793 --- /dev/null +++ b/database/Readme.md @@ -0,0 +1,25 @@ +# 数据库模块 + +数据库模块主要分为三个部分:database.py、model.py、crud.py。 +- database.py:包含数据库连接和会话管理。 +- model.py:定义数据库模型(实体类),包括表结构、字段类型、约束等。 +- crud.py:定义数据库操作函数,包括增删改查等。 + +## 使用示例 + +```python +from database.database import get_session +from database.tscheduler.crud import get_task_by_id + +with get_session() as db: + task = get_task_by_id(db, 1) + print(task) + print(task.id) +``` + +## 新增数据表流程 + +新增数据表主要涉及模型定义和CRUD实现。 +1. 在database包下创建以数据表命名的包名,然后在里面创建model.py和crud.py。 +2. model.py 定义数据库模型 +3. crud.py 定义数据库操作函数 \ No newline at end of file diff --git a/database/__init__.py b/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/__pycache__/__init__.cpython-312.pyc b/database/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..926b44162584e32cfd25e1e88de7c2d1f535df91 GIT binary patch literal 130 zcmX@j%ge<81k%Z2=^*+sh(HIQS%4zb87dhx8U0o=6fpsLpFwJVDMnbuBu*uC&Da}c>D`ExeUO>7%Q6rS1ju6OPA+G+FSCKTMJs8|RmPEp#Lh>$216-7M&mB5N*ZFVPSlUc7b zyZLdHpim*9AW&`;1l+iwv?vl4SJYm)v}#bz2t*L7L~btWp`sjkYkSkEhmLk;zxU>w zd2il(Ge0L1F$C@W8(Zc62tt1aLfEu6*!vN{6=Wd`Q{>;{ur%yrYP_+-gRsNI+M!}=L^GwgM;}2Z*JaZjL_X6^1GeE@GK0r5(ShD z#F1c-V8}*z68x?Z(=Yk9L)>|vCua$%8Wg8#3RAO+RJ4LV7q37mt)Q9Us86(ziJQ!sjwhx;e9WkU8-BZV5ITX_ z=~WE7s!v zbM@SkNvZ*rE6k;I$}neHkP|Ku#$3il&u3gRsSBkm@MycZIVN6^nM~&2WA7FwP1hom z2BYqz)plVYxs{j4P4Rm2G9Dt|T5cqCkGsi0$w3pO**Ym%GP%@RyyV==%-;KdED*;tgeJtRUVf zNC6%d#|3m#I5gfV-s}{Caxt6avGxlj#q)9A!3YwN6+6wC;mWZ@1f&=Mtkl=kE}&@l9$%BU)An< zBvsc_^^U{MuWP68XdS-ij@6$zdS8_e$qy1*Ojwf}y$DMmCq7KvMd{lp{YCdS%Ix6e SeIbU^dm<82O~hUTlm7#N$9#GK literal 0 HcmV?d00001 diff --git a/database/database.py b/database/database.py new file mode 100644 index 0000000..c80c549 --- /dev/null +++ b/database/database.py @@ -0,0 +1,37 @@ +from contextlib import contextmanager + +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +from log.log_manager import logger + +Base = declarative_base() + +DATABASE_URL = 'postgresql+psycopg://postgres:K8u3fg0o@47.119.128.161:60001/squirrel' +engine = create_engine( + DATABASE_URL, + pool_size=10, + max_overflow=20, + pool_timeout=30, + pool_recycle=1800, # 防止数据库端连接过期 + connect_args={ + 'connect_timeout': 15, + 'keepalives_idle': 60, + 'keepalives_interval': 10, + 'keepalives_count': 5 + } +) +Base.metadata.create_all(engine) + +@contextmanager +def get_session(): + session = sessionmaker(bind=engine)() + try: + yield session + session.commit() # 自动提交成功的事务 + except Exception as e: + session.rollback() # 异常时回滚 + logger.error(f"Database operation failed: {str(e)}") + raise # 重新抛出异常 + finally: + session.close() # 确保会话关闭 \ No newline at end of file diff --git a/database/tcontentdispatch/__init__.py b/database/tcontentdispatch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/tcontentdispatch/__pycache__/__init__.cpython-312.pyc b/database/tcontentdispatch/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06fd099233bcef9825c86189f5b71a8df854213d GIT binary patch literal 147 zcmX@j%ge<81pimWr-SInAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWfWl*lboND8k1O* zlOL0kSdy5OSezPDlANDclA2eNl384kSdyF(6Ca2KczG$)vkyY UXapk=7lRldnHd=wiJNJROM^vJ6X_6sPlHsCUhL7?b zj`3+BBSu9=3u#YAib_C>nl!_!p4(#7>(CO=vg*|uU|a^;r#5Jc>eG}NQB|Pds5WX% zn)EE}1sqK<;>TGSm4L$!{bsfKHi@?28fuHy3Ztzsdc;}t2+(ayZK1#qc^IWv2j9QVK(<&XpUYsv$s&*nWflhCbNfg2|uaS^zT5LZ=8N@O#cq+xlf zHbXUiF2q~BdV|XFD;-bT)%Cb%>cVs)t7_A6n$Avx1mZX1x;AY%7gC)YPA3+r+H-Ts z@+YVUVv0HQv;~%7>zx8(m28XBABNY4*I&8)CX%CTqqnajF}60geyb=R+g6UOMs|Re zREY{Lhly$ogmI9ljEZnwk)`U0W0xx>;Xc(R$JHC1KTSww&4%h>HXrv3AK)wG1FZhkV)|6>=5z8aD(&wGm+U7}v-S<*~0ZD`culd9#JNmAXQjWAB} z$Q17t5~v?()T-6w=sA{Lh^aSp)KOPg7saz%?T#sAX>9Q(`cB0t+F=@5^ej|h6oF^% zjr?(>csyJP^ppZGn}L^$1H+|(QFCDQ(c~A=zeUY~$wFYN6u52%t``EoU47%9-oUrA zZ?j>e;f}Hmgm1%l_mwZ@j#vMf+kAJ8 z#F?OC@dY*J%8EV@OLf7g{|c&uH85m`haN7P;cLan^-^TkjLhcebu*$D6r-dpo62%Q z$>qhItq_T7#v)xvI8Xr#6HVMT1&RPNj;_mUIHqm(;<#ovyOLh zBgr1A>M`;U@8Y&8j9E3sMbmMBn#d1`6_8VT z=CX@a4>eIdge5FwZ&3_1mau3fD5{~>ZP8-^o-i9pW&xZaR9SMCsv3nzNKwtUKZ|zk zWbCpAj>N?vHo*0cC1Q_-D{dX@UtL%I{M!#`L6lc zW^N<*!N`Z#-oI984y}%Cw?6y9=>1E@&R(;%cXjOB{h|14xxFNJn{xNolqrY5!(Pyo zgZIyv@;NrgdhMqCi=75fqx4@N@iwo`-7z4XmjeA}puf<3wjiDRY~rsIpH7w)k7sbd z!B@idBRJ+^hU-+t1z;nm>?S=GgQ$}_shr9utMlMG36yI#99crcp)bH5Z->0Kc7eug z36QQ>lAV>X^u~cum{9d92<@0jE+q9iSHsoO=Pay+II0grWhb{IYa_*`;|1|&CGn&w zo-76jO2PAH@O&vaXa)yAPZWZe3gS>n95cnSN2B@4-{h}O7sYp-Wcc@2?Rf6BkET}v zYb7*wvd{!!qY|88d#CKeVMU@pAqjrkfDXY+pu;)-Nx*uUybEDx8Uo0QxZ*J?n#&0} zG3RlE3PU2TV7o=;)X$Y6Woxr zn@Q1gu#KJN3iH~M<@sGay{PG^1&ju6Jn+}#wijT206u*IDzFKE%jVq1T=Cecg8y{M z-)s7Ni+vYMeM4s7(8Fc3@7D$YTP6RL>7Oe2qpPof<@Ikr*ZpDm{qSFUH^$b7?jHTR zvF+~be;VI9`DNqjvOqe{?9Y3PatC`i?RRbrT@20s0mNZCw~aKe8tS>at3>uj>aLG@ zxe78K^h$D9fqC9lbX^y>6)nLdU4z#QVyXLXLBz42xG0%jJ=(0Uqtii{ya=Bjf$Ase z(SCj0Jyn#S+kJ`P``3g<@K}Bg)?Q3& zzob`S9z%@&XQ;{o$8q0~ev|ZnPddIQuazZ{Ys+`_?GR|oeueAIpSiF@pe=WD+!VK! z*df@KC5~I+K4~jMZ-sj(;OGh)eTUn+oDZEhPr%PO?j0EOaf6?~SSHZqFTJ&c?Z4Z3 duB9w{xwfsdWdhA7fgNnh0S^xShd{#)|37)9N_zkR literal 0 HcmV?d00001 diff --git a/database/tcontentdispatch/__pycache__/model.cpython-312.pyc b/database/tcontentdispatch/__pycache__/model.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af106e602f51a25c3f28ab2cecf4a8aecbe4521c GIT binary patch literal 3051 zcmaJ@TTC3+89qBZdtooExnFE<1}EN>K$ECxl-QBLV4JwCBC&PS743MubAZKj!DnV` zLmg?8+6l7Knlwp`gWQ0XwwppRjnaB!u$xCmdB|G3lFLYzT!mfWsc!L8UfTc6FnDp( z4&vW)&iBvvpYz|&eChXl5ImohkinZSgubQ|>v3)lHcrFfA`*~b6On`>$VSJEXp@)- zBiSQ%iH)$5BjS*p5vSyexFmPPZRps=BFPi+7-LrSO1_BC7&}D2R2(U`A?#z&AtX4@ zBEf}=XYHoN*-gWdz^FS|v`%M1o9-5P4Qnw82VJ`JfFi~vS$Flw8Xm+%clK!{CJ*Xt z7@yX3SEr(gn3wgUa8GY{U$~?9Wt|;}%Tb_lcJ>_V358))1cZE4|KFA<6@YoHsL+hI91 zQi{t!x8#57mKNxi8@gqdF0O!cmKWfa241lZuPnf;47_R^UR{7!8+c6t?h;EP0uwJcy5>Bxuz`*h?B6pfT7M{Xtr>}vn^YmWrRHi zy7h+cW0o#%5FQ8L?=7&_XyiqZY6wr9MUf`qJJ>(K2(4$?$nMc6gKheb@BsyKS=PE@ z>IknzhxAg7i<(QEu(=kF*8#)YTYt!%Pp&6^mAmqr)ep|*-n+VzN~|m{=FZRbbOr6Y zcZ9?wo{VxQ@Tl(RND)0iUTgYhQHJim~;@qYgOh5Rq( z^Ot8jYoDxLnay2zXFc&c5W6-RNfo?flDT3kr^2?^{|!rV zT9B)MTFi~l2AKjScN7c_DrD49$WOnueq~a3XfaL1#!7DC_4UhFbZ=Cl+Xl}Jn(O&L zU(28Wt?nC9RBa0=0voxdMDC+c*RK9i_bDS-hPJ|K&{a&bH_zqfKcj9mf)y_T{f?g1 zrC+bze24nVggt!Bid~#Ct}ectzx<)W7!?(Di*YAlrZ3j!Z?4{$UrAlh&3^!{Hl3pT z==q~VScr@8PqhnSYVX{&l^gGxvC;jODo3Nb{xqn^E2xF-@yt&z<(4k!&H;r;yhfn8 zp?Eu0GlBw#dGEvg_{7(VH@*uo`p44SPp$tvnHzsIcj1kd)GuNFG$;b9d*P6SSjGh0 zAKh$lK!Xt6BZzkLjiVso-mslP!!UaEnSwh65NiLC2M{|Tn@zP41cbfp>O(BEF-#_MUZM0Hj{}>Z|F=!{{Kt^Z{kzF7@hPJO_aX_WJZ~UhH)#v-8 zih%ohA}am#aySlmLcg{dYpV$RCD0MuMn-iv#}Pb2IBu-w(XF&u5VfN|0V09^R`S@i z_WM&ar>0}c(<%0^p3giBXFh-7miENE=|GU^Dfp%uAT#bPvaY zry=uU?BlqTabDa+{2T|d5wSTXG*5_^3L0MB9)ms?;5c5E70qZxDg=X;A)((ox?NUI z84e)SQN&F}Ed|))@YO(Qmoc(Kx2qa4lZ9q9si%UTgEUe>`z6^;MKcvGRP3SRaVi{C z?4{yKDrhZ!jEqAX{v`prN(D8qijaWk(7p1#iQc~#mroo`mZ!q$Bg5I^6N%1odxkx+ zT;DSDqZFInv;Wr6E#a%7FNVGxPDh9C)1Vz?9w5~43X|xacn(s>@d~qCUOV~yWOu4N zedJ`eoFop7hchgJ)l1J#H&4B=Q1Q7tTi%m6JRZ%kJ2PtLi3&WH|L)wyG;}WTGs?b}d)ePijdabvRqyo%q2-bB67f4ttvLo~gv}jWF0H;)In7MfCWZ9AzHDhYad7-Vh7HqA4<=8hzL$RKXW6PV z=DyGTi?um;+kGeFXkTzYK=5M*g1SEx++n1F?om&Qya>Hk8Z{Hjh@g=SM)Zv|d6-1? z57pa*7%$?ernb?WWRPHhjC9n!TOXKMB|fmGmsnk;%^DCttefdTzYZRz$T#kNX8ONK zIsl+8N__^zeY?$OyNl{pQRof|-A19i=&8G?=`PxR&*pi+RNLAov<(D58+OEa|4A1N G(*FReBa1Bn literal 0 HcmV?d00001 diff --git a/database/tcontentdispatch/curd.py b/database/tcontentdispatch/curd.py new file mode 100644 index 0000000..3581d45 --- /dev/null +++ b/database/tcontentdispatch/curd.py @@ -0,0 +1,69 @@ +from datetime import datetime, timedelta, timezone + +from sqlalchemy import desc + +from database.tcontentdispatch.model import TContentDispatch + + +def create_content(db, content: TContentDispatch): + db.add(content) + db.commit() + db.refresh(content) + return content + + +def create_or_update_content(db, content: TContentDispatch): + content_in_db = db.query(TContentDispatch).filter(TContentDispatch.id == content.id).first() + + if content_in_db: + # Update existing content + db.commit() # Save changes to the database + db.refresh(content) # Refresh the object with the updated values + else: + # Create new content if not found in DB + db.add(content) + db.commit() # Save new content to the database + db.refresh(content) # Refresh to get the content's updated state (e.g., ID if it's auto-generated) + + +def get_content_by_id(db, content_id: int): + return db.query(TContentDispatch).filter(TContentDispatch.id == content_id).first() + +def get_content_by_title_and_category(db, title: str, category: str): + return db.query(TContentDispatch).filter(TContentDispatch.title == title, TContentDispatch.category == category).first() + +def get_contents_to_dispatch(db) -> list[TContentDispatch]: + return db.query(TContentDispatch).filter(TContentDispatch.is_sent == False).all() + +def get_recent_24_hours_content_to_dispatch(db, category: str) -> TContentDispatch: + # 获取查询日期的起始和结束时间 + end_time = datetime.now(timezone.utc) + start_time = end_time - timedelta(hours=24) + # 查询最近的一条未发送的内容 + return db.query(TContentDispatch).filter( + TContentDispatch.category == category, + # TContentDispatch.is_sent == False, + TContentDispatch.creation_date >= start_time, + TContentDispatch.creation_date < end_time + ).order_by(desc(TContentDispatch.creation_date)).first() + +def finish_contents_to_dispatch(db, ids): + db.query(TContentDispatch).filter(TContentDispatch.id.in_(ids)).update({'is_sent': True}) + db.commit() + +def update_content(db, content_id: int, updates: dict): + content = db.query(TContentDispatch).filter(TContentDispatch.id == content_id).first() + if content: + for key, value in updates.items(): + setattr(content, key, value) + db.commit() + db.refresh(content) + return content + + +def delete_content(db, content_id: int): + content = db.query(TContentDispatch).filter(TContentDispatch.id == content_id).first() + if content: + db.delete(content) + db.commit() + return content diff --git a/database/tcontentdispatch/model.py b/database/tcontentdispatch/model.py new file mode 100644 index 0000000..cc954e4 --- /dev/null +++ b/database/tcontentdispatch/model.py @@ -0,0 +1,33 @@ +from datetime import datetime + +from sqlalchemy import Column, Integer, String, Text, Boolean, TIMESTAMP, func +from sqlalchemy.dialects.postgresql import BIGINT +from dataclasses import dataclass +from database.database import Base + +@dataclass +class TContentDispatch(Base): + __tablename__ = 't_content_dispatch' + + id: int = Column(BIGINT, primary_key=True, autoincrement=True, comment='自动递增的唯一任务ID') + creation_date: datetime = Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False, comment='记录数据条目的创建时间') + is_sent: bool = Column(Boolean, default=False, nullable=False, comment='表示数据条目是否已被发送') + category: str = Column(String(255), nullable=False, comment='类别') + title: str = Column(String(255), nullable=False, comment='标题') + cover_image: str = Column(Text, nullable=True, comment='封面') + poster_image: str = Column(Text, nullable=True, comment='海报') + opening_text: str = Column(Text, nullable=True, comment='开头语') + content: str = Column(Text, nullable=False, comment='内容') + ai_content: str = Column(Text, nullable=True, comment='AI编辑的内容') + closing_text: str = Column(Text, nullable=True, comment='结束语') + is_scheduled: bool = Column(Boolean, default=False, nullable=False, comment='是否设置为定时发送') + schedule_time: str = Column(TIMESTAMP, nullable=True, comment='定时发送的具体时间') + format: str = Column(String(50), nullable=True, comment='数据条目的格式') + ai_generate: int = Column(Integer, default=0, nullable=False, comment='是否AI生成。0否,1部分参与,2是。') + + def __repr__(self): + return f"" + + def get_creation_date_in_localtime(self) -> datetime: + # 将 UTC 时间转换为localtime + return self.creation_date.astimezone() \ No newline at end of file diff --git a/database/thealthknowledge/__init__.py b/database/thealthknowledge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/thealthknowledge/__pycache__/__init__.cpython-312.pyc b/database/thealthknowledge/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ec5b6d85c242e7bca2be31a5feb6f5cce11623e GIT binary patch literal 147 zcmX@j%ge<81VPu%rGx0lAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdWfWl*lboND8k1O* zlOL0kSdy5OSezPDl98I2Q<9OLmtUTfnv$Lx6Ca2KczG$)vkyY UXapk=7lRldnHd=wis&=Io(GpP$#PVuwsU$4KOe&M<&TUzk*)4Zw)ono6 z1hjmpHDX#r48>P!TA@)Rh*9}67!qyMHEkynF|sZFYph>>dG6irx-H(#o;l~9bI(2R znOg+~ZUpPCnV)-F>kN{LR5!rlgeT}-pqwCQxm3ke!kS_jKsbTGW<m@1f8$2M$_dcRU)l9L=$qhLtFsvp2WyZ|w*HCN?Vu4li549=TXFb2Jth zNt}u#{xHNu5_p6~Y*Tso31i}@m^mofu>(6*TN98>!UcT$m?P_<<63tSqZSdMCHdyM!|Qw zfDRmgID_ZIY|cPiU~ElJTPZFBZKYJ3S_WAxPm~8MtfEj0Ry6ZiTQv5PhSeTy`Azv4 z+mcUY_d;akmoJOcH=I%eF)eIc{K;RxPdGzE~79yVCs^(%)X6 z8~l1<@GCg#Gcb7(6Zw7(9`GMZU#6$S%lQHI2vHAcFD>-~fbQi^q2qw9UKgN~fy1xf zM+i6qaF(k%pAQ@nIN6@aV*$tZf$InZ+%TJ$RM)z&s-S!HmGu^&JZ&G;W05wAEta||aI5w~^S%)07} zP471iH2@W?oT=P!@#yk`AGFl^``0SEtAz-CaNAC=NBD9apNKqT-%mF73zQ% zfXkzbj%C?$%d#F*;~K@ivV1bGXv>@eSq6VJoFNH~Iq^_H<6;RBsQf}%R-)0EsnDrs zfYlDlXCbpOA;Tn@;89OVIR$j;leLs9cEJE7PD;~~u>|Nn7G**+Pc{Jf43Ui#uc5$~ z1=I@TDuA=-PM|J%;16G5;K)eeTIlBXlrNNQ?iZ%S&}`+V;eF$w8{1Qr?aBT9&kuyp zy)Y%V&sIM$+&ymIIGU;sC0qMn9um%-rqWfl!$-!|8*QnogUOcuV*|!HeM&qyTTwl1 z9}ir6IaSe;+}&S3u<2aol-M#GSUdE@$lhyvC)!S?0tD2DrbIHke&eMl$JNxPr*F30 z-1AGzPc65eolv?bdS6Mce>K@QP(CHTx+IFWZ5f}aJ1hEzdd>RgM?eM<)Omha!5&>^ zu3xe<@i-0js`a1L|MiN>ov1@7cb*1+)yg~1-qI`CxswGe_bHW~#HYDa9CRb!u`lSb zY$xNGBv=A1dv`=LG0BvG6Gj^hm#zfMyJhQ+5W|GQ$*WzKCmy8(N~UdPP_JuhSRGiU zJ=m0Enu-lm&XXaHAX5c@;{X8KF@eFlcj{8>Hc$EMr~Qvl`X5gDfh&3N_Xjs$!XNc43dmM;$5T2Xma>F}bCC!26lb}5 z3=lTsI2o|ag3M9gs+EllhY~a{p&*}$ft|iSBH%Tlx-9WNe~aP8bAp9IRx(4IYzN6& z_#5=S0Ey zTKE{&6PYGwO`K~>*05I9e?kpvMA6_aW;FE146}z|V6nkq6=x@4LlzOb1lU-l8-LXbT-lPB=Ze?`{{o7JpF#it literal 0 HcmV?d00001 diff --git a/database/thealthknowledge/health_knowledge.py b/database/thealthknowledge/health_knowledge.py new file mode 100644 index 0000000..242d90e --- /dev/null +++ b/database/thealthknowledge/health_knowledge.py @@ -0,0 +1,38 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from sqlalchemy import Column, String, TIMESTAMP, func, Boolean +from sqlalchemy.dialects.postgresql import BIGINT + +from database.database import Base + + +@dataclass +class THealthKnowledge(Base): + __tablename__ = 't_health_knowledge' + + id: int = Column(BIGINT, primary_key=True, autoincrement=True, comment='序号') + subject: Optional[str] = Column(String, nullable=True, comment='主题') + knowledge: Optional[str] = Column(String, nullable=True, comment='知识内容') + keywords: Optional[str] = Column(String, nullable=True, comment='关键字') + url: Optional[str] = Column(String, nullable=True, comment='链接') + is_used: bool = Column(Boolean, default=False, nullable=False, comment='表示数据条目是否已被使用') + create_time: datetime = Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False, comment='创建时间') + + def __repr__(self): + return (f"") + +# 把未使用的数据按照create_time排序,获取最老的1条数据 +def get_oldest_unused_data(db): + unused_data = db.query(THealthKnowledge).filter(THealthKnowledge.is_used == False).order_by( + THealthKnowledge.create_time).first() + return unused_data + +# 把数据标记为已使用 +def mark_data_as_used(db, data): + data.is_used = True + db.commit() \ No newline at end of file diff --git a/database/tscheduler/__init__.py b/database/tscheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/tscheduler/__pycache__/__init__.cpython-312.pyc b/database/tscheduler/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b57285b8fbdcc923e1f5b73727d167303e85da9 GIT binary patch literal 141 zcmX@j%ge<81V5+8rGx0lAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdr4wNllboND8k1O* zlOL0kSdy5OSezPDQkpP83g5+AQuQ2C3)CO01>YFESx)XWIP#URE< MW=2NFB4!{90G5&=kpKVy literal 0 HcmV?d00001 diff --git a/database/tscheduler/__pycache__/crud.cpython-312.pyc b/database/tscheduler/__pycache__/crud.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8ca16b4ee3f4cb0412a63bbb9fdf70396ab0ce81 GIT binary patch literal 2622 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 literal 0 HcmV?d00001 diff --git a/database/tscheduler/crud.py b/database/tscheduler/crud.py new file mode 100644 index 0000000..6f11fce --- /dev/null +++ b/database/tscheduler/crud.py @@ -0,0 +1,35 @@ +from database.tscheduler.model import TScheduler + +def create_task(db, task: TScheduler): + db.add(task) + db.commit() + db.refresh(task) + return task + +def get_task_by_id(db, task_id: int): + return db.query(TScheduler).filter(TScheduler.id == task_id).first() + +def get_active_tasks(db): + return db.query(TScheduler).filter(TScheduler.active == True).all() + +def get_tasks_by_executor(db, executor: str): + return db.query(TScheduler).filter( + TScheduler.executor == executor, + TScheduler.active == True + ).all() + +def update_task(db, task_id: int, updates: dict): + task = db.query(TScheduler).filter(TScheduler.id == task_id).first() + if task: + for key, value in updates.items(): + setattr(task, key, value) + db.commit() + db.refresh(task) + return task + +def delete_task(db, task_id: int): + task = db.query(TScheduler).filter(TScheduler.id == task_id).first() + if task: + db.delete(task) + db.commit() + return task \ No newline at end of file diff --git a/database/tscheduler/model.py b/database/tscheduler/model.py new file mode 100644 index 0000000..7fb8b6c --- /dev/null +++ b/database/tscheduler/model.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional +from sqlalchemy import Column, Integer, String, Boolean, Text, DateTime +from database.database import Base + +@dataclass +class TScheduler(Base): + __tablename__ = 't_scheduler' + + id: int = Column(Integer, primary_key=True, autoincrement=True, comment='自动递增的唯一任务ID') + task_name: str = Column(String(64), nullable=False, comment='任务名称') + trigger: str = Column(String(10), nullable=False, comment='调度方式,interval、cron、date') + interval_seconds: Optional[int] = Column(Integer, nullable=True, comment='固定时间间隔(秒),用于 interval 类型') + cron_expression: Optional[str] = Column(String(255), nullable=True, comment='CRON 表达式,用于 cron 类型') + execution_date: Optional[datetime] = Column(DateTime, nullable=True, comment='执行时间,用于 date 类型') + task_payload: Optional[str] = Column(Text, nullable=True, comment='任务相关的参数或数据') + active: Optional[bool] = Column(Boolean, default=False, nullable=True, comment='任务状态,是否启用') + executor: Optional[str] = Column(String(32), nullable=True, comment='任务执行者') + handler: Optional[str] = Column(String(32), nullable=True, comment='任务执行程序') + last_run: Optional[datetime] = Column(DateTime, nullable=True, comment='上一次执行时间') + next_run: Optional[datetime] = Column(DateTime, nullable=True, comment='下一次执行时间') + create_time: datetime = Column(DateTime, default=datetime.utcnow, nullable=True, comment='创建时间') + update_time: datetime = Column(DateTime, default=datetime.utcnow, nullable=True, comment='更新时间') + module_path: Optional[str] = Column(String(255), nullable=True, comment='任务逻辑所在模块名称') + function_name: Optional[str] = Column(String(32), nullable=True, comment='任务逻辑的函数名称') \ No newline at end of file diff --git a/database/ttaskqueue/__init__.py b/database/ttaskqueue/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/database/ttaskqueue/__pycache__/__init__.cpython-312.pyc b/database/ttaskqueue/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6f83dc782efba3ed91e8bde9f43bad612382d949 GIT binary patch literal 141 zcmX@j%ge<81pimWr-SInAOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdr4wNllboND8k1O* zlOL0kSdy5OSezPDQj%DlU09l0ni>-ypP83g5+AQuQ2C3)CO1E&G$+-rh!v=r5r~UH OjE~HWjEqIhKo$U~9wEX2 literal 0 HcmV?d00001 diff --git a/database/ttaskqueue/__pycache__/curd.cpython-312.pyc b/database/ttaskqueue/__pycache__/curd.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3374265c550147311cef3fbb86f6c0ade5124bd7 GIT binary patch literal 1336 zcmcIj&r2IY6rS1LY<`SEiyjnOOfSam!3y;tp|pic&{zb~Lyl;S_&|IkZAgroz7LQlN~(vzpYwN!=MnY7248r+lD-+>)4{GP8Ex6;cJV_NMKu3l1{p^A?pD$ib)) ziS4Hb*M#c{<)qwV>cU$N0)wnG2?jJ{;lt_u>9>g*8?CdkDjWOwsveD1qp?#_jXtfh znL3-Tve_C-d^HlX599h#{FKdm`mC$o4{-UP>XrXly=|?m$#G80TB=(k=~)~ObFXkJy8t$;*NuO@N;kkV|fd|6vMuDpeeA58#^rF*rJL%4uKIatU@ zq+KU=QJRaM9tH+8bQZzfT%S+dAGS~Z)#&3Ids1idDvQ_H?9R+LKWhx%t{4a5Mj&(; z+z%cw1fl)Vo2j$Fu-E-w0T(R48tFW;iOWYeQGh3yzqk-SEoJ0wxC?fS((O%GVs4*_ z7sKnY^T0#cQp$@*@(xhgdd>qdO`THuolG`OgAN?uYZ3T0`zW2K$L{+?b@YCV;G}7q MKrj6!@biNH0kTT?(EtDd literal 0 HcmV?d00001 diff --git a/database/ttaskqueue/__pycache__/model.cpython-312.pyc b/database/ttaskqueue/__pycache__/model.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0391c08e78fd3359874cddcc32961935037b6cb GIT binary patch literal 1606 zcmY*ZOKclO7@l3P-;Xqjod+~L0s?#>66y(B8WGw&S`yL3L1-0ewegM1OgEb)Flzg0YO9r=b|1kiaBJYNJVt>xD}|icAT;N@Bi=j&o|%y z&&)oE$0G>FofpW=Up|EXWkG!eYR>UIILk;u3a26siHKu9uW}kM@|sWdX@V$de$lT5 z#DI(W)Swm;L#{8VVJ#v?T;H!owU`*=5RUL@2q}R@qy%wn4OV7j&3hm*UI?YbM=VNn zG^EHDw(=TI`)S}^L$x)X1|}?$*Jo&Oz%W!S>olAh8NED_d1v%}D&%ZE3-ba4BSRx& z8SuiuAZJzCG$9ZMWD{p=E4hzGoiB5&PK-oOK_Wj1^@=_oVFCLU9)7-cVNLKRiUAzN zA;o_ILY4vlu)?ng)_{Qxh$GiKJipo2D^!9?Xg%Cui{Uu1Me1xg0jovV8+{R5@EO44 zr|?7*-s<8lr|>gPc$YB)m@W3po>B&E*|?rx@el7d%WX4ox6mm%+D zvHkL@^Y!xKFB{J9i!`bkimhU4UbbeNa-5sL?cdpSTt*saC(z0py5ur3dQbNLr*{um zZyy$K)DpvHb`~=_Vt1V+t!y}@@4Zy#$J_f$C8#i1E3HXuD3{msCd?m8chZpLt|dt{ zB1tug5{*f)DYD8`01}sApP(sTNZ3&(VP;sV)R%|ew@8w#>xLyW^`<0|Aj3m0t{Wc{ zcBBcrt;Ekvrwf=CAxu8$X66}Y*p(t@!BAluZs#nFEf$!am`&wcgIYxi-+{@1ZyL(2IMh_aPT~!iTw8u9Q4R?0M`%eDpu}l}q|R DQA6TB literal 0 HcmV?d00001 diff --git a/database/ttaskqueue/curd.py b/database/ttaskqueue/curd.py new file mode 100644 index 0000000..0aceed0 --- /dev/null +++ b/database/ttaskqueue/curd.py @@ -0,0 +1,21 @@ +from database.ttaskqueue.model import TTaskQueue + + +def create_task(db, task: TTaskQueue): + db.add(task) + db.commit() + db.refresh(task) + return task + +def get_tasks_to_finish(db) -> list[TTaskQueue]: + return db.query(TTaskQueue).filter(TTaskQueue.finished == False).all() + +def finish_task(db, task_id: int): + task = db.query(TTaskQueue).filter(TTaskQueue.id == task_id).first() + if task: + task.finished = True + db.commit() + db.refresh(task) + return task + + diff --git a/database/ttaskqueue/model.py b/database/ttaskqueue/model.py new file mode 100644 index 0000000..0b593f4 --- /dev/null +++ b/database/ttaskqueue/model.py @@ -0,0 +1,18 @@ +from datetime import datetime + +from sqlalchemy import Column, String, Boolean, TIMESTAMP, func +from sqlalchemy.dialects.postgresql import BIGINT +from dataclasses import dataclass +from database.database import Base + +@dataclass +class TTaskQueue(Base): + __tablename__ = 't_task_queue' + + id: int = Column(BIGINT, primary_key=True, autoincrement=True, comment='自动递增的唯一任务ID') + create_time: datetime = Column(TIMESTAMP(timezone=True), server_default=func.now(), nullable=False, comment='任务创建时间') + task_name: str = Column(String, nullable=True, comment='任务名称') + module_path: str = Column(String, nullable=True, comment='任务模块路径') + function_name: str = Column(String, nullable=True, comment='任务函数名称') + scheduler: str = Column(String, nullable=True, comment='任务执行者名称') + finished: bool = Column(Boolean, default=False, nullable=False, comment='任务是否执行完成') diff --git a/log/__init__.py b/log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/log/__pycache__/__init__.cpython-312.pyc b/log/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..de331d3d1aa14e0c1f39b4eeff5328afbccf3ff1 GIT binary patch literal 125 zcmX@j%ge<81Sh&9(?RrO5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!l8La2NzPA6jY%xZ y$&bm&PmhU@&&$Z07)=i*-4V1V-)L=3hY8d}AEQusWo0;a(dso`Dee>P7 zxh*4f1T#oxlmr|JGm*rY86sKyswm(6(mBY=#Sb&cV4v(VKPaC(_qBxrdXszZx##}Q zx#ynyyZ4voW*35XY5k-09TlN_Vp4BfP1*PcBQ%Q$A~=T<*dC>XWRG$}(GZb{OcdfE zY8n&goRq;tB~38bsFbzi*F#oE;+j(cqY*dpkY*e2opU5q(von3N9)sQF-vF?qAt>U z7E!GqU8?~Iz9v=Ggqz@mhq}!!>dto8mKz4rb`8!T+8}r&ya^BSQO_0hrIc_MB;tpC zJ?0A(XPc_1Mw#8xIIHT^Q;&yHq5FUQUi=R~^@$&x2xqrGk0DAOlkibrcKh=P>Z~I$ zBJH$&NFsr=N+K|hqro)+kFSZ-9^{H?7#3HK#19_h-jq>bMvflV3nWL)sKgyPI!bfA z>15TyCIUGloz4`}vE!CW^?VJGkNs%};8F$tcQ`{qWJSoRu1hDAIyk3CQnz7;WN7~*rEzq%HbDtRkm)+1= zCy0mW?;Et- z6|#eRaV1k2GPqhTIQO#DNlHe5ze$@)*c6YQiK`8pqJ~@sKU2J_17hJN5JmKx;+h_q z8o0P;w(mmUvVYg2e^<%3djT(OUkH_2-?**p-2fbl2Dw-4DqcWsKbuc5=Q0~-yt!(v zEt+BQ5!5Hfh{e4}j~z<(A9ydh|5*Iskwe@Ek&;z>vQ}NL082(~$+!Xxuy_lgYM^1= z)bou7`(n9+4d*fz$4OvwCP(+%#N#fKNm(^rOH=EpT`Y4mWMqo(3i7o~y9{PP*aX+@ z`aEjdn>R!O_nCXZCJM)bAS*J)_$~_EMXr0Oy`pGXsdS;%wsJVK(&Q}%y33(xsdMML zLvC|bWJjx7@ggl$4!mqHZ7l~wv)K#Tb+nfM zZ{-yf>?|SQiqkbcHZ@jM)>Y*1SQNxx>gc~omV5)nma;EcY`G)*Rzf?LL$SqBY@RKJ zUR_jz#kY&6%iXd0)1}y(rO;boSqZ&cQi8t&R&h_qr{c@XrbT7bW#hIItEkA~X|NS; vDLpG%*SU^mt!q*1y1Zve+gemtkUH(2a+f@tZ=o#}7303o{eK}C?7IC6_WjxH literal 0 HcmV?d00001 diff --git a/log/log_manager.py b/log/log_manager.py new file mode 100644 index 0000000..d7b5dd5 --- /dev/null +++ b/log/log_manager.py @@ -0,0 +1,70 @@ +import logging.config +import sys + +import config.config + +""" +Usage: +1 code +from log.log_manager import logger +logger.info("Starting Jarvas") +2 app start +python demo.py --logconfig=log_prod.config + +当前目录下的log_prod.config是一份参考配置 +""" + +# default logging config for development +LOG_DEV_CONFIG = { + "version": 1, + "disable_existing_loggers": False, + "loggers": { + "root": { + "level": "INFO", + "handlers": ["consoleHandler"] + } + }, + "handlers": { + "consoleHandler": { + "class": "logging.StreamHandler", + "level": "INFO", + "formatter": "verbose", + "stream": sys.stdout + } + }, + "formatters": { + "verbose": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S" + } + } +} + +log_config_message = "" +# 获取命令行参数 +args = sys.argv + +# 查找包含 'logconfig' 的参数 +logconfig_param = next((arg for arg in args if '--logconfig' in arg), None) +logconfig_value = None +if logconfig_param: + # 如果找到了 logconfig 参数,提取其值 + _, logconfig_value = logconfig_param.split('=') # 以 '=' 分割 + log_config_message = f"--logconfig value: {logconfig_value}" +else: + log_config_message = "没有找到 --logconfig 参数,使用默认log配置" + +if logconfig_value: + # 使用入参日志配置文件 + logging.config.fileConfig(logconfig_value) +else: + # 使用默认日志配置 + logging.config.dictConfig(LOG_DEV_CONFIG) + +logger = logging.getLogger('root') +# 打印日志配置信息 +logger.info(log_config_message) + + +def log(message: str): + logger.info(f'{config.config.scheduler_name} {message}') diff --git a/log/log_prod.config b/log/log_prod.config new file mode 100644 index 0000000..fe2255b --- /dev/null +++ b/log/log_prod.config @@ -0,0 +1,22 @@ +[loggers] +keys=root + +[handlers] +keys=fileHandler + +[formatters] +keys=verbose + +[logger_root] +level=INFO +handlers=fileHandler + +[handler_fileHandler] +class=FileHandler +level=INFO +formatter=verbose +args=('app.log', 'a') + +[formatter_verbose] +format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +datefmt=%Y-%m-%d %H:%M:%S diff --git a/mail/__init__.py b/mail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mail/__pycache__/__init__.cpython-312.pyc b/mail/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38e93f3928b145a312df2acb91e3e7674e4e52ba GIT binary patch literal 126 zcmX@j%ge<81Tq`K(n0iN5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!l8vy6NzPA6jY%xZ z$&blR%*=_2kI&4@EQycTE2#X%VUwGmQks)$SHucb%?QNBAjU^#Mn=XWW*`dy^a&c3 literal 0 HcmV?d00001 diff --git a/mail/__pycache__/mail_manager.cpython-312.pyc b/mail/__pycache__/mail_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..565e92c9f1d225030b1267abad2c35bdf851d884 GIT binary patch literal 2489 zcmaJ@O=ufe5Pom}WyzM~|Gzj&i1qH;UUh%$x2xC| za`C|j8wyED!6~8Oqk}I#MYoSEMMq=wgq4dz3Qx{s&OXuxsRoVG@pn3CV=grK{ zd^4+`>g($eEd1=}iLZEsexrwQi4|ry4a^Esk%|qJraNO`lSwm}@~pv{T$=NF&frZU zEnvi;i%8`ckt(b+ft9qVMpaRhR0)0&O&S-}x*Di9KoL^2qsX+N=wF zJ>^GohO1A>gbG;0o=^f~0j;=YZR zrP$5raK zL#Zt%g`E$9Gnm581sbfdJo6lsrvm%%8Qlw@ooTo&4I|8hf!qXMIH zA$Zm+9svg2n_x1}M<`JI2H$HrTfg^APp^rYE%56Vs8aYGL2g5Mw-1r-dBHspo>jge z+=Z4e;JfHY?*AyS!j58vFq59cuQE0mphIE!!6gB5B32Q zDN0>sDSoqIx#9cSRdKETY5Va_X|Nn`E5#GVc;adNttD=UVI!AtIo5dd%JP*`EK!Ul zR$EGk2aAUXH%1?x+lpQOXFJ~UC&Hk$9g9C{O07+=DI47nzI<@`N#li5>|!x?@!uVm zK7ZkKKy;59-}!V1JsOIf?O`9|183XW$L&1uN)_OsL}BfNe+ZZr3L4n=uWSV{weqkz zD2VRi(IgYpgPYFjFx>szJJeAnslqt)zcZO{s?BI1>j5o?W*(Y9Z1Ikw1nt~KT0ji8 z^9PO(44xSHWMF9EUtK0bJY&cmcg zhmR$jS|;PwWiqC%<_yZmGMVX|Y*e1G04mZ9-bf!M?*h4peD^*m2t|oc=wx(gvKG;E zhU1UjQFah)4?ozY+w+Dx{t{ko-a&L37A(7 zCAPpUG#A)c)uw<7I2NZ zYbkP8#$5lsSV9ksBU%Zs5n_7zYcrZ6wYNTfPC&V8zUK~DsDfoo}`{N^c z$M?Zt5OIof6pg`*kWK|SIzv7L8O6Et5fErUjDJB5&rshpl=>YV`detl-OmvvDFJug T0zkmFdez?^H2+0UzS;i(Nv9Ip literal 0 HcmV?d00001 diff --git a/mail/mail_manager.py b/mail/mail_manager.py new file mode 100644 index 0000000..33e5fd7 --- /dev/null +++ b/mail/mail_manager.py @@ -0,0 +1,37 @@ +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart + +from log.log_manager import log + + +class MailManager: + def __init__(self, email: str = "1026090807@qq.com", password: str = "hqorvminmnuubebf"): + self.sender_email = email + self.server = smtplib.SMTP('smtp.qq.com', 587) + self.server.starttls() # Secure the connection + self.server.login(email, password) # Login with your email and password + + + def send_mail(self, subject: str, content: str, receiver_email:str = "changsongd@126.com"): + message = MIMEMultipart() + message['From'] = self.sender_email + message['To'] = receiver_email + message['Subject'] = subject + message.attach(MIMEText(content, 'plain')) + text = message.as_string() + try: + self.server.sendmail(self.sender_email, receiver_email, text) + except Exception as e: + log(f"send mail failed with error {e}. subject: {subject}") + + def finish(self): + self.server.quit() + +def send_mail(subject: str, content: str, receiver_email:str = None): + mail_manager = MailManager() + if receiver_email is None: + mail_manager.send_mail(subject, content) + else: + mail_manager.send_mail(subject, content, receiver_email) + mail_manager.finish() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..32b3743acf30a5108e5c4d59c8372293742069de GIT binary patch literal 1340 zcmZXU%}xSA5QO_|;-f%h7yLPRF!81bl?O2EqDWi;VF}>FtMzs7xQk{oK?Pr@i60%wN9*WXWA!rY)5uwcQ&@tULCu!O5ZET<-!cK60-D2 zZ8Lk(hA9(AW<$qi{yB6`*b1?%l~GF{KP!9Jf0Z&n4-_IBC#6SAdvoq{=j+>f!?;is zmcn_`ImkY35Nqk7$O%g6g26q`;;h$RlfoVx0o&xj%c-OBaxJZ`e4|+ibtB)w7;IE9 za4SX7oe4QtEeo596YV$|+SGC6cJhhr!+|qR<^EX< zkFdfLn1q}*P~{jYxO2K z)$=?iqEAy{R&1#CzUMPAvp;u#*z=m1c+D zfHSqJyY?-}{C;XpJNLtD6ctG?MjMYV=WPIiYQ#2jQ)$gq^}NaM icI#Fyo}?c$5-&}>A^2+Nzpd5Q>%d&4`y0Am+~5!Rg~d7m literal 0 HcmV?d00001 diff --git a/task/__init__.py b/task/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/task/__pycache__/__init__.cpython-312.pyc b/task/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea6fdc6fa81ba475038d272b09616a2b5906c2b6 GIT binary patch literal 127 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 b&KJox*|T+t0J?ZkN0J`7w&e){6sz-Z7kEur literal 0 HcmV?d00001 diff --git a/task/article_publish/__init__.py b/task/article_publish/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/task/article_publish/__pycache__/__init__.cpython-312.pyc b/task/article_publish/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21d706f84943bb76f3998a2fb3fb44418b5e0e68 GIT binary patch literal 142 zcmX@j%ge<81kG$k=^*+sh(HIQS%4zb87dhx8U0o=6fpsLpFwJV=|)(^Bmein literal 0 HcmV?d00001 diff --git a/task/article_publish/__pycache__/article_publish.cpython-312.pyc b/task/article_publish/__pycache__/article_publish.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93c06544183c07329833921eea0f222334800138 GIT binary patch literal 2112 zcma(SOKcle@V(u&*WWl!q9jh_)HS5k5Sg?JLPP{9To65^Dnt#n0?T@L8#`IsVLwe1 zIZ}}-;VPBN0hLMxshj}iQuS1E>zRu~TP0m37l@F03k_1Fo|t*NYX@`TY2TZ9GxO%n zo8SH#k4FfM)93Cld=((%58U{J^N_>afP70-qEekyXn|71Ii0KU1)kx&E>r@A0K)}6 zSP=^%!vlJ#5-x-p9@Hb1Xd%jQQIA#Pg*d}QdRHY;NB|zbP72*>1lFV))x^u38oMqO zdbD0OzI2)pBg7Fcx#<~2$!gbXS1$1!_c`i_vo+f)%QZlw3z{XFnrW76RoK3UTSHT{ zswJJBkV-Y%FeRl{wUA)dB(-cV%a&3C(a7w_N=Z{~T{BRRVra6ZNtSFbG3G2F7i`Ve zV8`pV1pqPas;1bUkmA|K0Q!}Oc}OUF&w|w*{tMnB>%>9{HtOKrP0!=&v`&o+7R1Bj z(M|94I#=fwMW5b=zD`Q4uuoOFI$dPZ^(ijAOH>{>v42x>2OI%7iT`qZ{ddXtLEjb@ zJ;(&#YKRDVg`6QKUEwIX1kXmwRsIrLp}D|}jfVYs`~LF>-|hdhvDUq87xl7P%E*RQ zR&*`1Hkz?&@UbXnOj}VPf|-@F)fOqMlbN;Nj9gV)r>t^Co6O`wh5*_P^q7Gzbwp3S zfwwvVt8D3-!3!+kf?ce49E?>x*tTzcGd zt|2~)_U!kk?<_p-Kh^kPC)RuG{EhQZV(I-@qS=$Z_x6udKTO?wvw7xXbMCY4IeBYN zZeA=li+1zM`Vj$-f6D#kf7MByvQ9TNo3$w`bPStiOH}rotd8C zA58?Nr$hzzt$|O+d6_-vK#pl%J|lUbJ0aK6RZ_X`Pc=hS$j= zYU&sufx6iMiI9)CAxN~#lPqHPbqtVbX|`G=RfpT#1p{sL{}vE8;|zGYD0Mi|&we!1 z31do1Rk@-$(MkbpRUA!B$2&l!+c{g+; z9k)uh>9$7c3v7wTNjNtQ)0~9`g6L-SZX&f&ZwNc7vkl>9*Us>(+r#5q!{ZI%Noa5< zeQG4gY(_7%;GO%nxBX;3=hh_JeZBR?%35&#E%M2I!ke&bWmgu$o6&;d_`A$HY2O8Wmz literal 0 HcmV?d00001 diff --git a/task/article_publish/article_publish.py b/task/article_publish/article_publish.py new file mode 100644 index 0000000..e154887 --- /dev/null +++ b/task/article_publish/article_publish.py @@ -0,0 +1,38 @@ +from channel.toutiao.toutiao import Toutiao +from database.database import get_session +from database.tcontentdispatch.curd import get_recent_24_hours_content_to_dispatch +from database.tscheduler.model import TScheduler +from database.ttaskqueue.curd import create_task +from database.ttaskqueue.model import TTaskQueue +from log.log_manager import log +from task.manager_task import execute_task + + +def article_publish(): + # 1. 从数据库获取文章 + with get_session() as db: + # 2. 获取所有未发布的文章 + article = get_recent_24_hours_content_to_dispatch(db, '新鲜事') + if article: + # 3. toutiao发布文章 + toutiao = Toutiao(article) + toutiao.publish() + # 4. 打印日志 + log(f'publish article {article.title} to toutiao success with article id: {article.id} and article time: {article.get_creation_date_in_localtime()}') + + +def article_publish_task(): + execute_task(article_publish) + +def article_publish_use_task_queue(scheduler: TScheduler): + with get_session() as db: + task = TTaskQueue() + task.task_name = 'toutiao_article_publish' + task.module_path = scheduler.module_path + task.function_name = scheduler.handler + task.scheduler = scheduler.task_name + create_task(db, task) + + +if __name__ == '__main__': + article_publish_task() diff --git a/task/health_knowledge/__init__.py b/task/health_knowledge/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/task/health_knowledge/__pycache__/__init__.cpython-312.pyc b/task/health_knowledge/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39c2f499af57a0648d9b9cc8fbc2afec30079717 GIT binary patch literal 170 zcmX@j%ge<81ix>dO9#=9K?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^%TqtJIJKx)KRZ7! zD>1nsza+6FU*9D^ximL5ucTN%IX@*;Kd~q$U%w==I9op>H8H0oBR)GXzdR>3B|TL? rK0Y%qvm`!Vub}c4hfQvNN@-52T@fqLWJVw^1~EP|Gcqz3F#}luC!Z}1 literal 0 HcmV?d00001 diff --git a/task/health_knowledge/__pycache__/health_knowledge.cpython-312.pyc b/task/health_knowledge/__pycache__/health_knowledge.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d85f77f3847dd9e9341e0a00d769a2732c52e4e2 GIT binary patch literal 1251 zcmZWoO=ufO6n?Y2(yk<1iS3ZY39^NKaO9YkmEa_XP-+?onBo%B9IVS`y*sgG?N72Z ziXtIH4mmiqkltb_J(ONt+(V8&rnf*ZO6!njv1uVD;G4nFf=_v~*4n|DW#4@7XWo1B z-n>7$yVHQ{ziaLJ2Qt8W!Ng1HfW3zZo`VfGalj>ILIjo^$(75ptCW?9mz{*0EGHwZ zII63awFoDil$$Q65hrQ#o@A>}lyZi4*;@4iK#-MyX7Eq0b%L#t13;jWYNQJ39l;hz zn3<>CV3e_n?-iu*v|xS5ri>dkug0it*d{l_9@h-25j0F@2sxfje|y_npmxon0dlhA z&*PS2)UyrOtT+f`wbh9p<&Z*(HG#bkNS*^f)Wx|@z!KrYB3dmH=e{Jhq{X;;u^RU! z8Rf}O1Rc1fa1kq7ExD!G#5!AU$v=Xvpas~8bAUMcQzDLvmff*si?pOQ z>41y6fE_#H8!!wdV5BY)m<57g10KnbU3z%m$%fhDQ>lMC`=N0bId^0_kw|tLNkLPzzVGbeD3u++l6NsXODyz*c zQj3SFs^`}oYR}U^6IxY}1d>7`5CsV(doDb=Kuw1)7)Q2Qp;9{`n6S@_wSTL4htYr) ztG>5rTK9c!a=&=Pw`wk?!itt})1nzTevzB3T0HLdGl71;5e`O=gw6(MU_71)69ev$9x~%>!;MnWlv1N5r>-k}1w-9s2L(F&oE{R-C>WIg*lT^JqA(v!_k=4`N5qIi>e>LW7Mjs z8Dam?sl!3hU96@Ph{qhBz=6{7#{@hRdl~gnOk%~+1v!*QO^#j!X6uXEY6N8EKMy|0#Kj=SM`lJw#v*1Q F3jp-L9dZBw literal 0 HcmV?d00001 diff --git a/task/queue/__pycache__/task_queue.cpython-312.pyc b/task/queue/__pycache__/task_queue.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30f0fbe04b9a6f895ab7e441dad06edfbc9f9980 GIT binary patch literal 1462 zcmZuxO>7%Q6n?Y2UjJ^qC6*e43|7)VBV;8YI9RBqp%lcS7dRvrTny!7C7!@e9h1pPB>~^vXh@H#)Ol#Q!7^h@Pq^opX$zO06QoI z2)d|?O6kA2daHzJylr9PS{{SxhC|H22?Dp(WNPZ;k7~<~-SHe;lISGgBv!Bzn51Ra z-KHBX({w0>ot==qR)dL*bH}N5h$GB28AP|#PGb;}B-tvnd9uN*n?S~UQN(XT9}(^$ zMh`_9^d%A-TcyZqqm>&h?+p@<_U!-lyAR)v&mibYH{g>tKwl<29x-ld(Qm3FavWCpjF1eML_K7!X0@LR#8d0g?V6W#egts6nW2!pLU#% zQ(Se)a;OLHnp0eh7sLLSH1-BoeU&YY1WpqtI7+&HyM>A8E>SInrr+XxqUlhYZHp{p z-eb(mPveYZ79p5w^-i-!7!A#)x 0: + log(f'start task queue with task size {len(tasks)}') + for task in tasks: + # 1. 动态构建任务函数 + module = importlib.import_module(task.module_path) + task_function = partial(execute_task, getattr(module, task.function_name)) + # 2. 执行任务 + task_function() + # 3. 标记任务完成 + finish_task(db, task.id) + # 4. 打印日志 + log(f"{task} finish") + + +if __name__ == '__main__': + start_task_queue(TScheduler()) diff --git a/task/reference_message/__init__.py b/task/reference_message/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/task/reference_message/__pycache__/__init__.cpython-312.pyc b/task/reference_message/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f4a07d16319a7c2b272c4d3300bc6877294b32b GIT binary patch literal 144 zcmX@j%ge<81ZXEY6N8N=-{GO3h17jn7RjE>28Ojfs!X%*!l^kJl@x{Ka9Do1apelWJGQ3e?XC R#Kj=SM`lJw#v*1Q3jnt6A{zhz literal 0 HcmV?d00001 diff --git a/task/reference_message/__pycache__/reference_message.cpython-312.pyc b/task/reference_message/__pycache__/reference_message.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6705facb6a2d75bdebb8a61e41fb10051bb8317c GIT binary patch literal 1661 zcmZ`(Uu@e%7{9X}CvnpzWu^aUNjfUJl;E%d>X1sMZWDz)Oe}?m1t#O#ZtB){TA#C$ zD3N&Jp%azH+a@8TA%qy}OL^;K1uvC&aagAmlL{nw*;}O|A?=Cp+_@azEdmRPMk%&f!UbNDT*H2BQXgQkY5XmV@s!o<=M_KS*nAa_xESx$CIKl_B%7Uhr z49y0MHHrlgQKDI@WXie$YA~&|GV$}Fr^G@(+yU=h zVdTUEpNgxYub{G39_q^uBG9?7L+waFN(8ZON$|RLGD|g7fyvWj+qs_Dm2d5OD*EY} ztbflT=S+}86<66dGOU2FZI{tY=sY5LmBHu+!tj;Rr|b>1iqqk1m0?=?6fJ#&S$K@D+F|MtWIf=o9Rf!>Y*QL|j5_Ca2TTtlh{x(G@e=>{dtp+VThtWC z?t-<10;i7JliBQtM6*eDsc0?A%5u?>onrP%Q7M`9I%Sojs%2%{C}tg*EM;wNUb8hz z(IgX&M=oejd&K3km+~*sQ^EeVk|>bf4=Kg}s; z-&mt>yxupy8+)Z0i@VQdH^UFgTf+9l&g(muwkNkm_xVxBMX0nl)sb^+(6Yn+?XWZVYt<^1kyW-~N8acC`GhJ)NEv~pZ+r3U);(KRqZqdjHY`b~lpwH;IJ4JHxk!pAbsTFQ(tV+|T|Lo#sLO+#k9;680hz zJq}BfuBAJnlCmSu$wU(a#V3mQVql6+izY#7MWtk`K9PJ{cmwUo<7Z1Wp?Zc*9}_PQ z&zlGxHnpH`_7S_fDN{u{9Q$ci{PNHYE!os0=?8QOPGt8{pvWYk!kal|p9PaXe&j6} z4p@xwK1%)tMOQl^Oj literal 0 HcmV?d00001 diff --git a/task/reference_message/reference_message.py b/task/reference_message/reference_message.py new file mode 100644 index 0000000..33f9faa --- /dev/null +++ b/task/reference_message/reference_message.py @@ -0,0 +1,27 @@ +from database.database import get_session +from database.tcontentdispatch.curd import get_contents_to_dispatch, finish_contents_to_dispatch +from database.tscheduler.model import TScheduler +from log.log_manager import log +from mail.mail_manager import send_mail + + +def send_reference_message_mail_task(scheduler: TScheduler): + with get_session() as db: + # 获取需要发送的内容列表 + dispatch_contents = get_contents_to_dispatch(db) + # 发送邮件 + ids = [] + for dispatch_content in dispatch_contents: + subject = dispatch_content.title + content = dispatch_content.content + send_mail(subject, dispatch_content.content) + ids.append(dispatch_content.id) + log(f"send mail success with title {subject}, content {content[:20]}.") + if dispatch_content.ai_content: + send_mail(subject + '[AI]', dispatch_content.ai_content) + log(f"send ai content mail success with title {subject}, content {dispatch_content.ai_content[:20]}.") + # 更新数据库 + finish_contents_to_dispatch(db, ids) + +if __name__ == '__main__': + send_reference_message_mail_task(TScheduler()) \ No newline at end of file