news 2026/9/17 2:01:17

Agent-harness定时任务调度实战:从手动触发到自动执行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent-harness定时任务调度实战:从手动触发到自动执行

你有没有过这种经历:Agent 开发完了,测试的时候跑得挺顺,可真上了线,每天早上还是得自己手动触发一次,或者半夜爬起来看它到底执行完没有。我最早搭 Agent-harness 框架时就是这样的状态,直到我把定时任务调度接进去,才真正体会到什么叫“Agent 自己会按时上班”。

上周我刚好把一个内部用的“每日行业动态简报 Agent”从手动触发改成了定时调度,整个过程踩了不少坑,也把 Agent-harness 里跟定时调度相关的部分摸了个遍。这篇教程就围绕 Agent-harness 的定时任务调度来写,从为什么需要、怎么选型、关键参数怎么配置,到完整跑通一个定时 Agent 任务,最后附上我实际的排错记录。如果你正准备让 Agent 从“随叫随到”变成“到点就干”,这篇文章应该能帮你省掉不少弯路。

1. 为什么 Agent 需要一个“闹钟”:定时任务调度的本质与场景

1.1 从“你叫它干活”到“它到点自己干”

先把概念理清楚。Agent-harness 本质上是一个帮助你定义、编排和运行智能体(Agent)的开发框架,它解决的是 Agent 的“工作流”问题:Agent 如何感知输入、如何调用工具、如何拆解任务、如何组织最终输出。但框架本身默认不会替你考虑“什么时间执行”这件事。

定时任务调度,就是在 Agent-harness 的外面再套一个“触发层”,让 Agent 的运行入口按照预设的时间规则被自动调用。你可以把它理解成给 Agent 装了一个闹钟,到点了,闹钟响,Agent 起床干活。

以前我需要一个“临时任务”,就在命令行里敲一条启动命令,等它跑完。这是典型的“人肉触发”。但企业里真正有价值的任务往往是重复性的、周期性的:每天早上汇总行业资讯,每小时巡检一次服务状态,每周一生成一份上周数据复盘。这些任务如果你不交给调度器,就永远得靠人记着、人触发,一旦某天忘了,整个数据链路就断了。

1.2 Agent-harness 里定时调度的典型应用场景

我梳理了一下,在 Agent-harness 环境下,定时调度最常用的场景有这么几类:

  1. 周期性信息采集与简报生成:比如每天早上 8 点,调度器触发一个 Agent,让它去抓取指定资讯源的关键内容,用另一个子 Agent 做摘要提炼,最后生成一份结构化简报推送到钉钉、飞书或者邮件。
  2. 定时巡检与异常上报:每隔一段时间,Agent 调用一次服务健康检查工具,发现状态码异常或者响应超时,就自动生成一份故障描述并发给值班群。
  3. 数据自动同步与清洗:凌晨业务低峰期,Agent 定时执行数据同步任务,拉取上游数据文件,调用清洗逻辑,写入目标表,再生成一份质量报告。
  4. 个人助理类任务:每天晚上提醒第二天的日程安排,每周整理一次未读完的收藏文章,这类任务更适合由轻量级 Agent 配合定时器来完成。

这一类任务有一个共同特征:单个任务执行时间可能不长,但执行频率稳定、逻辑相对固定,而且最好不需要人来盯着。这正是定时任务调度最擅长解决的问题。

1.3 定时调度的几种实现路线对比

在接 Agent-harness 之前,我认真对比过三条技术路线:

方案适用场景优势短板
操作系统 cron单机、简单定时脚本稳定、零依赖、通用无法感知 Python 环境异常,重试和监控能力弱
APSchedulerPython 项目内嵌的调度库进程内调度、支持多种触发器、可持久化任务单机单进程,分布式场景需要额外设计
Celery Beat分布式任务系统支持分布式执行、失败重试、任务队列需要额外部署消息队列,对轻量场景偏重

对于大多数 Agent-harness 项目来说,我建议从 APScheduler 开始,原因是它可以直接嵌在你的 Python 进程里,不需要额外维护 Redis、RabbitMQ 这些基础设施,也不需要去跟 crontab 的格式和环境变量纠缠。等以后任务量大了、需要多个 worker 协作时,再考虑把执行层迁移到 Celery,触发层保留 APScheduler 或者换成 Beat 也不迟。

2. 动手前的准备工作:安装 Agent-harness 与调度组件选型

2.1 最小化运行环境搭建

我这次的演示环境是 CentOS 7 的云服务器,Python 3.10。无论你用的是虚拟环境还是 Docker,建议先把 Python 版本锁定在 3.10 或以上,因为后面操作时区、类型标注都会省心很多。

mkdir agent-scheduler-demo && cd agent-scheduler-demo python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install agent-harness

安装完成后验证一下版本:

python -c "import agent_harness; print(agent_harness.__version__)"

正常能打印出版本号,说明框架核心装好了。如果你在安装过程中遇到依赖冲突,大概率是 setuptools 或者 wheel 版本太低,先升级这两个再装:

pip install --upgrade setuptools wheel

随后安装定时调度需要的 APScheduler:

pip install apscheduler

这里多说一句:APScheduler 3.x 和 4.x 的 API 有差异,我教程里用的是 3.x 版本(3.10.4),如果你装到的是 4.x,接口命名可能不一样,建议直接固定版本安装,避免文档和代码对不上。

2.2 APScheduler 与 Agent-harness 的集成思路

把调度器接进 Agent-harness,最忌讳的是“调度器自己在跑,Agent 自己在跑,两边互不相通”。我在初学阶段犯过的错误就是——调度器到点执行了一个空函数,函数内部再去手动调 Agent。虽然功能上也能实现,但整个链路很别扭。

更合理的思路是:把 Agent 的启动入口设计成一个普通函数,这个函数内部完成“创建 Agent -> 编排任务 -> 执行 -> 回收结果”的完整流程,调度器只负责按时间规则调用这个函数。这样一来,调度器不关心 Agent 内部的实现细节,Agent 也不依赖调度器的类型,两边通过函数边界解耦,后续要接 cron、要接 Celery 都只需要改一层包装。

在代码组织上,我建议至少拆出三个模块:

  • agent_runner.py:负责把 Agent 执行流程封装成可调用函数。
  • scheduler.py:负责创建调度器、注册定时任务、启动事件循环。
  • tasks/目录:按业务域存放各种 Agent 任务的定义和编排逻辑。

这样拆的好处是,单个任务出问题不会影响其他任务,调度逻辑和数据逻辑完全隔离。

2.3 两个必须提前确认的关键前提

第一个是进程必须常驻。APScheduler 是进程内调度,它靠的是进程内部的事件循环,不是操作系统级别的守护进程。如果你用nohup启动后,终端一关进程就没了,那调度肯定不生效。我在生产环境里用的是 systemd 服务来托管调度进程,具体的 service 文件大概是这样:

[Unit] Description=Agent Scheduler Service After=network.target [Service] User=www-data WorkingDirectory=/opt/agent-scheduler-demo ExecStart=/opt/agent-scheduler-demo/venv/bin/python /opt/agent-scheduler-demo/scheduler.py Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

第二个是日志要单独落文件。调度任务很多时候是半夜执行的,如果程序里的输出没有被正确记录,第二天发现任务失败了,你根本不知道凌晨三点发生了什么。我习惯在启动入口加一行 logging 配置:

import logging logging.basicConfig( filename='/var/log/agent_scheduler.log', level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' )

后面排查问题,基本上全靠这份日志。

3. 核心细节解析:任务定义、注册与调度参数

3.1 在 Agent-harness 中定义一个可被调度的 Agent 任务

先不急着写调度,得先把“Agent 任务本身”做成一个可以被反复调用的函数。拿我这次做的“每日行业动态简报”来说,核心逻辑分三步:

  1. 资讯采集 Agent 从 RSS 源里抓取当天的新闻标题和链接。
  2. 摘要 Agent 对每一条新闻生成一句话摘要。
  3. 推送 Agent 把整理好的简报内容发送到钉钉群机器人。

task_briefing.py里,我写了一个generate_daily_briefing(date_str=None)函数,完整流程都封装在里面:

import asyncio from datetime import date from agent_harness import Agent, Workflow async def _run_briefing_pipeline(date_str: str) -> dict: # 1. 创建采集 Agent collector = Agent( name="news-collector", role="资讯采集员", tools=["fetch_rss", "extract_content"], backend="default" ) # 2. 创建摘要 Agent summarizer = Agent( name="news-summarizer", role="摘要提炼员", backend="default" ) # 3. 创建推送 Agent pusher = Agent( name="notify-pusher", role="消息推送员", tools=["send_dingtalk_webhook"], backend="default" ) # 编排工作流 workflow = Workflow() workflow.add_stage("collect", collector, input=date_str) workflow.add_stage("summarize", summarizer, depends_on="collect") workflow.add_stage("push", pusher, depends_on="summarize") result = await workflow.run() return result def generate_daily_briefing(date_str: str = None): """ 供调度器调用的任务入口。 APScheduler 无法直接调度 async 函数,所以包一层同步函数。 """ if date_str is None: date_str = date.today().isoformat() loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: result = loop.run_until_complete(_run_briefing_pipeline(date_str)) logging.info("简报生成完成: %s", result) return result finally: loop.close()

这里有一个非常关键的设计点:APScheduler 的任务回调是同步函数,没法直接await一个异步的 Agent 工作流。我刚开始写的时候直接把async函数注册进了调度器,结果任务到点时只看到一句警告,函数根本没有执行,因为调度器把这个协程对象丢弃了。

所以,最终的调度入口必须是一个同步函数,在函数内部再创建事件循环去跑 Agent 的协程。上面代码里asyncio.new_event_loop()那一段就是干这个事的。

3.2 调度器配置:触发器、任务存储与并发控制

有了任务函数,接下来就是往调度器里注册。

APSchedulerBackgroundScheduler是最适合 Agent-harness 项目的调度器类型,它不会阻塞主进程,还会额外启动一个线程池来处理任务回调。

from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.jobstores.sqlite import SQLAlchemyJobStore from apscheduler.executors.pool import ThreadPoolExecutor jobstores = { 'default': SQLAlchemyJobStore(url='sqlite:///jobs.db') } executors = { 'default': ThreadPoolExecutor(max_workers=10) } scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors) scheduler.add_job( generate_daily_briefing, trigger=CronTrigger( hour=8, minute=0, day_of_week='mon-fri', timezone='Asia/Shanghai' ), id='daily_briefing', replace_existing=True, max_instances=1, coalesce=True, misfire_grace_time=3600 ) scheduler.start()

先解释几个新手容易忽视的参数。

max_instances=1表示同一个任务只允许一个实例在运行。这个参数格外重要,因为有些 Agent 任务耗时比较长,万一上一轮还没跑完,下一轮触发条件又满足了,调度器就会再拉一个新的实例,形成并发。假如你用的是同一个 API Key 去调外部接口,并发会导致限流;假如任务里更新的是同一张数据库表,就可能造成数据错乱。我经历过一次很尴尬的事故,就是因为没设这个参数,凌晨 2 点的清洗任务和凌晨 3 点的清洗任务重叠执行了,同一条数据被两个实例同时更新,最终结果完全乱了。

coalesce=True的作用是合并错过的执行。假设服务器因为维护关机了几个小时,期间有多次调度任务本该运行但没运行,开机后如果 coalesce 为 False,调度器会把错过的所有任务一次性补执行;如果 coalesce 为 True,则只在恢复后执行最近一次。对大部分 Agent 任务来说,补执行一堆“过期任务”没有意义,我统一用的是 True。

misfire_grace_time=3600是用来兜底的。当任务因为主进程繁忙或者其他原因没能在预定时间启动,只要错过的时长在宽限期内,调度器仍然会补执行。3600 秒是 1 小时,对于日报任务足够了。如果任务对时效性要求高,比如每 5 分钟一次的状态检查,这个值最好调小到 60~120 秒。

3.3 时区、任务 ID 与冲突控制:三个必踩的坑

第一坑:时区不一致。

我刚开始部署时,调度进程跑在服务器上,服务器时区是 UTC,而我心里想的是北京时间 8 点,结果任务每天下午 4 点才执行。排查了半天才发现是时区问题。

通过 APScheduler 设置时区最稳妥的方式是在CronTrigger里显式声明timezone,不要依赖操作系统时区。APScheduler 支持字符串形式的时区名:

CronTrigger( hour=8, minute=0, timezone='Asia/Shanghai' )

这个写法背后用的是标准时区库,我建议你在设置完后先打印一下触发器对象,确认它解析后的时区偏移量:

trigger = CronTrigger(hour=8, minute=0, timezone='Asia/Shanghai') print(trigger) # 输出内容中应包含时区信息:Asia/Shanghai 或 CST+0800

第二坑:任务 ID 不稳定。

add_job时必须显式指定稳定的id,比如daily_briefing。如果你不指定,APScheduler 每次都会生成一个随机 UUID。开发调试时还好,但部署后你需要在持久化任务存储中管理这个任务,比如临时暂停、修改触发规则,如果 ID 是随机生成的,就无从下手。

而且,当你需要更新某个任务的触发时间时,务必设置replace_existing=True,否则新任务注册会失败,直接在日志里报Job id (daily_briefing) conflicts with an existing job

第三坑:执行锁缺失。

max_instances=1解决的是同一个调度器内部的并发问题。但如果你用了 systemd 托管,又因为某些原因启动了多个调度进程,每个进程都是独立的,max_instances就不起作用了。这时候就需要引入跨进程锁。

我不想引入 Redis 这类额外依赖,就用最简单的方式:在任务内部通过文件锁来保证同一时刻只有一个实例在跑。

import fcntl import os LOCK_FILE = '/tmp/agent_daily_briefing.lock' def generate_daily_briefing(date_str: str = None): lock_fd = open(LOCK_FILE, 'w') try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: logging.warning("检测到任务已在其他进程运行,本次跳过") lock_fd.close() return try: # ... 原有的 Agent 编排逻辑 pass finally: fcntl.flock(lock_fd, fcntl.LOCK_UN) lock_fd.close()

LOCK_NB表示非阻塞模式,如果拿不到锁,说明另一个进程已经有实例在跑,当前这次直接跳过。这个方案在单机多进程场景下足够可靠。

4. 实操过程:从零实现一个“每日简报 Agent”

4.1 流程设计与子 Agent 拆分

我选的案例是“每日行业动态简报 Agent”,业务逻辑不复杂,但能完整覆盖 Agent-harness 的编排调度场景。

任务要求:每天早上 8 点(工作日)自动抓取三个资讯源的最新内容,筛选与“人工智能”相关的条目,每条生成一句话摘要,最后把 10 条以内的简报推送到钉钉工作群。

我拆分成了三个子 Agent:

  • 采集 Agent:负责任务拆解,调用 RSS 解析工具,抓取指定资讯源的标题、链接、发布时间。
  • 摘要 Agent:对采集结果逐条做摘要生成,这一步本质上是调大语言模型接口,但在 Agent-harness 里被封装成了 Agent 的一个动作。
  • 推送 Agent:格式化输出,通过钉钉 Webhook 机器人发送消息。

这样的拆分不是为了炫技,而是为了让每个 Agent 的职责单一、可单独测试。后续如果要把“摘要 Agent”换成不同的模型,只需替换一个节点,不影响采集和推送。

4.2 调度入口与主程序实现

还是回到代码。scheduler.py完整的内容如下:

import logging import asyncio from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from apscheduler.jobstores.sqlite import SQLAlchemyJobStore from apscheduler.executors.pool import ThreadPoolExecutor from task_briefing import generate_daily_briefing logging.basicConfig( filename='/var/log/agent_scheduler.log', level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) def main(): jobstores = { 'default': SQLAlchemyJobStore(url='sqlite:///jobs.db') } executors = { 'default': ThreadPoolExecutor(max_workers=10) } scheduler = BackgroundScheduler( jobstores=jobstores, executors=executors, timezone='Asia/Shanghai' ) scheduler.add_job( generate_daily_briefing, trigger=CronTrigger( hour=8, minute=0, day_of_week='mon-fri', timezone='Asia/Shanghai' ), id='daily_briefing', replace_existing=True, max_instances=1, coalesce=True, misfire_grace_time=3600 ) scheduler.start() logging.info("调度器已启动,等待任务触发...") try: asyncio.get_event_loop().run_forever() except (KeyboardInterrupt, SystemExit): scheduler.shutdown() if __name__ == '__main__': main()

这里有个小细节:scheduler.start()之后,主程序不能直接退出,否则调度进程就死了。我用的asyncio.get_event_loop().run_forever()让主线程一直挂起。如果你不想引入 asyncio,用一个while True: time.sleep(60)的死循环也是可以的。总之,进程必须常驻。

启动时用:

nohup python scheduler.py > /dev/null 2>&1 &

启动后,通过日志确认调度器注册成功:

tail -f /var/log/agent_scheduler.log

正常应该看到类似“调度器已启动”的日志。为了进一步确认任务确实注册成功,可以在代码里加一行打印:

scheduler.print_jobs()

输出里会显示任务 ID、触发器表达式和下一次运行时间。看到“next run time”是北京时间 08:00:00,就说明注册没问题。

4.3 手动触发验证与真实运行结果

等待到点再验证太慢了,我习惯先手动触发一次,确认 Agent 流程本身没有问题。

scheduler.py里临时加一行:

# 启动后立即手动触发一次,验证整个链路 scheduler.add_job( generate_daily_briefing, trigger='date', run_date=datetime.now() + timedelta(seconds=5), id='manual_verify', replace_existing=True )

然后观察钉钉群有没有收到消息。这一步非常关键,它把“调度触发”和“Agent 执行”分开验证了。如果手动触发能跑通,就说明 Agent 编排没问题;如果手动触发能跑通但定时触发不生效,问题大概率出在调度配置上;如果手动触发都跑不通,那就要回去查 Agent 定义和工具调用。

我的真实运行结果里,采集 Agent 抓到了 23 条资讯,摘要 Agent 逐条过滤后保留了 8 条跟目标主题强相关的,推送 Agent 在 40 秒内完成了全部动作。整个过程没有人工介入。

4.4 如何调整定时规则:实用配置示例

换一个需求,你可能要改成“每天 23:30 运行一次”:

CronTrigger(hour=23, minute=30, timezone='Asia/Shanghai')

改成“每周一、周三、周五早上 9 点 15 分”:

CronTrigger(day_of_week='mon,wed,fri', hour=9, minute=15, timezone='Asia/Shanghai')

改成“每 30 分钟执行一次”:

from apscheduler.triggers.interval import IntervalTrigger IntervalTrigger(minutes=30, timezone='Asia/Shanghai')

改成“每月 1 号和 15 号早上 10 点”:

CronTrigger(day='1,15', hour=10, minute=0, timezone='Asia/Shanghai')

间隔触发和 cron 触发的区别在于:IntervalTrigger是从任务启动时刻开始计时,每 N 个单位时间执行一次;CronTrigger则是严格按日历时间对齐。做“每小时整点执行”这类需求,建议用CronTrigger(minute=0),而不是IntervalTrigger(hours=1),因为后者会随任务启动时间漂移。

5. 常见问题与排查技巧实录

5.1 任务到点没触发,日志里什么都没留下

这种问题是最让人头疼的,因为“什么都没发生”本身就意味着信息缺失。

我总结的排查顺序是:

  1. 先确认调度进程还在:执行ps aux | grep scheduler.py,看看进程是否存活。
  2. 再确认下次运行时间:在代码里加scheduler.print_jobs(),检查任务的next run time是否在预期时间附近。如果显示的时区不是 +0800,说明时区配置有问题。
  3. 查看系统日志:如果进程突然被杀,dmesg里可能有 OOM 记录,或者 systemd 的Restart策略导致进程一直在重启。

有一次我遇到的真实情况是:服务部署到了新机器,环境变量里没设置TZ,程序逻辑里也没显式传时区,APScheduler 就默认用了 UTC,结果所有定时任务都比预期晚了 8 小时。从那以后,我所有调度器的CronTrigger里都会强制写明timezone='Asia/Shanghai',绝不把时区交给运行环境。

5.2 任务重复执行,同一条数据被处理两次

这个前面提到过,最常见的原因是max_instances没设成 1,或者部署了多个调度进程。

如果你用同一个 SQLite 的jobs.db文件,却同时启动了多个scheduler.py进程,两个进程会同时认为自己拥有这个任务,到点后各跑一遍。文件锁可以兜底,但更干净的方式是:用 systemd 或者 supervisor 这类进程管理工具确保只存在一个调度进程。毕竟文件锁是一种补救手段,单实例才是正解。

5.3 重启后任务全部消失

APScheduler 默认的内存任务存储只在进程运行期间有效,进程一退出,所有注册的任务就没了。解决方式是给调度器配置持久化 jobstore,也就是我在前面代码里用的SQLAlchemyJobStore(url='sqlite:///jobs.db')

用了这个配置之后,任务配置会被写入本地 SQLite 文件。scheduler 重启后,会从 SQLite 里恢复任务,但这里有个细节:如果任务是通过scheduler.add_job()动态注册的,恢复的任务依然在;但如果你更新了任务代码,老的任务记录可能还指向旧的函数名,需要显式replace_existing=True或者清空 jobstore 里的遗留任务,否则会报TypeError: generate_daily_briefing() ...这类函数缺失错误。

5.4 快速排查速查表

现象可能原因解决方法
任务到点无任何反应调度进程未运行 / 时区不对检查进程;确认CronTrigger的 timezone 参数
任务反复执行未设 max_instances 或存在多个调度进程max_instances=1;单实例部署
重启后任务消失未配置持久化 jobstore使用 SQLite 的SQLAlchemyJobStore
任务执行报“协程未运行”直接把 async 函数注册给了调度器包一层同步函数,内部创建事件循环
任务到点延迟很久才执行主进程阻塞 / 线程池耗尽调大ThreadPoolExecutor的 max_workers
错过多次任务后一次性补跑coalesce=False改成coalesce=True

5.5 日志排查的小技巧

我最后再分享一个日志排查的经验。之前在/var/log/agent_scheduler.log里发现某次任务执行了 3 个小时还没结束,但日志只在开头写了一句“任务开始”,后面全无踪影。

后来我把 Agent 内部的每一步编排都加了日志,才定位到是外部资讯源接口卡住了,HTTP 请求一直没设置超时。于是我在调用工具的代码里强制加了 15 秒超时,问题立刻解决。

给所有外部调用加超时,这句话真的是老生常谈,但它就是能避免大多数“任务卡死”类的疑难问题。Agent 任务往往要调多个外部服务,任何一个服务不响应,整个调度链都会卡住。建议每次在 Agent 的工具函数里都写清楚超时时间和重试策略,好过上生产了再手忙脚乱地查日志。

写在最后

这次给 Agent-harness 接定时任务调度,我个人的体会是:调度的技术本身不难,难的是把“定时”和“Agent 执行”这两件事完整接起来,并考虑到异常、时区、并发、持久化这些边角问题。从一开始只会在 cron 里写一条命令,到现在能清晰地把调度器、任务入口、Agent 编排拆成不同的层次,过程中的收获不仅仅是多会了一个工具,更多的是对整个任务生命周期有了把控感。

如果你接下来也要在 Agent-harness 里接定时任务,我建议先从一个低频、非核心的任务开始跑一周,把日志、监控、异常处理都摸通以后,再让调度器去承载那些真正重要的业务任务。定时调度就像一个靠谱的同事,你交代清楚规则,它会一直替你盯着时间;但前提是你得先把“后路”都铺好,它才不会在关键时刻掉链子。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 1:57:25

AWS S3大文件上传优化:从putObject到多线程分段上传实践

如果你的项目里还有人在用putObject直接传几个 GB 的大文件,我建议你把这篇文章转给他。前阵子我接手一个数据迁移工具,要从内网把几千个大文件搬到 AWS S3,最初版本就是最简单的单请求上传,一个 1.2GB 的备份文件平均要跑 6 分多…

作者头像 李华
网站建设 2026/9/17 1:55:36

VisionMaster授权更新报错“本地LM通讯出错”排查与解决

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 1:51:00

工控自动化核心技术与实战指南:从PLC到微信群的经验分享

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 1:50:52

基于uniapp的英语选课打卡系统开发实践

1. 项目概述这个基于uniapp的学生英语选课在线学习打卡系统,是一个整合了前端小程序、后端PHP/Python服务的综合性教育解决方案。作为一名经历过多个教育类项目开发的老兵,我深知这类系统在高校和培训机构中的实际需求痛点。系统核心要解决三个关键问题&…

作者头像 李华