项目实现架构
s3下载器
flowchart TD
controller["NAS 下载主控循环"]
schedule["根据云服务器执行端空闲槽位请求切片"]
taskStore[("SQLite 任务切片库<br/>任务 ID、字节范围、状态、租约、下载路径、服务器 ID、S3 官方校验值")]
claim["SQLite 写事务原子领取切片<br/>查询 状态=等待 或已过期切片<br/>写入任务 ID、服务器 ID、最后检测时间"]
launch["NAS 启动该切片的独立 SSH 会话"]
cloud["云服务器执行端"]
cloudScript["启动云端下载脚本"]
inspect{"检查云端临时切片文件"}
complete["已有完整切片"]
partial["存在部分下载文件"]
fresh["没有可用下载进度"]
resume["读取已有进度<br/>计算下一个 Range 起点"]
requestRange["发起 S3 Range 下载"]
s3["S3 对象存储"]
response["返回指定字节范围"]
remoteFiles["云端临时切片文件"]
remoteCheck{"切片是否达到预期大小"}
remoteComplete["云端切片完整"]
rsync["NAS 使用 rsync 把云端切片拉回 NAS"]
nasTemp["NAS 临时文件<br/>xxx.download"]
verify["检查文件大小是否等于准确字节数"]
publish["临时文件原子改名<br/>xxx.download → xxx"]
verified["更新 SQLite<br/>状态为本地已校验"]
assembly["所有切片完成后合成文件"]
expectedChecksum["读取任务保存的 S3 官方 ChecksumSHA256"]
compareChecksum{"完整文件 SHA-256 校验是否一致"}
completeTask["更新 SQLite<br/>任务状态为已完成"]
hashFail["删除不可信合成文件<br/>相关切片重新标记为等待状态"]
fail["SSH、S3、下载或 rsync 失败"]
requeue["保留可复用进度<br/>切片标记为等待状态"]
renewScan["主控循环每约 15 秒扫描活动切片"]
renewDB["校验任务 ID、字节范围、服务器 ID、租约标识<br/>更新最后检测时间 = 当前时间 + 60 秒"]
expireScan["检查是否存在过期租约"]
expire["超过 60 秒未续租<br/>状态更改:下载/回传 → 等待"]
keepalive["SSH 保活<br/>每 10 秒探测<br/>连续 3 次无响应则断开"]
stalled["约 45 秒没有下载进度"]
stop["终止卡住的下载进程"]
lifecycleTimer["Agent 工作流定时器<br/>当前配置:每 3 小时触发"]
lifecycleLock[获取订购生命周期全局锁先恢复未完成的退订/重购事务]
pendingTx{"是否存在未完成的生命周期事务"}
resumeTx["按事务当前阶段继续执行<br/>禁止重复退款或重复下单"]
txStep["执行当前阶段<br/>退款确认、退款核验、购买、初始化、测速或接入"]
healthCheck[巡检云服务器节点读取实时工作状态、下载指标和固定 S3 测速]
retireDecision{"是否满足连续两轮不合格条件"}
keepActive["节点继续保留<br/>维持下载服务"]
drain["节点标记为 draining<br/>停止分配新切片并等待活动切片归零"]
retire["Agent 工作流通过网页工单手动提交退订/退款"]
refundCheck{目标实例是否已从服务器列表消失}
purchase["按实时余额与实例配额重购替代节点"]
init["发现新实例并完成初始化<br/>SSH、curl、rsync、procps、配置"]
benchmark["执行公网双源、三段 S3 和 NAS 回传测速"]
benchmarkDecision{"新节点验收是否合格"}
enable[原子写入云服务器配置重启控制器并恢复下载]
replacementFail["新节点验收不合格<br/>进入下一轮退订重购事务"]
lifecycleWait["记录当前阻塞阶段<br/>下一次定时器继续恢复同一事务"]
lifecycleDone["记录订单、实例、测速和状态变化<br/>释放生命周期全局锁"]
controller --> schedule
schedule --> claim
claim -.-> taskStore
claim --> launch
launch --> cloud
cloud --> cloudScript
cloudScript --> inspect
inspect -->|已有完整文件| complete
inspect -->|存在部分文件| partial
inspect -->|没有文件| fresh
partial --> resume
fresh --> requestRange
resume --> requestRange
requestRange --> s3
s3 --> response
response --> remoteFiles
remoteFiles --> remoteCheck
remoteCheck -->|未完成| resume
remoteCheck -->|已完成| remoteComplete
complete --> remoteComplete
remoteComplete --> rsync
rsync --> nasTemp
nasTemp --> verify
verify -->|大小一致| publish
publish --> verified
verified --> assembly
verify -->|大小不一致| requeue
assembly --> compareChecksum
taskStore -.-> expectedChecksum
expectedChecksum --> compareChecksum
compareChecksum -->|一致| completeTask
compareChecksum -->|不一致| hashFail
hashFail --> claim
cloudScript -->|命令失败| fail
rsync -->|传输失败| fail
keepalive -->|连接断开| fail
fail --> requeue
requeue --> claim
controller -.-> renewScan
renewScan --> renewDB
renewDB -.-> controller
controller -.-> expireScan
expireScan -->|发现过期租约| expire
expire --> claim
launch -.-> keepalive
launch -.-> stalled
stalled --> stop
stop --> requeue
controller -.-> lifecycleTimer
lifecycleTimer --> lifecycleLock
lifecycleLock --> pendingTx
pendingTx -->|有| resumeTx
resumeTx --> txStep
txStep --> lifecycleDone
pendingTx -->|无| healthCheck
healthCheck --> retireDecision
retireDecision -->|否| keepActive
keepActive --> lifecycleDone
retireDecision -->|是| drain
drain -.-> schedule
drain --> retire
retire --> refundCheck
refundCheck -->|未生效| lifecycleWait
refundCheck -->|已从服务器列表消失| purchase
purchase --> init
init --> benchmark
benchmark --> benchmarkDecision
benchmarkDecision -->|合格| enable
enable --> lifecycleDone
benchmarkDecision -->|不合格| replacementFail
replacementFail --> lifecycleWait
lifecycleWait --> lifecycleDone
api聚合系统网关链路
flowchart TD
client["客户端<br/>控制台页面或模型 API 调用"]
subgraph console["用户面板"]
web["前端页面"]
nginx{"Nginx 按请求路径分流"}
web --> nginx
end
client --> web
subgraph business["前置网关服务"]
bizEntry[前置网关 API 入口]
bizRoute{"接口类型"}
loginProxy["注册、登录接口<br/>原样代理给 New API"]
bizAuth["读取 Authorization"]
tokenLookup{"在共享 MySQL 中<br/>查询 users.access_token"}
jwtVerify["调用 New API /api/user/self<br/>验证 Dashboard JWT"]
bizExecute["执行业务接口<br/>套餐、订阅、订单、支付、发票、代理商、价格等"]
bizEntry --> bizRoute
bizRoute -->|注册或登录| loginProxy
bizRoute -->|受保护的业务接口| bizAuth
bizAuth --> tokenLookup
tokenLookup -->|找到 Access Token| bizExecute
tokenLookup -->|未找到,可能是 Dashboard JWT| jwtVerify
jwtVerify -->|验证成功| bizExecute
jwtVerify -->|验证失败| bizUnauthorized["返回未登录或无权限"]
bizExecute --> bizResponse["返回业务结果"]
end
subgraph newapi["New API"]
newEntry["New API 请求入口"]
newRoute{"请求类型"}
dashboardAuth["控制台身份认证<br/>登录、注册、JWT、刷新会话"]
apiTokenAuth["校验用户 API Token"]
userPolicy["读取用户、分组、模型权限和额度"]
modelRateLimit{"检查用户或模型请求频率"}
rateReject["超过限制<br/>返回 429"]
channelSelect["根据模型、分组、优先级和渠道状态<br/>选择可用渠道"]
userQuota["请求前额度检查或预扣"]
channelType{"选中的渠道"}
buildMaxxRequest["按渠道类型构造上游请求<br/>携带 网关 内部渠道凭证"]
newResponse["接收上游响应"]
usageParse["解析输入、输出 Token 用量"]
userBilling["按用户模型倍率结算<br/>补扣或退还预扣额度"]
apiResponse["向客户端返回模型响应"]
newEntry --> newRoute
newRoute -->|控制台登录、注册、自身信息| dashboardAuth
newRoute -->|模型调用 /v1/*| apiTokenAuth
apiTokenAuth -->|无效| newUnauthorized["返回 401"]
apiTokenAuth -->|有效| userPolicy
userPolicy -->|无权限或额度不足| newForbidden["拒绝请求"]
userPolicy -->|通过| modelRateLimit
modelRateLimit -->|超过限制| rateReject
modelRateLimit -->|通过| channelSelect
channelSelect --> userQuota
userQuota -->|额度不足| newForbidden
userQuota -->|通过| channelType
channelType --> buildMaxxRequest
newResponse --> usageParse
usageParse --> userBilling
userBilling --> apiResponse
end
nginx -->|前置网关<br/>支付、订阅、发票、价格等| bizEntry
nginx -->|/v1/* 和其他 New API 路径| newEntry
loginProxy --> dashboardAuth
jwtVerify -.-> dashboardAuth
dashboardAuth --> dashboardResponse["签发或验证 Dashboard JWT"]
dashboardResponse --> bizResponse
subgraph maxx["多上游代理网关"]
maxxEntry[请求入口]
internalToken[校验 New API 使用的网关内部 API Token]
tokenConcurrency{"检查该 Token 的<br/>单实例并发上限"}
tokenReject["超过 Token 并发限制<br/>返回 429"]
parseRequest["识别客户端协议和模型<br/>提取 Session ID"]
sessionSources["Session ID 来源<br/>Session_id 请求头<br/>previous_response_id<br/>prompt_cache_key<br/>metadata.session_id / user_id<br/>X-Session-Id<br/>均不存在时生成稳定摘要"]
routeLoad["加载启用的 Route、Provider<br/>路由策略、模型映射和重试配置"]
routeFilter["生成并筛选候选 Provider"]
concurrencyPrefilter{"Provider 是否已达到<br/>配置的并发上限"}
cooldownCheck{"逐级检查冷却状态<br/>Provider<br/>Provider + 协议<br/>Provider + Model<br/>Provider + 协议 + Model"}
modelSupport{"Provider 是否支持<br/>当前模型或协议转换"}
candidateList["得到有序候选 Provider 列表"]
stickyEnabled{"是否启用会话亲和"}
stickyGet["从 Redis 查询会话绑定<br/>协议 + 分组+ API Token / Session ID"]
stickyUsable{"原绑定 Provider<br/>是否仍在候选列表"}
promote["将原绑定 Provider<br/>提升为第一候选"]
normalOrder["使用优先级或<br/>加权随机顺序"]
choose["选择当前候选 Provider"]
mapModel["把客户端模型名<br/>映射为上游模型名"]
convertCheck{"Provider 是否原生支持<br/>客户端请求协议"}
requestConvert["转换请求体、URI 和字段<br/>客户端协议 → Provider 协议"]
keepOriginal["保留原请求协议"]
acquireSlot{"原子占用 Provider 并发槽位"}
nextOnBusy["当前 Provider 已满<br/>直接尝试下一候选<br/>不写入错误冷却"]
adapterExecute["调用对应 Provider Adapter<br/>向具体上游发送请求"]
upstreamResult{"上游执行结果"}
releaseSlot["释放 Provider 并发槽位"]
responseConvert["必要时转换响应<br/>Provider 协议 → 客户端协议"]
maxxCost["记录上游 Token、耗时和成本"]
exactSuccess["记录成功<br/>清除精确的 Provider + 协议 + 模型冷却<br/>重置这一层失败次数"]
stickySet["将成功 Provider 写回 Redis<br/>失败不影响本次响应"]
maxxResponse["响应返回 New API"]
maxxEntry --> internalToken
internalToken -->|无效| maxxUnauthorized["返回 401"]
internalToken -->|有效| tokenConcurrency
tokenConcurrency -->|已满| tokenReject
tokenConcurrency -->|可用| parseRequest
sessionSources -.-> parseRequest
parseRequest --> routeLoad
routeLoad --> routeFilter
routeFilter --> concurrencyPrefilter
concurrencyPrefilter -->|已满| routeFilter
concurrencyPrefilter -->|未满| cooldownCheck
cooldownCheck -->|仍在冷却| routeFilter
cooldownCheck -->|没有冷却或已经到期| modelSupport
modelSupport -->|不支持| routeFilter
modelSupport -->|支持或可以转换| candidateList
candidateList --> stickyEnabled
stickyEnabled -->|是| stickyGet
stickyGet --> stickyUsable
stickyUsable -->|可用| promote
stickyUsable -->|已冷却、已满或已下线| normalOrder
stickyEnabled -->|否| normalOrder
promote --> choose
normalOrder --> choose
choose --> mapModel
mapModel --> convertCheck
convertCheck -->|不原生支持| requestConvert
convertCheck -->|原生支持| keepOriginal
requestConvert --> acquireSlot
keepOriginal --> acquireSlot
acquireSlot -->|占用失败| nextOnBusy
nextOnBusy --> choose
acquireSlot -->|占用成功| adapterExecute
adapterExecute --> upstreamResult
upstreamResult --> releaseSlot
releaseSlot -->|成功| responseConvert
responseConvert --> maxxCost
maxxCost --> exactSuccess
exactSuccess --> stickySet
stickySet --> maxxResponse
end
buildMaxxRequest --> maxxEntry
maxxResponse --> newResponse
subgraph cooldownFlow["多上游代理网关 错误分类、冷却、重试与降级"]
classify["Provider Adapter 解析<br/>HTTP 状态码、响应体和网络错误"]
committed{"响应是否已经<br/>写给客户端"}
committedStop["记录失败并结束<br/>避免切换上游后重复响应"]
scope{判断错误影响范围}
requestScope["当前请求级<br/>参数错误、请求过大、内容过滤等"]
modelScope["模型级<br/>某个模型不存在或暂时不可用<br/>冷却键:Provider + 协议 + Model"]
keyScope["密钥级<br/>额度耗尽、Key 限流、认证失败<br/>冷却键:Provider + 协议"]
endpointScope["端点级<br/>某种协议对应的上游端点异常<br/>冷却键:Provider + 协议"]
providerScope["Provider 级<br/>DNS、连接超时、整个上游不可达<br/>冷却键:Provider"]
noCooldown["不写入冷却"]
duration{"确定冷却结束时间"}
explicitTime["优先使用上游返回的<br/>明确恢复时间 CooldownUntil"]
retryAfter["其次使用<br/>当前时间 + Retry-After"]
localPolicy["没有上游时间时<br/>按错误原因和连续失败次数计算"]
policies["默认冷却策略<br/>5xx:5、10、15 秒递增,最长 10 分钟<br/>网络错误:5、10、20、40 秒递增,最长 30 分钟<br/>额度耗尽:1 小时<br/>限流或上游并发限制:5 秒<br/>认证失败:1 小时<br/>模型不可用:5 分钟<br/>未知错误:线性递增,最长 5 分钟"]
saveCooldown["写入冷却状态<br/>相同键保留更晚的结束时间"]
currentCandidates["当前请求继续使用请求开始时<br/>已经生成的候选 Provider 列表<br/>不会重新执行冷却筛选"]
requestRetryable{"当前请求级错误<br/>是否允许重试"}
requestFailed["直接返回当前请求错误<br/>不冷却,也不切换 Provider"]
retryable{"当前 Provider 的错误<br/>是否允许重试"}
attemptsLeft{"当前 Provider 是否还有<br/>同渠道重试次数"}
waitSource{"本次重试等待时间来源"}
retryAfterWait["上游返回 Retry-After<br/>按该时长等待<br/>不受 RetryConfig 最大间隔限制"]
retryConfigWait["上游未返回 Retry-After<br/>按 RetryConfig 计算退避时间<br/>不超过配置的最大间隔"]
waitState{"等待期间请求是否<br/>仍然有效"}
requestStopped["客户端断开或请求超时<br/>停止等待并结束当前请求"]
retrySame["等待结束<br/>再次请求同一个 Provider"]
nextProvider{"是否还有下一个<br/>候选 Provider"}
failover["切换到下一候选 Provider<br/>该 Provider 使用自己的重试配置"]
allFailed["所有候选均失败<br/>返回最终错误"]
classify --> committed
committed -->|已经写出| committedStop
committed -->|尚未写出| scope
scope -->|Request| requestScope
scope -->|Model| modelScope
scope -->|Key| keyScope
scope -->|Endpoint| endpointScope
scope -->|Provider| providerScope
requestScope --> noCooldown
modelScope --> duration
keyScope --> duration
endpointScope --> duration
providerScope --> duration
duration -->|上游返回存在冷却时间| explicitTime
duration -->|否则存在 至少冷却时间| retryAfter
duration -->|都不存在| localPolicy
localPolicy -.-> policies
explicitTime --> saveCooldown
retryAfter --> saveCooldown
localPolicy --> saveCooldown
noCooldown --> requestRetryable
requestRetryable -->|否| requestFailed
requestRetryable -->|是| currentCandidates
saveCooldown --> currentCandidates
saveCooldown -.->|只影响后续新请求| cooldownCheck
currentCandidates --> retryable
retryable -->|是| attemptsLeft
retryable -->|否,不再重试当前 Provider| nextProvider
attemptsLeft -->|有| waitSource
waitSource -->|存在 Retry-After| retryAfterWait
waitSource -->|不存在 Retry-After| retryConfigWait
retryAfterWait --> waitState
retryConfigWait --> waitState
waitState -->|仍然有效| retrySame
waitState -->|已取消或超时| requestStopped
attemptsLeft -->|没有,当前 Provider 重试结束| nextProvider
nextProvider -->|有| failover
nextProvider -->|没有| allFailed
end
releaseSlot -->|失败| classify
retrySame --> adapterExecute
failover --> choose
committedStop --> maxxResponse
requestFailed --> maxxResponse
requestStopped --> maxxResponse
allFailed --> maxxResponse
subgraph data["数据与分布式状态"]
sharedMySQL[(共享 MySQLNew API 用户、Token、渠道、套餐、订单、订阅、支付、发票等)]
maxxMySQL[("Maxx MySQL<br/>Provider、Route、模型映射、重试配置<br/>请求记录、成本、冷却持久化")]
redis[("Redis<br/>会话亲和绑定<br/>冷却状态和冷却版本号<br/>发布冷却变更通知")]
localCache["各网关实例的本地内存<br/>用于快速查询冷却状态"]
redisWrite[写入 Redis 冷却值]
localWrite["更新当前网关实例的<br/>本地冷却状态"]
mysqlWrite["将冷却状态持久化到 MySQL"]
versionBump["在 Redis 中原子递增<br/>该 Provider 的冷却版本号"]
publishEvent["发布冷却变更通知<br/>Provider ID + 最新版本号"]
instanceSync["其他网关实例<br/>接收变更通知或主动检查版本号"]
versionCompare{"本地已知版本号<br/>是否落后于 Redis"}
reloadCooldown["从 Redis 重新加载<br/>该 Provider 的全部冷却状态"]
ignoreEvent["版本未变化<br/>忽略重复或过期通知"]
saveCooldown --> redisWrite
redisWrite --> redis
redisWrite --> localWrite
localWrite --> localCache
localWrite --> mysqlWrite
mysqlWrite --> maxxMySQL
mysqlWrite --> versionBump
versionBump --> redis
versionBump --> publishEvent
publishEvent --> instanceSync
redis -.->|主动检查最新版本号| instanceSync
instanceSync --> versionCompare
versionCompare -->|是| reloadCooldown
versionCompare -->|否| ignoreEvent
redis --> reloadCooldown
reloadCooldown --> localCache
localCache -.-> cooldownCheck
stickyGet <--> redis
stickySet --> redis
routeLoad <--> maxxMySQL
maxxCost --> maxxMySQL
dashboardAuth <--> sharedMySQL
apiTokenAuth <--> sharedMySQL
userPolicy <--> sharedMySQL
channelSelect <--> sharedMySQL
userBilling --> sharedMySQL
tokenLookup <--> sharedMySQL
bizExecute <--> sharedMySQL
end
subgraph upstreams["具体模型上游"]
upstreamA["Provider A"]
upstreamB["Provider B"]
upstreamC["Provider C"]
end
adapterExecute --> upstreamA
adapterExecute --> upstreamB
adapterExecute --> upstreamC
eks 交付系统
flowchart TD
developer["开发者"]
%% =========================
%% 外部镜像来源
%% =========================
subgraph imageSource["外部镜像来源"]
release["业务系统发布流程<br/>构建并发布容器镜像"]
imageRegistry[("镜像仓库<br/>保存容器镜像")]
writeImageTag["自动更新 IaC 仓库中的镜像版本"]
release --> imageRegistry
release --> writeImageTag
end
%% =========================
%% IaC 仓库
%% =========================
subgraph iacRepo["基础设施与部署配置仓库(IaC 仓库)"]
appConfig["修改应用或平台配置<br/>apps/** 或 platform/**"]
appPR["创建 IaC 仓库合并请求"]
manifestCheck["Kubernetes 资源清单预检查<br/>生成 Kustomize 配置<br/>校验资源格式和字段<br/>检查开发与生产环境的密钥隔离"]
appReview["人工评审应用或平台配置"]
appMerge["合并应用或平台配置"]
infraConfig["修改云基础设施配置<br/>infra/**"]
infraPR["创建 IaC 仓库合并请求"]
tofuPlan["OpenTofu 变更预演<br/>展示将新增、修改或删除的云资源"]
infraReview["人工评审基础设施变更"]
infraMerge["合并基础设施配置"]
mainBranch[("IaC 仓库 main 分支<br/>保存应用、平台和云基础设施配置")]
appConfig -->|创建合并请求| appPR
appPR --> manifestCheck
manifestCheck --> appReview
appReview --> appMerge
appMerge --> mainBranch
appConfig -->|直接提交<br/>跳过合并请求预检查| mainBranch
infraConfig -->|创建合并请求| infraPR
infraPR --> tofuPlan
tofuPlan --> infraReview
infraReview --> infraMerge
infraMerge --> mainBranch
infraConfig -->|直接提交<br/>跳过变更预演和人工评审| mainBranch
end
developer --> appConfig
developer --> infraConfig
writeImageTag -->|写入新镜像版本| mainBranch
%% =========================
%% Argo CD 同步
%% =========================
subgraph gitops["Argo CD 应用部署链路"]
argoPoll["Argo CD 每约 180 秒<br/>读取 IaC 仓库 main 分支"]
renderManifest["读取 apps/** 和 platform/**<br/>生成 Kubernetes 资源清单"]
compareState{"仓库配置与集群当前状态<br/>是否存在差异?"}
unchanged["没有差异<br/>保持当前运行状态"]
sync["发现差异后按仓库配置同步<br/>创建或更新发生变化的资源<br/>删除仓库中已移除的受管资源<br/>同步后集群状态与仓库配置保持一致"]
rollingUpdate["多副本服务滚动更新<br/>先创建新 Pod<br/>新 Pod 通过就绪检查后接收流量<br/>再逐个停止旧 Pod"]
recreateUpdate["单副本服务重新创建<br/>先停止旧 Pod,再创建新 Pod<br/>更新期间可能短暂中断"]
configResources["其他 Kubernetes 资源<br/>Service、Ingress、Secret<br/>ExternalSecret、PodDisruptionBudget"]
pod["运行中的 Pod"]
clusterChange["集群资源被手动修改<br/>或运行状态发生意外变化"]
mainBranch -->|读取应用和平台配置| argoPoll
argoPoll --> renderManifest
renderManifest --> compareState
compareState -->|没有差异| unchanged
compareState -->|存在差异| sync
sync -->|多副本服务| rollingUpdate
sync -->|单副本服务| recreateUpdate
sync --> configResources
rollingUpdate --> pod
recreateUpdate --> pod
clusterChange -->|下次检查发现差异| compareState
end
imageRegistry -->|创建 Pod 时拉取镜像| pod
%% =========================
%% 用户请求
%% =========================
subgraph requestFlow["用户请求链路"]
user["用户请求"]
ingress["Ingress<br/>根据域名和路径转发请求"]
service["Service<br/>提供稳定访问地址<br/>并把请求分配给可用 Pod"]
user --> ingress
ingress --> service
service -->|只转发给通过就绪检查的 Pod| pod
end
configResources -.-> ingress
configResources -.-> service
%% =========================
%% Pod 状态检查
%% =========================
subgraph healthCheck["Pod 状态检查"]
startup["启动检查<br/>判断应用是否完成初始化"]
readiness["就绪检查<br/>失败时停止接收新请求"]
liveness["存活检查<br/>持续失败时重新启动容器"]
pod --> startup
pod --> readiness
pod --> liveness
liveness -->|持续失败| pod
end
%% =========================
%% OpenTofu
%% =========================
subgraph infrastructure["OpenTofu 云基础设施链路"]
applyWorkflow["GitHub Actions<br/>检测到 main 分支中的 infra/** 发生变化"]
acquireLock["获取状态锁<br/>防止多个 OpenTofu 任务同时执行"]
lockTable[("AWS DynamoDB 状态锁表<br/>code0-iac-tofu-locks<br/>us-west-2")]
readState["读取 OpenTofu 状态文件"]
stateBucket[("AWS S3 状态文件存储<br/>记录 OpenTofu 已管理的云资源<br/>启用版本控制和 KMS 加密")]
tofuApply["OpenTofu 执行基础设施变更"]
cloudAPI["调用 AWS 接口"]
cloudResources["AWS 云资源<br/>网络、Kubernetes 集群<br/>数据库、权限及其他基础设施"]
saveState["变更完成后<br/>把最新状态写回 S3"]
releaseLock["释放 DynamoDB 状态锁"]
mainBranch -->|infra/** 发生变化时触发| applyWorkflow
applyWorkflow --> acquireLock
acquireLock -.-> lockTable
acquireLock --> readState
stateBucket -.-> readState
readState --> tofuApply
tofuApply --> cloudAPI
cloudAPI --> cloudResources
cloudResources -->|变更完成| saveState
saveState --> stateBucket
saveState --> releaseLock
releaseLock -.-> lockTable
end
cloudResources -.->|提供运行环境| pod
%% =========================
%% 监控与日志
%% =========================
subgraph observability["监控与日志"]
prometheus["Prometheus<br/>采集运行指标"]
logCollector["日志采集组件<br/>收集容器日志"]
loki["Loki<br/>保存和查询日志"]
grafana["Grafana<br/>查看指标、日志和告警"]
pod --> prometheus
pod --> logCollector
logCollector --> loki
prometheus --> grafana
loki --> grafana
end
支付系统
flowchart TD
client["客户端<br/>选择支付方式并发起支付"]
subgraph create["一、创建预约订单和支付订单"]
preview["价格预览<br/>计算课程价格、优惠和应付金额"]
booking["创建预约订单<br/>锁定教师时间 15 分钟"]
payment["创建支付订单<br/>锁定价格并预留优惠券"]
channel{"选择支付渠道"}
client --> preview
preview --> booking
booking --> payment
payment --> channel
end
subgraph attempt["二、创建或复用支付尝试"]
sameChannel["继续使用原支付渠道"]
reusable{"原支付尝试和支付链接<br/>是否仍然有效"}
reuse["复用原支付尝试和支付链接"]
otherChannel["切换到其他支付渠道"]
newAttempt["创建新的支付尝试"]
providerOrder["创建新的 PayPal 订单"]
checkout["返回支付链接"]
channel -->|同一支付渠道| sameChannel
sameChannel --> reusable
reusable -->|有效| reuse
reusable -->|无效| newAttempt
channel -->|不同支付渠道| otherChannel
otherChannel --> newAttempt
newAttempt --> providerOrder
providerOrder --> checkout
reuse --> checkout
end
subgraph authorization["三、用户授权和客户端查询"]
authorize["用户打开支付链接<br/>完成 PayPal、Apple Pay 或 Google Pay 授权"]
clientReturn["客户端返回支付页面<br/>通知平台开始查询"]
queryOrder["平台主动查询 PayPal 订单状态"]
orderState{"PayPal 订单状态"}
approved["订单已批准"]
capture["平台发起正式扣款"]
queryCapture["查询 PayPal 订单详情<br/>读取最新真实扣款记录"]
captureState{"真实扣款状态"}
checkout --> authorize
authorize --> clientReturn
clientReturn --> queryOrder
queryOrder --> orderState
orderState -->|已批准| approved
approved --> capture
capture --> queryCapture
orderState -->|已完成| queryCapture
orderState -->|处理中| processing
orderState -->|失败| failed
queryCapture --> captureState
end
captureState -->|扣款已完成| settle
captureState -->|扣款处理中| processing
captureState -->|暂无扣款记录| noCapture
captureState -->|扣款被拒绝| declined
captureState -->|扣款失败| failed
noCapture["暂无真实扣款记录<br/>暂不判定支付成功"]
declined["扣款被支付渠道拒绝"]
noCapture --> processing
declined --> failed
subgraph webhook["四、PayPal 异步通知处理"]
webhookReceive["接收 PayPal 异步通知"]
readHeaders["读取签名请求头<br/>签名算法、证书地址、传输编号、签名、传输时间"]
verifySignature["携带签名信息、Webhook ID<br/>和完整事件内容进行签名验证"]
signatureState{"签名验证结果"}
rejectWebhook["拒绝通知<br/>不修改支付状态"]
eventCheck["检查事件编号和事件类型"]
eventDedup{"事件编号是否已经处理"}
duplicate["重复通知<br/>直接返回成功"]
saveEvent["保存支付事件记录<br/>事件编号唯一去重"]
eventType{"PayPal 通知事件类型"}
webhookApproved["订单已批准<br/>支付订单和支付尝试标记为处理中"]
webhookCompleted["扣款已完成"]
webhookDeclined["扣款被拒绝"]
webhookFailed["扣款失败"]
otherEvent["其他事件<br/>仅保存事件记录"]
webhookReceive --> readHeaders
readHeaders --> verifySignature
verifySignature --> signatureState
signatureState -->|验证失败| rejectWebhook
signatureState -->|验证成功| eventCheck
eventCheck --> eventDedup
eventDedup -->|已处理| duplicate
eventDedup -->|未处理| saveEvent
saveEvent --> eventType
eventType -->|订单已批准| webhookApproved
eventType -->|扣款已完成| webhookCompleted
eventType -->|扣款被拒绝| webhookDeclined
eventType -->|扣款失败| webhookFailed
eventType -->|其他事件| otherEvent
webhookApproved --> processing
webhookCompleted --> settle
webhookDeclined --> failed
webhookFailed --> failed
end
subgraph processingFlow["五、支付处理中"]
processing["保持支付处理中<br/>不提前标记为支付成功"]
waitForNext["等待客户端再次查询<br/>或等待 PayPal 异步通知"]
processing --> waitForNext
waitForNext -->|客户端再次查询| queryOrder
waitForNext -->|收到异步通知| webhookReceive
end
subgraph settlement["六、统一幂等结算"]
settle["进入统一结算入口"]
lockPayment["数据库事务加锁<br/>锁定支付订单和支付尝试"]
settledCheck{"支付订单是否已经结算"}
alreadySettled["已经结算<br/>直接返回已有结果"]
markSuccess["支付订单标记为已支付<br/>支付尝试标记为成功"]
confirmBooking["确认预约订单<br/>正式占用教师时间"]
consumeCoupon["核销预留的优惠券"]
commitSettlement["提交结算事务"]
notify["发送学生和导师付款确认通知"]
settle --> lockPayment
lockPayment --> settledCheck
settledCheck -->|是| alreadySettled
settledCheck -->|否| markSuccess
markSuccess --> confirmBooking
confirmBooking --> consumeCoupon
consumeCoupon --> commitSettlement
commitSettlement --> notify
end
subgraph failure["七、支付失败"]
failed["确认支付失败"]
markFailed["当前支付尝试标记为失败"]
retryChoice{"用户是否重新支付"}
retrySame["继续使用同一支付渠道"]
retryOther["选择其他支付渠道"]
waitExpired["暂不支付<br/>等待预约订单到期"]
failureResult["向客户端返回支付失败"]
failed --> markFailed
markFailed --> failureResult
markFailed --> retryChoice
retryChoice -->|同一渠道| retrySame
retryChoice -->|其他渠道| retryOther
retryChoice -->|暂不支付| waitExpired
retrySame --> newAttempt
retryOther --> newAttempt
end
subgraph timeout["八、15 分钟到期扫描和最终核验"]
timeoutJob["后台定时任务<br/>扫描未完成的预约订单"]
expiredCheck{"预约锁定时间是否已到期"}
nextScan["未到期<br/>等待下一轮扫描"]
lockExpired["开启数据库事务<br/>锁定预约订单和支付订单"]
reread["重新读取最新支付状态<br/>防止与客户端查询或 Webhook 并发冲突"]
currentState{"重新读取后的支付状态"}
paidAlready["已经支付成功"]
keepPaid["保持预约有效<br/>不释放教师时间和优惠券"]
noAttempt["没有有效的支付尝试"]
clearlyFailed["支付已经明确失败"]
processingOrUnknown["支付处理中、状态未知<br/>或存在未确认的支付尝试"]
startFinalVerify["进入额外 1 分钟主动核验期<br/>记录核验截止时间"]
queryLatest["主动查询 PayPal 订单<br/>读取最新真实扣款记录"]
verifyResult{"最终核验结果"}
verifiedSuccess["真实扣款已完成"]
verifiedProcessing["扣款仍在处理中"]
verifiedNoCapture["暂无真实扣款记录"]
verifiedDeclined["扣款被拒绝"]
verifiedFailed["扣款失败"]
verifiedUnpaid["确认尚未支付"]
queryUnavailable["PayPal 暂时无法查询<br/>网络超时或服务异常"]
windowCheck{"是否仍在 1 分钟核验期内"}
waitAndRetry["短暂等待后再次主动查询"]
abnormal["标记为支付核验异常<br/>不能直接判断为未支付"]
preserve["保留预约订单、教师时间和优惠券"]
adminAlert["向管理员发送异常告警邮件"]
backgroundRetry["后台继续自动重查"]
manualReview["管理员后台人工核对"]
timeoutJob --> expiredCheck
expiredCheck -->|未到期| nextScan
expiredCheck -->|已到期| lockExpired
lockExpired --> reread
reread --> currentState
currentState -->|已经支付| paidAlready
paidAlready --> keepPaid
currentState -->|没有有效支付尝试| noAttempt
currentState -->|明确支付失败| clearlyFailed
noAttempt --> releaseTx
clearlyFailed --> releaseTx
currentState -->|处理中或状态未知| processingOrUnknown
processingOrUnknown --> startFinalVerify
startFinalVerify --> queryLatest
queryLatest --> verifyResult
verifyResult -->|扣款已完成| verifiedSuccess
verifiedSuccess --> settle
verifyResult -->|扣款处理中| verifiedProcessing
verifyResult -->|暂无扣款记录| verifiedNoCapture
verifyResult -->|PayPal 暂时无法查询| queryUnavailable
verifiedProcessing --> windowCheck
verifiedNoCapture --> windowCheck
queryUnavailable --> windowCheck
windowCheck -->|仍在核验期内| waitAndRetry
waitAndRetry --> queryLatest
windowCheck -->|核验期已结束| abnormal
verifyResult -->|扣款被拒绝| verifiedDeclined
verifyResult -->|扣款失败| verifiedFailed
verifyResult -->|确认尚未支付| verifiedUnpaid
verifiedDeclined --> releaseTx
verifiedFailed --> releaseTx
verifiedUnpaid --> releaseTx
abnormal --> preserve
abnormal --> adminAlert
abnormal --> backgroundRetry
abnormal --> manualReview
backgroundRetry --> queryLatest
manualReview --> queryLatest
end
payment -.-> timeoutJob
subgraph release["九、取消预约和释放资源"]
releaseTx["开启释放事务<br/>再次锁定并复核订单状态"]
releaseCheck{"复核时是否已经支付成功"}
stopRelease["停止释放<br/>转入统一结算"]
cancelPayment["支付订单标记为已过期"]
closeAttempts["未完成的支付尝试标记为已结束"]
cancelBooking["取消预约订单<br/>释放教师时间"]
releaseCoupon["释放预留的优惠券"]
commitRelease["提交释放事务"]
expiredResult["客户端再次查询时<br/>返回预约和支付已过期"]
releaseTx --> releaseCheck
releaseCheck -->|已经支付| stopRelease
stopRelease --> settle
releaseCheck -->|仍未支付| cancelPayment
cancelPayment --> closeAttempts
closeAttempts --> cancelBooking
cancelBooking --> releaseCoupon
releaseCoupon --> commitRelease
commitRelease --> expiredResult
end
payment -.-> timeoutJob
seo/geo 内容矩阵
flowchart TD
operator["运营人员"]
developer["开发者"]
visitor["网站访客"]
%% =====================================================
%% AI 内容矩阵
%% =====================================================
subgraph aiMatrix["AI 内容矩阵生产"]
keyword["运营导入选题词库"]
siteProfile["读取各站点内容配置<br/>品牌定位、目标受众、语气、关键词<br/>文章提示词、图片提示词、SEO/GEO 规则"]
contentTask["创建内容生产任务<br/>同一选题生成多个站点版本"]
articleGen["按站点生成文章<br/>标题、正文、分类、Slug、摘要"]
coverGen["按站点生成封面图<br/>结合站点风格和文章主题生成"]
seoGeoGen["生成 SEO / GEO 信息<br/>Meta 描述、结构化内容、内链建议、图片 Alt"]
review["CMS 统一审核和修改<br/>确认后发布到指定站点"]
keyword --> contentTask
siteProfile --> contentTask
contentTask --> articleGen
contentTask --> coverGen
contentTask --> seoGeoGen
articleGen --> review
coverGen --> review
seoGeoGen --> review
end
operator --> keyword
operator --> review
%% =====================================================
%% AI 辅助快速定制前端
%% =====================================================
subgraph frontendCustomize["AI 辅助快速定制前端"]
template["标准 Astro 前端模板<br/>内置基础页面结构、文章展示、SEO、GEO 和发布规范"]
cmsContract["固定 CMS 内容接口和内容协议<br/>统一内容快照、媒体清单和字段格式"]
aiFrontend["使用 AI 定制前端<br/>修改页面结构、品牌视觉、组件和样式"]
siteCode["形成独立站点目录<br/>sites/站点目录"]
template --> aiFrontend
cmsContract --> aiFrontend
aiFrontend --> siteCode
end
developer --> aiFrontend
%% =====================================================
%% 站点目录文件
%% =====================================================
subgraph siteContract["每个站点目录中的当前文件"]
siteFiles["site.config.json:站点身份、名称、域名、文章路由和站点级 AI 配置<br/>cms.schema.json:本站支持的内容类型、字段和页面区块规则<br/>package.json:前端依赖和构建命令<br/>package-lock.json:锁定依赖版本,保证本地、Action 和服务器一致<br/>astro.config.mjs:静态构建方式和构建输出目录<br/>scripts/fetch-cms-data.mjs:构建前从 CMS 获取本站内容快照<br/>scripts/sync-cms-media.mjs:下载并校验 CMS 图片<br/>scripts/build-service.mjs:接收发布请求、执行构建并切换静态版本<br/>scripts/ensure-standard-site-files.mjs:生成或检查 robots、Sitemap、RSS、llms.txt<br/>Dockerfile 或 Dockerfile.builder:构建站点构建服务镜像<br/>deploy/site-nginx.conf:配置 Nginx 提供当前生效版本"]
end
siteCode --> siteFiles
%% =====================================================
%% 代码仓库与 GitHub Actions
%% =====================================================
subgraph github["代码仓库与自动化发布"]
codeRepo["cloud-server-sites 代码仓库<br/>CMS、站点前端、部署脚本和配置"]
pullRequest["代码检查<br/>CMS 测试、部署测试、站点配置校验<br/>各 Astro 站点构建检查"]
actionStart["GitHub Actions 开始运行"]
scanSites["重新扫描全部 sites/**<br/>每次根据仓库当前内容重新生成结果"]
readCurrentConfig["读取每个站点当前配置<br/>站点 ID、名称、域名、Schema、文章路由<br/>构建入口、输出目录、部署目录和服务器配置"]
validateSites["校验当前站点清单<br/>ID 和域名不能重复<br/>Schema 格式和版本有效<br/>构建和发布文件齐全"]
generateRegistry["生成 CMS 项目注册表<br/>站点名称、域名、内容模块和 Schema"]
generateSeeds["生成站点初始化种子<br/>仅用于首次初始化<br/>不覆盖已有 CMS 内容"]
generateRoutes["生成公网路由配置<br/>域名、www 跳转、活动链接转发<br/>普通请求转发到对应站点"]
generateRuntime["生成站点运行配置<br/>环境变量、站点构建服务地址、容器别名和端口"]
selectTargets["根据代码变化选择部署目标<br/>确定需要构建和部署的组件"]
buildImages["构建受影响组件的镜像<br/>CMS 镜像或站点构建服务镜像"]
imageRegistry[("GHCR 镜像仓库<br/>保存带提交版本的容器镜像")]
deployConfig["通过 SSH 部署当前生成的配置和镜像<br/>上传 CMS 注册表、初始化种子、公网路由和运行配置<br/>更新目标服务器上的 Docker 服务"]
deployHealth["部署后检查<br/>容器健康状态、CMS 接口、站点构建服务接口<br/>首页和公开域名"]
configError["停止发布并报告配置错误"]
deploymentDone["完成本次代码发布"]
keepOldCode["保留原有运行版本"]
codeRepo --> pullRequest
codeRepo --> actionStart
siteFiles --> codeRepo
actionStart --> scanSites
scanSites --> readCurrentConfig
readCurrentConfig --> validateSites
validateSites -->|校验失败| configError
validateSites -->|校验通过| generateRegistry
generateRegistry --> generateSeeds
generateSeeds --> generateRoutes
generateRoutes --> generateRuntime
generateRuntime --> selectTargets
selectTargets --> buildImages
buildImages --> imageRegistry
imageRegistry --> deployConfig
deployConfig --> deployHealth
deployHealth -->|成功| deploymentDone
deployHealth -->|失败| keepOldCode
end
developer --> codeRepo
%% =====================================================
%% CMS 控制中心
%% =====================================================
subgraph controlPlane["CMS 控制中心"]
cmsWeb["CMS 管理后台<br/>站点切换、文章、图片、SEO、AI 任务和投放管理"]
cmsApi["CMS 后端服务<br/>身份认证、站点隔离、内容管理和构建接口"]
aiService["AI 任务服务<br/>调用文本模型和图片模型<br/>保存任务状态、生成结果和错误信息"]
postgres[("PostgreSQL<br/>站点、文章、页面、分类、Schema<br/>AI 任务、活动、访问和转化数据")]
contentStore[("持久化内容目录<br/>站点 JSON 和兼容快照")]
mediaStore[("持久化图片目录<br/>按站点保存图片原件")]
runtimeConfig["AI 运行时配置<br/>模型、API 地址、加密密钥和任务配置<br/>只保存在服务器"]
cmsWeb --> cmsApi
cmsApi --> postgres
cmsApi --> contentStore
cmsApi --> mediaStore
cmsApi --> runtimeConfig
cmsApi --> aiService
end
review --> cmsWeb
generateRegistry -->|部署后提供站点注册信息| cmsApi
generateSeeds -->|部署后提供首次初始化内容| contentStore
generateRuntime -->|部署后提供运行环境配置| cmsApi
%% =====================================================
%% AI 内容生成执行
%% =====================================================
subgraph aiExecution["AI 内容生成执行"]
textModel["文本模型"]
imageModel["图片模型"]
taskResult["生成结果<br/>文章、封面图和 SEO/GEO 字段"]
aiService --> textModel
aiService --> imageModel
textModel --> taskResult
imageModel --> taskResult
taskResult --> cmsApi
cmsApi --> review
end
%% =====================================================
%% CMS 内容发布
%% =====================================================
subgraph contentPublish["CMS 内容发布到站点"]
publishTask["运营点击立即发布<br/>指定一个或多个站点"]
createSnapshot["CMS 读取当前站点内容<br/>只取已发布内容"]
schemaCheck["按当前站点 Schema 校验<br/>字段、区块、必填项和内容格式"]
mediaManifest["生成图片清单<br/>图片路径、文件大小、SHA-256<br/>受保护下载地址"]
callBuildService["调用对应站点构建服务<br/>携带站点 ID、发布任务 ID 和访问令牌"]
publishError["发布失败<br/>返回校验或构建错误"]
publishTask --> createSnapshot
createSnapshot --> schemaCheck
schemaCheck -->|通过| mediaManifest
schemaCheck -->|失败| publishError
mediaManifest --> callBuildService
createSnapshot -.->|读取内容| postgres
createSnapshot -.->|读取兼容内容| contentStore
mediaManifest -.->|读取图片| mediaStore
end
cmsWeb --> publishTask
%% =====================================================
%% 独立站点构建节点
%% =====================================================
subgraph siteNodes["多个独立站点节点"]
siteBuildService["站点构建服务<br/>每个站点独立运行<br/>接收发布请求并执行静态构建"]
fetchSnapshot["拉取 CMS 内容快照<br/>页面、产品、分类、SEO 和已发布文章"]
syncMedia["下载引用图片<br/>校验文件大小和 SHA-256<br/>复用站点本地图片缓存"]
astroBuild["执行 Astro 构建<br/>生成静态 HTML、CSS、JS 和图片"]
seoFiles["生成或检查标准文件<br/>robots.txt、Sitemap、RSS、llms.txt"]
staging["临时构建目录<br/>构建期间不影响当前线上版本"]
activateRelease["构建成功后切换当前生效版本<br/>新版本一次性成为线上版本"]
oldRelease["历史发布版本<br/>构建失败时继续提供原有版本"]
nginx["站点 Nginx<br/>读取当前生效版本并提供静态文件"]
callBuildService --> siteBuildService
siteBuildService --> fetchSnapshot
fetchSnapshot --> syncMedia
syncMedia --> astroBuild
astroBuild --> seoFiles
seoFiles --> staging
staging -->|构建成功| activateRelease
staging -->|构建失败| oldRelease
activateRelease --> nginx
oldRelease --> nginx
end
%% =====================================================
%% 服务器部署位置
%% =====================================================
siteServerA["站点服务器 A<br/>运行一个或多个站点节点"]
siteServerB["站点服务器 B<br/>运行一个或多个站点节点"]
siteServerN["站点服务器 N<br/>运行一个或多个站点节点"]
deployConfig -->|按当前站点配置部署| siteServerA
deployConfig -->|按当前站点配置部署| siteServerB
deployConfig -->|按当前站点配置部署| siteServerN
siteServerA --> siteBuildService
siteServerB --> siteBuildService
siteServerN --> siteBuildService
%% =====================================================
%% 公网访问入口
%% =====================================================
subgraph publicAccess["公网访问入口"]
caddy["Caddy<br/>HTTPS、域名路由、压缩和安全响应头"]
normalRequest["普通页面请求<br/>转发到对应站点 Nginx"]
trafficRequest["活动链接和行为采集请求<br/>/r/活动码"]
cmsRequest["CMS 管理和构建接口请求"]
visitor --> caddy
caddy --> normalRequest
caddy --> trafficRequest
caddy --> cmsRequest
normalRequest --> nginx
cmsRequest --> cmsWeb
end
%% =====================================================
%% 流量归因
%% =====================================================
subgraph traffic["流量归因"]
campaign["活动链接<br/>/r/活动码"]
redirect["CMS 记录首次到达<br/>校验活动和落地页<br/>返回 302 跳转"]
landing["站点落地页"]
trackingScript["站点公共采集脚本<br/>记录页面验证、停留、滚动和咨询点击"]
eventApi["CMS 行为事件接口"]
trafficReport["后台查看有效流量、咨询转化和异常访问"]
trafficRequest --> campaign
campaign --> redirect
redirect --> landing
landing --> trackingScript
trackingScript --> eventApi
redirect -.->|写入| postgres
eventApi -.->|写入| postgres
postgres --> trafficReport
trafficReport --> cmsWeb
end
%% =====================================================
%% 备份与恢复
%% =====================================================
subgraph backup["备份与恢复"]
backupJob["备份任务<br/>保存站点内容、图片和数据库相关对象"]
backupStorage[("S3 兼容对象存储<br/>保存备份文件和备份清单")]
restoreWorker["恢复工作进程<br/>预览差异、执行恢复和结果核验"]
backupJob --> backupStorage
backupStorage --> restoreWorker
restoreWorker -->|恢复结构化内容| postgres
restoreWorker -->|恢复文件和图片| contentStore
restoreWorker -->|恢复期间阻止写入| cmsApi
end
cmsApi --> backupJob
agent 系统
flowchart TD
user["操作者"]
web["Web 工作台"]
client["接口访问层"]
user --> web
web --> client
subgraph core_group["核心服务入口"]
direction TB
http_in["1. 接收 HTTP 请求"]
request_ctx["生成请求编号和调用链编号"]
middleware["执行访问日志、超时、限流<br/>跨域和请求体大小检查"]
request_route{"识别请求类型"}
http_in --> request_ctx
request_ctx --> middleware
middleware --> request_route
end
client --> http_in
subgraph management_group["系统管理"]
direction LR
manage_agent["AI 助手配置"]
manage_model["模型接入配置"]
manage_skill["技能配置"]
manage_module["可插拔模块配置"]
manage_schedule["定时计划配置"]
manage_system["系统运行配置"]
manage_agent --> manage_model
manage_model --> manage_skill
manage_skill --> manage_module
manage_module --> manage_schedule
manage_schedule --> manage_system
end
request_route -->|系统管理请求| manage_agent
subgraph call_group["统一功能调用"]
direction TB
call_entry["根据功能名称查找实现"]
call_find["读取当前已启用的功能实现"]
call_actor{"是否由 AI 助手发起"}
call_permission["执行权限检查"]
call_execute["调用对应功能模块"]
call_result{"执行结果"}
call_success["记录成功并触发后续处理"]
call_failure["记录失败原因"]
call_return["返回调用结果"]
call_entry --> call_find
call_find --> call_actor
call_actor -->|是| call_permission
call_actor -->|否| call_execute
call_execute --> call_result
call_result -->|成功| call_success
call_result -->|失败| call_failure
call_success --> call_return
call_failure --> call_return
end
request_route -->|业务功能请求| call_entry
subgraph module_group["可插拔模块运行层"]
direction TB
module_config["读取模块声明<br/>功能、订阅、页面、设置和模型能力"]
module_snapshot["生成当前可用模块清单<br/>配置变化时递增版本"]
module_lookup["根据功能名称找到目标模块"]
module_kind{"模块运行方式"}
module_process["独立进程运行<br/>通过标准输入输出交换 JSON 消息"]
module_wasm["Wasm 运行<br/>限制内存、时间和计算量"]
module_response["返回模块执行结果"]
module_diagnostic["模块异常时记录诊断<br/>失效进程按需重建"]
module_config --> module_snapshot
module_snapshot --> module_lookup
module_lookup --> module_kind
module_kind -->|独立进程| module_process
module_kind -->|Wasm| module_wasm
module_process --> module_response
module_wasm --> module_response
module_process -->|异常| module_diagnostic
module_wasm -->|异常| module_diagnostic
end
call_find -.-> module_snapshot
call_execute --> module_lookup
module_response --> call_result
subgraph conversation_group["会话管理"]
direction TB
msg_append["2. 追加用户消息"]
msg_save["3. 保存会话、线路、分支和原始消息"]
msg_identify["生成消息编号和分支内顺序"]
conv_db[("会话数据库<br/>原始消息唯一保存位置")]
msg_return["4. 立即返回消息写入结果"]
msg_notify["通知前端显示新消息"]
reply_background["5. 后台启动回复流程<br/>不阻塞消息写入接口"]
msg_append --> msg_save
msg_save --> msg_identify
msg_identify --> conv_db
msg_identify --> msg_return
msg_identify --> msg_notify
msg_identify --> reply_background
end
call_entry -->|追加消息| msg_append
msg_return --> call_return
msg_notify --> web
subgraph selector_group["回复处理选择"]
direction TB
reply_received["6. 收到待回复的用户消息"]
reply_find["查找可以处理会话回复的模块"]
reply_order["按优先级稳定排序"]
reply_ask["依次询问是否处理本次回复"]
reply_choice{"处理结果"}
reply_continue["跳过、失败或不适用<br/>继续下一个模块"]
reply_claimed["确定本次回复处理模块"]
reply_none["没有模块接管<br/>记录未处理状态"]
reply_received --> reply_find
reply_find --> reply_order
reply_order --> reply_ask
reply_ask --> reply_choice
reply_choice -->|不接管| reply_continue
reply_continue --> reply_ask
reply_choice -->|接管或完成| reply_claimed
reply_order -->|没有可用模块| reply_none
end
reply_background --> reply_received
subgraph assistant_group["AI 助手运行模块"]
direction TB
assistant_default["7. 默认回复模块"]
assistant_dedup["领取消息处理记录<br/>避免同一消息重复回复"]
assistant_load["加载 AI 助手、模型、技能和生成参数"]
assistant_source["读取当前用户消息"]
assistant_history["按当前分支分页读取历史"]
assistant_skills["加载已启用技能"]
assistant_tools["准备命令和技能调用能力"]
assistant_context["开始组装模型上下文"]
runtime_db[("AI 助手运行数据库<br/>处理记录、执行状态和历史摘要")]
assistant_default --> assistant_dedup
assistant_dedup --> runtime_db
assistant_dedup --> assistant_load
assistant_load --> assistant_source
assistant_source --> assistant_history
assistant_history --> assistant_skills
assistant_skills --> assistant_tools
assistant_tools --> assistant_context
end
reply_claimed --> assistant_default
assistant_source -.->|读取单条消息| call_entry
assistant_history -.->|分页读取历史| call_entry
subgraph context_group["上下文管理"]
direction TB
context_fixed["加入系统要求、AI 助手信息<br/>技能说明和工具定义"]
context_budget["计算可用输入容量<br/>模型窗口减去输出预留和安全余量"]
context_request["请求记忆等模块补充上下文"]
context_accept["按优先级和来源上限<br/>接纳补充内容"]
context_history["将剩余容量分配给会话历史"]
context_check{"历史是否达到压缩阈值"}
context_find_summary["查找已有历史摘要"]
context_validate{"摘要是否仍属于<br/>当前分支的有效历史"}
context_summarize["按完整对话回合生成摘要"]
context_save["保存摘要边界、内容版本和历史指纹"]
context_final{"调用模型前再次检查总容量"}
context_error["停止生成并返回容量错误<br/>不静默删除消息"]
context_ready["形成最终模型输入"]
assistant_context --> context_fixed
context_fixed --> context_budget
context_budget --> context_request
context_request --> context_accept
context_accept --> context_history
context_history --> context_check
context_check -->|未达到阈值| context_final
context_check -->|达到阈值| context_find_summary
context_find_summary --> context_validate
context_validate -->|有效| context_final
context_validate -->|无效或不存在| context_summarize
context_summarize --> context_save
context_save --> runtime_db
context_save --> context_final
context_final -->|超出容量| context_error
context_final -->|通过| context_ready
end
subgraph memory_group["记忆系统"]
direction TB
memory_prepare["收到补充上下文请求"]
memory_scope["根据当前 AI 助手和记忆分类<br/>确定检索范围"]
memory_search["检索相关记忆"]
memory_db[("记忆数据库<br/>提炼内容、来源引用、向量和关系")]
memory_return["返回预算内的记忆内容和来源"]
memory_evidence{"是否需要核对原始依据"}
memory_get_source["根据消息编号读取原文"]
memory_manage["显式新增、查询和审查记忆"]
memory_future["自动从会话提炼记忆<br/>当前尚未接入"]
memory_prepare --> memory_scope
memory_scope --> memory_search
memory_search --> memory_db
memory_search --> memory_return
memory_return --> memory_evidence
memory_evidence -->|需要| memory_get_source
memory_manage --> memory_db
memory_future -.-> memory_db
end
context_request --> memory_prepare
memory_return --> context_accept
memory_get_source -.-> call_entry
msg_identify -.->|未来只传消息编号| memory_future
subgraph model_group["模型调用与工具执行循环"]
direction TB
model_call["8. 请求模型服务"]
model_permission["检查模型调用权限"]
model_endpoint["读取模型地址、凭证和能力"]
model_convert["转换统一请求为目标模型格式"]
model_external["外部模型服务"]
model_parse["解析模型响应"]
model_result{"模型返回类型"}
model_final["得到最终回复"]
model_tool["模型要求调用工具"]
model_continue["将工具结果加入上下文<br/>继续请求模型"]
context_ready --> model_call
model_call --> model_permission
model_permission --> model_endpoint
model_endpoint --> model_convert
model_convert --> model_external
model_external --> model_parse
model_parse --> model_result
model_result -->|最终内容| model_final
model_result -->|工具调用| model_tool
model_continue --> model_call
end
subgraph tool_group["命令与技能执行"]
direction TB
tool_type{"工具类型"}
tool_command["解析本地命令、参数<br/>工作目录和超时"]
tool_skill["读取已启用技能的入口和配置"]
tool_permission["检查工具操作权限"]
tool_run_command["启动本地命令"]
tool_run_skill["执行技能脚本"]
tool_state{"执行状态"}
tool_done["返回执行结果"]
tool_pending["保存等待审批或异步执行状态"]
tool_resume["审批或异步操作完成后恢复"]
model_tool --> tool_type
tool_type -->|命令| tool_command
tool_type -->|技能| tool_skill
tool_command --> tool_permission
tool_skill --> tool_permission
tool_permission --> tool_run_command
tool_permission --> tool_run_skill
tool_run_command --> tool_state
tool_run_skill --> tool_state
tool_state -->|完成| tool_done
tool_state -->|等待| tool_pending
tool_pending --> tool_resume
tool_resume --> tool_done
tool_done --> model_continue
end
subgraph permission_group["权限和审批"]
direction TB
permission_input["根据 AI 助手、操作、目标<br/>会话和任务生成权限请求"]
permission_policy["读取权限策略和命令匹配规则"]
permission_decision{"权限结果"}
permission_allow["允许执行"]
permission_deny["拒绝执行并记录原因"]
permission_approval["创建待审批事项"]
permission_user{"操作者审批结果"}
permission_grant["生成临时许可<br/>单次、当前回复或当前会话"]
permission_db[("权限数据库<br/>权限事件、审批和临时许可")]
permission_input --> permission_policy
permission_policy --> permission_decision
permission_decision -->|允许| permission_allow
permission_decision -->|拒绝| permission_deny
permission_decision -->|需要确认| permission_approval
permission_approval --> permission_user
permission_user -->|批准| permission_grant
permission_user -->|拒绝| permission_deny
permission_grant --> permission_allow
permission_policy --> permission_db
permission_approval --> permission_db
permission_grant --> permission_db
end
call_permission --> permission_input
model_permission --> permission_input
tool_permission --> permission_input
permission_allow --> call_execute
permission_allow --> model_endpoint
permission_allow --> tool_run_command
permission_allow --> tool_run_skill
permission_approval --> tool_pending
permission_deny --> call_failure
subgraph reply_write_group["回复写回"]
direction TB
reply_prepare["9. 整理最终回复格式"]
reply_append["调用追加消息功能"]
reply_save["保存 AI 助手回复"]
reply_db[("会话数据库")]
reply_notify["通知前端更新会话"]
model_final --> reply_prepare
reply_prepare --> reply_append
reply_append --> reply_save
reply_save --> reply_db
reply_save --> reply_notify
end
reply_notify --> web
subgraph event_group["事件记录与订阅"]
direction TB
event_publish["记录已经发生的事实<br/>只携带编号和必要信息"]
event_db[("事件数据库<br/>事件内容和投递状态")]
event_find["查找订阅该事件的模块"]
event_create["为每个订阅者创建投递任务"]
event_loop["后台执行投递"]
event_order["按优先级和稳定顺序排列"]
event_invoke["调用订阅模块"]
event_result{"投递结果"}
event_success["标记投递成功"]
event_failure["记录失败并保留恢复依据"]
event_publish --> event_db
event_db --> event_find
event_find --> event_create
event_create --> event_db
event_create --> event_loop
event_loop --> event_order
event_order --> event_invoke
event_invoke --> event_result
event_result -->|成功| event_success
event_result -->|失败| event_failure
event_success --> event_db
event_failure --> event_db
end
msg_identify -->|消息已创建| event_publish
call_success -->|功能执行成功| event_publish
tool_pending -->|操作状态变化| event_publish
event_invoke --> module_lookup
subgraph subscription_group["主要订阅关系"]
direction TB
sub_operation["异步操作状态变化"]
sub_restore["恢复等待中的 AI 助手执行"]
sub_run["编排任务已创建"]
sub_workflow_start["启动任务编排"]
sub_stage["编排阶段变化"]
sub_workflow_update["更新编排状态"]
sub_artifact["任务产物已创建"]
sub_artifact_record["登记任务产物"]
sub_schedule["定时计划到期"]
sub_schedule_run["启动定时任务"]
sub_operation --> sub_restore
sub_run --> sub_workflow_start
sub_stage --> sub_workflow_update
sub_artifact --> sub_artifact_record
sub_schedule --> sub_schedule_run
end
event_invoke --> sub_operation
event_invoke --> sub_run
event_invoke --> sub_stage
event_invoke --> sub_artifact
event_invoke --> sub_schedule
sub_restore --> tool_resume
subgraph workflow_group["任务编排"]
direction TB
workflow_entry["显式创建任务<br/>或由定时计划触发"]
workflow_create["创建一次任务运行"]
workflow_plan["生成结构化执行方案"]
workflow_steps["从方案生成任务步骤"]
workflow_run_agent["调用 AI 助手执行步骤"]
workflow_check{"检查步骤结果"}
workflow_approval["等待确认或审批"]
workflow_retry["重试、调整方案或结束"]
workflow_artifact["保存文件或结构化产物"]
workflow_accept["根据完成标准验收"]
workflow_finish["生成最终任务结果"]
workflow_db[("任务编排数据库<br/>运行、方案、步骤、尝试<br/>审批、产物和任务交接")]
workflow_entry --> workflow_create
workflow_create --> workflow_db
workflow_create --> workflow_plan
workflow_plan --> workflow_db
workflow_plan --> workflow_steps
workflow_steps --> workflow_db
workflow_steps --> workflow_run_agent
workflow_run_agent --> workflow_check
workflow_check -->|需要确认| workflow_approval
workflow_check -->|失败可重试| workflow_retry
workflow_check -->|成功| workflow_artifact
workflow_approval --> workflow_steps
workflow_retry --> workflow_steps
workflow_artifact --> workflow_db
workflow_artifact --> workflow_accept
workflow_accept -->|通过| workflow_finish
workflow_accept -->|未通过| workflow_retry
end
sub_workflow_start --> workflow_entry
sub_workflow_update --> workflow_check
sub_artifact_record --> workflow_db
sub_schedule_run --> workflow_entry
workflow_run_agent -.-> assistant_default
workflow_artifact -->|产物已创建| event_publish
workflow_finish -->|编排阶段变化| event_publish
workflow_note["普通会话默认不自动进入任务编排<br/>当前主要通过显式创建或定时计划进入"]
workflow_note -.-> workflow_entry
subgraph schedule_group["定时计划"]
direction TB
schedule_db[("定时计划文件")]
schedule_scan["扫描到期计划"]
schedule_trigger{"触发方式<br/>单次、固定间隔或 Cron"}
schedule_executor{"执行方式"}
schedule_command["执行本地命令"]
schedule_workflow["启动任务编排"]
schedule_retry["失败重试和退避"]
schedule_history["保存最近运行历史"]
schedule_delivery["可选:将全文、摘要或结论<br/>投递到指定会话"]
schedule_db --> schedule_scan
schedule_scan --> schedule_trigger
schedule_trigger --> schedule_executor
schedule_executor -->|命令| schedule_command
schedule_executor -->|AI 任务| schedule_workflow
schedule_command --> schedule_retry
schedule_workflow --> workflow_entry
workflow_finish --> schedule_retry
schedule_retry --> schedule_history
schedule_history --> schedule_delivery
schedule_delivery --> msg_append
end
subgraph trace_group["调用链追踪与日志"]
direction TB
trace_root["HTTP 请求入口记录<br/>请求编号和调用链编号"]
trace_feature["功能调用记录"]
trace_module["模块调用记录"]
trace_model["模型或工具调用记录"]
trace_event["事件发布记录"]
trace_async["建立原请求与后台任务的异步关联"]
trace_delivery["事件订阅投递记录"]
trace_reply["回复处理模块选择结果"]
trace_db[("日志数据库<br/>日志、调用节点和异步关联")]
trace_text["核心服务和模块文本日志"]
trace_stream["实时日志通知"]
trace_root --> trace_feature
trace_feature --> trace_module
trace_module --> trace_model
trace_feature --> trace_event
trace_event --> trace_async
trace_async --> trace_delivery
trace_root --> trace_db
trace_feature --> trace_db
trace_module --> trace_db
trace_model --> trace_db
trace_event --> trace_db
trace_async --> trace_db
trace_delivery --> trace_db
trace_reply --> trace_db
trace_module --> trace_text
trace_db --> trace_stream
end
request_ctx -.-> trace_root
call_execute -.-> trace_feature
module_lookup -.-> trace_module
model_call -.-> trace_model
tool_type -.-> trace_model
event_publish -.-> trace_event
event_invoke -.-> trace_delivery
reply_choice -.-> trace_reply
subgraph live_group["前端实时更新"]
direction TB
live_center["实时状态分发中心"]
live_conversation["会话消息变化"]
live_operation["AI 执行和工具状态变化"]
live_permission["权限和审批变化"]
live_module["模块状态变化"]
live_schedule["定时计划变化"]
live_logs["日志变化"]
live_center --> live_conversation
live_center --> live_operation
live_center --> live_permission
live_center --> live_module
live_center --> live_schedule
live_center --> live_logs
end
msg_identify --> live_center
reply_save --> live_center
tool_pending --> live_center
permission_approval --> live_center
module_snapshot --> live_center
schedule_history --> live_center
trace_db --> live_center
live_conversation --> web
live_operation --> web
live_permission --> web
live_module --> web
live_schedule --> web
live_logs --> web