本项目推荐使用 trendpublish.config.ts 作为主要配置来源。它有 TypeScript
类型提示,适合集中组织模型、抓取源、微信发布、图片和通知配置。
cp trendpublish.config.example.ts trendpublish.config.ts
deno task doctor部署级配置只从 trendpublish.config.ts 读取。Dashboard
可编辑的运行时业务配置会写入 SQLite/D1;密钥、binding 和外部服务连接仍然只放在
TS 配置或部署环境里。 doctor 会按功能块检查缺失项,并把
your_api_key、change-me 这类占位值视为未配置。
默认读取当前目录的 trendpublish.config.ts。需要切换配置文件时,可以显式指定:
deno task doctor --config ./config/trendpublish.config.ts
deno task article --dry-run --config ./config/trendpublish.config.ts
deno task dev --config ./config/trendpublish.config.ts也可以设置 TRENDPUBLISH_CONFIG 指向配置文件。Docker 镜像默认读取
/app/config/trendpublish.config.ts。
配置仍然是 TypeScript 结构。如果部署环境里的密钥或地址需要动态注入,可以把
defineConfig 写成函数:
import { defineConfig } from "@src/utils/config/define-config.ts";
export default defineConfig((runtime) => ({
server: {
apiKey: runtime.required("SERVER_API_KEY"),
},
providers: {
ai: {
baseUrl: runtime.value("AI_BASE_URL", "https://api.deepseek.com/v1"),
apiKey: runtime.required("AI_API_KEY"),
model: runtime.value("AI_MODEL", "deepseek-chat"),
},
},
features: {
article: {
dryRun: true,
sources: ["https://news.ycombinator.com/"],
},
},
}));这样只有“哪些值从运行时来”是显式的,不会出现通用覆盖规则导致配置来源混乱。
如果只是启动服务、预览模板、跑 AI 摘要和动态模板,先填这一组:
import { defineConfig } from "@src/utils/config/define-config.ts";
export default defineConfig({
server: { apiKey: "your-api-key" },
providers: {
ai: {
baseUrl: "https://api.deepseek.com/v1",
apiKey: "your_api_key",
model: "deepseek-chat",
},
},
features: {
article: {
renderer: { template: "minimal" },
dryRun: true,
},
},
});如果要在固定 IP 的本地服务器或 Docker 里直连微信公众号,还必须填:
providers: {
publish: {
weixin: {
appId: "your_app_id",
appSecret: "your_app_secret",
},
},
}如果是 Cloudflare Worker / Workflows 这类没有固定出口 IP 的环境,推荐发布到 固定
IP 机器上的 weixin-relay,Cloudflare 配置里只放 relay 地址和 token:
providers: {
publish: {
weixinRelay: {
url: "https://relay.example.com",
token: "your_relay_token",
},
},
},
features: {
article: {
publisher: { provider: "weixin-relay" },
dryRun: false,
},
},| 想开启的功能 | TS 配置位置 | 说明 |
|---|---|---|
| 启动 JSON-RPC 服务 | server.apiKey |
API 请求需带 Authorization: Bearer <key> |
| AI 摘要、排序、动态模板 | providers.ai.* |
使用 OpenAI Chat Completions 兼容接口 |
| 本地模板预览 | features.article.renderer.template |
静态模板不依赖公众号配置 |
| 提示词风格 | features.article.renderer.promptProfile |
控制排序、摘要、标题、动态排版和配图口径 |
| 微信文章 dry-run | features.article.dryRun: true |
不发布,本地输出 HTML,Cloudflare 输出 R2 artifact |
| 微信公众号正式发布 | features.article.publisher, providers.publish.weixin 或 providers.publish.weixinRelay |
本地固定 IP 可直连微信;Cloudflare 推荐 relay |
| 文章数据源 | features.article.sources |
URL 列表,可用抓取分组前缀 |
| 抓取供应商 | providers.fetch.* |
FireCrawl、Twitter/X、Xquik、Jina、RSS |
| 封面生图 | features.article.cover, providers.image.dashscope/minimax.apiKey |
支持阿里云图片生成和 MiniMax,失败时使用兜底封面 |
| 正文 AI 智能配图 | features.article.bodyImages, providers.image.dashscope/minimax.apiKey |
按文章内容生成正文配图,失败时回退已有图片 |
| 文章向量去重 | features.article.deduplication, providers.vector.embedding.*, storage.vector.* |
本地/Docker 用 SQLite,Cloudflare 用 D1 |
| 运行看板和产物 | storage.artifacts, storage.runState |
本地写文件,Cloudflare 使用 R2/KV/D1 |
| 日志观测 | observability |
镜像所有 Logger 输出到 stdout 或 HTTP ingest |
| Bark 通知 | features.article.notifications.channels, providers.notify.bark |
channels 中包含 bark 时检查 Bark URL |
| 钉钉通知 | features.article.notifications.channels, providers.notify.dingtalk |
channels 中包含 dingtalk 时检查 webhook |
| 飞书通知 | features.article.notifications.channels, providers.notify.feishu |
channels 中包含 feishu 时检查 webhook |
本地/Docker 默认配置无需手动填写:
storage: {
artifacts: {
provider: "local",
outputDir: "src/temp",
},
runState: {
provider: "local-json",
outputDir: "src/temp",
},
runtimeConfig: {
provider: "sqlite",
sqlitePath: "src/temp/trendpublish.sqlite3",
},
vector: {
provider: "sqlite",
sqlitePath: "src/temp/trendpublish.sqlite3",
},
},Cloudflare Workflow 原生模式使用 bindings:
storage: {
artifacts: {
provider: "r2",
bucketBinding: "ARTICLE_ARTIFACTS",
},
runState: {
provider: "kv-d1",
kvBinding: "ARTICLE_RUNS",
d1Binding: "ARTICLE_DB",
},
runtimeConfig: {
provider: "d1",
d1Binding: "ARTICLE_DB",
},
vector: {
provider: "d1",
d1Binding: "ARTICLE_DB",
},
},runtimeConfig 是 Dashboard 可编辑配置的存储位置。它只保存 Profile、
数据源、抓取分组、定时规则和非敏感功能参数;provider 密钥仍来自
providers.*、Docker secrets 或 Cloudflare secrets。
Dashboard 中的运行时配置分成两类:
- 能力 Profile:LLM、图片生成、通知、抓取策略、Embedding 等可复用能力。
- 功能 Profile:微信文章这类具体功能,引用能力 Profile,并允许覆盖少量参数。
例如多个微信文章 Profile 可以共用同一个“正文配图”能力 Profile,也可以分别覆盖 图片数量或尺寸。
/dashboard 会读取同一套 run state 和 artifact,因此本地和 Cloudflare
都能查看步骤、错误、耗时和 HTML 产物。
server: { apiKey: "your-api-key" },
providers: {
ai: {
baseUrl: "https://api.deepseek.com/v1",
apiKey: "your_api_key",
model: "deepseek-chat",
},
},
features: {
article: {
renderer: { template: "minimal" },
dryRun: true,
},
},运行:
deno task preview在最小配置基础上,配置抓取供应商和 URL 列表:
providers: {
fetch: {
firecrawl: { apiKey: "your_firecrawl_key" },
twitter: { xquikApiKey: "your_xquik_key" },
jina: { apiKey: "your_jina_key" },
},
},
fetchGroups: {
default: ["auto"],
web: ["firecrawl", "jina"],
social: ["twitter"],
},
features: {
article: {
dryRun: true,
renderer: {
template: "dynamic",
promptProfile: "technology",
},
sources: [
"https://news.ycombinator.com/",
"web:https://openai.com/news/",
"social:https://x.com/OpenAIDevs",
],
},
},无前缀 URL 使用 fetchGroups.default;web:、social: 是自定义抓取分组名。
分组内 provider 按顺序 fallback,成功一个就停止。auto 会按 URL 推断:
Twitter/X 域名走 Twitter,RSS/RSSHub 走 RSS,其余网页走 FireCrawl。
运行:
deno task article --dry-run在 dry-run 跑通后,再配置:
providers: {
publish: {
weixin: {
appId: "your_app_id",
appSecret: "your_app_secret",
},
},
},
features: { article: { dryRun: false } },运行:
deno task articleproviders: {
image: { dashscope: { apiKey: "your_dashscope_key" } },
},
features: {
article: {
cover: { enabled: true, provider: "dashscope", model: "qwen-image-2.0-pro" },
},
},如果使用 MiniMax:
providers: {
image: { minimax: { apiKey: "your_minimax_key" } },
},
features: {
article: {
cover: { enabled: true, provider: "minimax", model: "image-01" },
},
},封面生成失败不会中断主流程,会回退默认封面。
providers: {
image: { dashscope: { apiKey: "your_dashscope_key" } },
},
features: {
article: {
bodyImages: {
mode: "missing",
provider: "dashscope",
model: "qwen-image-2.0",
count: 1,
size: "1024*1024",
},
},
},MiniMax 正文配图写法:
providers: {
image: { minimax: { apiKey: "your_minimax_key" } },
},
features: {
article: {
bodyImages: {
mode: "missing",
provider: "minimax",
model: "image-01",
count: 1,
size: "1024*1024",
},
},
},默认只在文章没有抓取到原文 media 图片时生成正文配图。生成失败不会中断发布,
会回退到已有 media 图片布局。
providers: {
vector: {
embedding: {
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
apiKey: "your_dashscope_key",
model: "text-embedding-v3",
},
},
},
features: {
article: {
deduplication: {
enabled: true,
embeddingProvider: "dashscope",
vectorStore: "sqlite",
},
},
},
storage: {
vector: {
provider: "sqlite",
sqlitePath: "src/temp/trendpublish.sqlite3",
},
},SQLite 也需要建表,但你不需要手工执行。Local/Docker 首次使用 SQLiteVectorStore
时会自动执行内置建表 SQL。Cloudflare D1 使用
migrations/0001_article_workflow_state.sql,通过 deno task cf migrate
应用到远端,或通过 deno task cf migrate:local 应用到本地 Wrangler dev 数据库。
providers: {
notify: {
bark: { url: "https://api.day.app/your_key" },
dingtalk: { webhook: "https://oapi.dingtalk.com/robot/send?access_token=..." },
feishu: { webhookUrl: "https://open.feishu.cn/open-apis/bot/v2/hook/..." },
},
},
features: {
article: {
notifications: {
channels: ["bark"],
},
},
},通知是否启用只看 features.article.notifications.channels;providers.notify.*
只保存对应渠道的凭证。
项目里的日志仍然使用原来的 new Logger("name").info/warn/error/debug 写法。
配置 observability 后,这些日志会被额外镜像成结构化事件,可送到 stdout 或 HTTP
日志入口。原本的控制台输出不会改变。
observability: {
enabled: true,
serviceName: "trendpublish",
environment: "production",
stdout: {
enabled: false,
format: "json",
},
http: {
enabled: true,
endpoint: "https://logs.example.com/ingest",
bearerToken: "your_log_token",
headers: {},
format: "object",
timeoutMs: 5000,
},
},HTTP sink 是通用入口,适合接 Axiom、Better Stack、自建 OpenTelemetry Collector
或其他支持 HTTP ingest 的日志服务。apiKey、token、secret
等字段会做基础脱敏。
也可以直接用内置的平台便捷配置:
observability: {
enabled: true,
serviceName: "trendpublish",
environment: "production",
axiom: {
enabled: true,
dataset: "trendpublish",
token: "your_axiom_api_token",
},
betterStack: {
enabled: false,
sourceToken: "your_better_stack_source_token",
ingestingHost: "https://in.logs.betterstack.com",
},
},Axiom 需要先创建 dataset 和带 ingest 权限的 API token。Better Stack Logs
需要创建 HTTP source,使用 source token。workflow 内部日志会自动带上
runId、step、 profileId 等字段,方便按一次运行过滤。
默认情况下,全项目只使用一套模型配置,内容排序、摘要、标题生成和动态模板都会走这组配置。
常见兼容 OpenAI Chat Completions 的供应商示例:
- OpenAI:
baseUrl: "https://api.openai.com/v1",model: "gpt-4o-mini" - DeepSeek:
baseUrl: "https://api.deepseek.com/v1",model: "deepseek-chat" - Qwen:
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",model: "qwen-max"
features.article.renderer.promptProfile
控制同一条微信文章链路里的排序、摘要、标题、动态模板和 AI 配图口径。默认值是
technology,适合 AI 科技资讯。
可选值:
technology: AI 科技趋势,关注模型、产品、开源、工程和科技商业动态。general: 通用资讯,适合更宽泛的信息简报。business: 商业与产业,关注公司战略、资本、市场和产业信号。product: 产品与体验,关注产品更新、用户价值、设计和工作流。developer: 开发者与工程,关注开源、API、架构、部署和工程实践。research: 学术与研究,关注论文、方法、实验、评测和模型能力。
示例:
features: {
article: {
renderer: {
template: "dynamic",
promptProfile: "business",
},
},
},features.article.renderer.template 可选值:
default: 微信原生正式风modern: 蓝青科技资讯风tech: 工程技术专栏风mianpro: AI 日报风longform: 杂志长文风product: 更新日志风minimal: 极简阅读风darktech: 深色研究笔记风dynamic: AI 根据本次文章内容实时生成公众号内联 HTML,失败自动回退minimalrandom: 每次随机选择一个模板
服务启动后使用 heartbeat 调度:本地/Docker 和 Cloudflare 都会定期检查
runtimeConfig 中保存的 schedule,命中后才创建微信文章 workflow 运行实例。
默认初始化出的 schedule 是每天凌晨 3 点(Asia/Shanghai),后续可以在 Dashboard
修改,不需要重新部署。项目不再按星期切换其他工作流。
- 每次改完
trendpublish.config.ts后先跑deno task doctor。 - 先跑
deno task preview,再跑deno task article --dry-run,最后再正式发布。 - 新环境建议先关闭
features.article.deduplication.enabled和通知,跑通主链路后再逐项开启。 - 本地真实的
trendpublish.config.ts已加入.gitignore,不要提交真实密钥。