线上环境固定数据同步检查清单¶
本文用于上线前核对本地或测试环境中哪些“固定数据/运营数据”需要同步到线上环境。这里的固定数据指:应用运行时从数据库或仓库文件读取、但不属于用户业务数据的配置,例如套餐、首购优惠、兑换码、Prompt 模板和 Prompt 场景绑定。
本文基于 2026-05-21 对本地
.env.dev数据库的只读检查,以及当前代码仓库结构整理。
结论¶
上线前必须重点同步:
system_configs:首购折扣相关配置。subscription_plans:商城套餐定义。prompt_templates+prompt_template_versions:线上运行时 Prompt 模板。prompt_scene_bindings:Prompt 场景到模板的绑定规则。
需要按活动策略决定是否同步:
redeem_codes:内部兑换码/促销码。本地当前没有可用兑换码,不建议把已使用或测试码直接带到线上。
通常不需要单独同步:
app/prompts/**文件:属于代码发布内容,随镜像/代码上线。PROMPT_SCENE_DEFINITIONS场景枚举:代码常量,随代码上线。- 用户、订单、标书、知识库、产品库等业务数据:不属于本清单范围。
必须同步的数据¶
1. 系统配置:system_configs¶
运行时用途:
first_purchase_discount:首购折扣率,当前本地值0.85。first_purchase_plan_code:首购适用套餐,当前本地值monthly_1。first_purchase_discount_code:首购折扣审计码,当前本地值SEEMCODE_a0c076cf。
本地当前快照:
| key | value | 说明 |
|---|---|---|
first_purchase_discount |
0.85 |
首购折扣率 |
first_purchase_plan_code |
monthly_1 |
首购适用套餐 |
first_purchase_discount_code |
SEEMCODE_a0c076cf |
首购折扣审计/记录码 |
同步建议:
- 线上如果已经有运营确认过的值,以线上为准,不要被本地覆盖。
- 新环境初始化时,需要至少写入这 3 个 key。
校验 SQL:
SELECT key, value, description, updated_at
FROM system_configs
WHERE key IN (
'first_purchase_discount',
'first_purchase_plan_code',
'first_purchase_discount_code'
)
ORDER BY key;
幂等 upsert 示例:
INSERT INTO system_configs (key, value, description, created_at, updated_at)
VALUES
('first_purchase_discount', '0.85', '首购折扣(0-1之间的小数)', now(), now()),
('first_purchase_plan_code', 'monthly_1', '首购适用的套餐 plan_code', now(), now()),
('first_purchase_discount_code', 'SEEMCODE_a0c076cf', '首购折扣码,用于审计/记录', now(), now())
ON CONFLICT (key) DO UPDATE SET
value = EXCLUDED.value,
description = EXCLUDED.description,
updated_at = now();
2. 套餐定义:subscription_plans¶
运行时用途:
- 商城套餐列表。
- 下单金额、配额发放、有效期、是否允许叠加。
- 新人注册福利逻辑会按
newcomer_welfare查询;当前本地库没有该套餐,代码 seed 脚本中有。
本地当前快照:
| plan_code | name | type | price_cents | original_price_cents | quota | period_days | monthly_count | active | sort |
|---|---|---|---|---|---|---|---|---|---|
monthly_1 |
月套餐1 | MONTHLY |
8800 | 12800 | 1000000 | 30 | true | 1 | |
monthly_2 |
月套餐2 | MONTHLY |
18800 | 25800 | 3000000 | 30 | true | 2 | |
monthly_3 |
月套餐3 | MONTHLY |
28800 | 38800 | 不限 | 30 | true | 3 | |
yearly_1 |
年套餐1 | YEARLY |
88800 | 108000 | 3000000 | 365 | 12 | true | 4 |
yearly_2 |
年套餐2 | YEARLY |
138800 | 246900 | 5000000 | 365 | 12 | true | 5 |
yearly_3 |
年套餐3 | YEARLY |
188800 | 320000 | 不限 | 365 | 12 | true | 6 |
topup_1 |
字数套餐1 | TOPUP |
8800 | 10000 | 500000 | 永久 | true | 7 |
同步建议:
- 新环境初始化或线上缺失套餐时,按
plan_code幂等 upsert。 - 现有线上环境若已经有真实价格/活动价,需要先由运营确认,不要盲目用本地覆盖。
exector/insert_subscription_plans.sql是幂等 upsert,但文件中的original_price_cents与本地当前库不完全一致;上线前需要选择一个权威来源。- 建议补齐或确认
newcomer_welfare。代码app/services/subscription/service.py会查询该plan_code发放新人福利。
校验 SQL:
SELECT plan_code, name, plan_type, distribution_type, price_cents,
original_price_cents, character_quota_per_period, period_days,
monthly_distribution_count, allow_stacking, is_active,
sort_order, tag, features, updated_at
FROM subscription_plans
ORDER BY sort_order, plan_code;
3. Prompt 模板:prompt_templates / prompt_template_versions¶
运行时用途:
document_parse:页级/全文解析使用。outline_skeleton:一二级目录骨架生成使用,但当前本地模板 key 是outline_l1_l2,需注意代码实际读取 key。writing_hint:章节编写思路与三四级目录生成使用。section_content:章节正文生成使用。
本地当前模板快照(记录运行时版本的正文长度和 SHA-256,便于和线上比对):
| key | version | status | body_chars | body_sha256 |
|---|---|---|---|---|
document_parse |
1 | published | 1890 | 338026896d4329fe4db5022ec89f633bacb435b5b046c37d70ad42573035d0d9 |
outline_l1_l2 |
1 | published | 866 | c1be38cefd6e3534131369f71c1af9aac036477b1c92b6de9b2e089b514ba3d6 |
project_overview |
1 | published | 1134 | 62e7632934daf4612ad05484a3fd64e3791b77747997da3d5344a89d17dc2ee3 |
scoring_criteria |
1 | published | 909 | d5d4731ba82cd19f81b7e457bb397ca14d6f44f4a5026d95185aecfc8b329536 |
section_content_engineering |
1 | published | 1452 | 17e9bd9aadda9cb4d0254effffcad2cbc4734d023a41da65a73d1c0dbc7dddaa |
section_content_goods_distributor |
1 | published | 1403 | ffa3dfd3200409829ad66030efaae5f48ed1dd101bb31028875f5e0c52265bee |
section_content_goods_manufacturer |
1 | published | 1443 | 7d4161465f68b339fc3bfd9b32c899e1281e5fa214fc57d643c8c58231f8e788 |
section_content_service |
1 | published | 2723 | 7e95de18749aed4c0382dec7b64951527d2325ffccb6430c0eb0e001e934c401 |
writing_hint_engineering |
1 | published | 3145 | 38dc48e3adb4d0a8ad848041bf8acfefa5eec30e35ee6b80ba6ea4eb4851860b |
writing_hint_goods_distributor |
1 | published | 3198 | 713a57a0682c931561aff8ca172b652bf3b9a49645bfe7c513ad699c4d2ac71e |
writing_hint_goods_manufacturer |
1 | published | 3035 | 77d3990e2b0587e08433a09fd37cd751c5284001e5d54d7126f874202a72d823 |
writing_hint_service |
1 | published | 2608 | abd37981cf43c431c6a3c18c8ba607c866f9175f913e112f2d8114218635132e |
同步建议:
- Prompt 模板是数据库运行时数据,不会因为部署代码自动同步。
- 需要同步
prompt_templates和对应prompt_template_versions的已发布版本。 - 同步时优先按
prompt_templates.key作为稳定身份,不要依赖本地 UUID。 - 每个模板线上至少要有一个
published版本;服务会优先取最新 published,否则退回最新版本,但生产环境不建议依赖草稿回退。
校验 SQL:
CREATE EXTENSION IF NOT EXISTS pgcrypto;
SELECT t.key,
t.display_name,
t.description,
v.version,
v.status,
v.published_at,
length(v.body) AS body_chars,
encode(digest(v.body, 'sha256'), 'hex') AS body_sha256,
greatest(t.updated_at, v.updated_at) AS updated_at
FROM prompt_templates t
LEFT JOIN LATERAL (
SELECT *
FROM prompt_template_versions v
WHERE v.template_id = t.id
ORDER BY CASE WHEN v.status = 'published' THEN 0 ELSE 1 END,
v.version DESC
LIMIT 1
) v ON true
ORDER BY t.key;
导出建议:
COPY (
SELECT jsonb_pretty(jsonb_agg(row_to_json(x)::jsonb))
FROM (
SELECT t.key, t.display_name, t.description,
v.version, v.body, v.status, v.change_note
FROM prompt_templates t
JOIN prompt_template_versions v ON v.template_id = t.id
WHERE v.status = 'published'
ORDER BY t.key, v.version
) x
) TO STDOUT;
4. Prompt 场景绑定:prompt_scene_bindings¶
运行时用途:
- 根据
scene_key + tender.industry + tender.goods_role解析应使用哪个 Prompt 模板。 - 匹配优先级:
industry+goods_role优先,其次industry,再其次goods_role,最后默认绑定;同层级priority数字越小越优先。
本地当前绑定快照:
| scene_key | industry | goods_role | template_key | priority | active |
|---|---|---|---|---|---|
section_content |
SERVICE |
section_content_service |
100 | true | |
section_content |
GOODS |
MANUFACTURER |
section_content_goods_manufacturer |
100 | true |
section_content |
GOODS |
DISTRIBUTOR |
section_content_goods_distributor |
100 | true |
section_content |
ENGINEERING |
section_content_engineering |
100 | true | |
writing_hint |
SERVICE |
writing_hint_service |
100 | true | |
writing_hint |
GOODS |
MANUFACTURER |
writing_hint_goods_manufacturer |
100 | true |
writing_hint |
GOODS |
DISTRIBUTOR |
writing_hint_goods_distributor |
100 | true |
writing_hint |
ENGINEERING |
writing_hint_engineering |
100 | true |
同步建议:
- 必须在模板导入后再导入绑定,因为绑定需要引用
prompt_templates.id。 - 导入时应通过
template_key在目标环境查出template_id,不要直接复制本地template_id。 - 当前没有
document_parse/project_overview/scoring_criteria/outline_skeleton的场景绑定;其中document_parse是按模板 key 直接读取,不需要绑定。
校验 SQL:
SELECT b.scene_key,
b.industry::text,
b.goods_role::text,
t.key AS template_key,
b.priority,
b.is_active,
b.remark,
b.updated_at
FROM prompt_scene_bindings b
JOIN prompt_templates t ON t.id = b.template_id
ORDER BY b.scene_key, b.priority, b.industry NULLS FIRST, b.goods_role NULLS FIRST;
按策略同步的数据¶
兑换码/促销码:redeem_codes¶
运行时用途:
- 下单时传入
redeem_code,命中后订单金额改为amount_cents。 - 每个 code 只能核销一次,核销后写入
user_id/order_id/used_at。
本地当前状态:
- 当前本地没有
is_active = true AND used_at IS NULL的可用兑换码。
同步建议:
- 不建议直接同步本地
redeem_codes全表,避免带入测试码、已使用码或测试用户绑定关系。 - 若线上要发放内部码,应在上线前单独生成一批新 code,只同步
code/is_active/amount_cents,不要同步user_id/order_id/used_at。 - code 格式受约束:12 位大写字母/数字,当前工具会避免容易混淆字符。
校验 SQL:
SELECT code, is_active, amount_cents, used_at, user_id, order_id, updated_at
FROM redeem_codes
ORDER BY updated_at DESC, code;
只看可用码:
SELECT code, amount_cents, updated_at
FROM redeem_codes
WHERE is_active = true AND used_at IS NULL
ORDER BY updated_at DESC, code;
代码随发布同步的数据¶
这些数据不需要手工从本地数据库同步,但需要确认线上镜像/代码包含最新文件。
app/prompts/**¶
仓库当前包含的 Prompt 文件会随代码发布,例如:
注意:
- 当前部分运行时已经改为从 DB 表
prompt_templates读取,仓库文件不等价于线上运行时 Prompt。 app/core/extraction/extractor.py的document_parse从 DB 读取模板 key:document_parse。
Prompt 场景枚举¶
app/schemas/prompt_scene_binding.py 中的 PROMPT_SCENE_DEFINITIONS 随代码发布,不读库:
document_parseproject_overviewscoring_criteriaoutline_skeletonwriting_hintsection_content
推荐上线流程¶
已整理一份可执行的幂等同步脚本:
脚本范围:system_configs、subscription_plans、prompt_templates、prompt_template_versions、prompt_scene_bindings。脚本不包含 redeem_codes。
- 线上先跑结构迁移:
- 在线上只读导出当前固定数据快照,保存备份:
CREATE EXTENSION IF NOT EXISTS pgcrypto;
SELECT * FROM system_configs;
SELECT * FROM subscription_plans ORDER BY sort_order, plan_code;
SELECT t.key, v.version, v.status, length(v.body), encode(digest(v.body, 'sha256'), 'hex')
FROM prompt_templates t
JOIN prompt_template_versions v ON v.template_id = t.id
ORDER BY t.key, v.version;
SELECT b.*, t.key AS template_key
FROM prompt_scene_bindings b
JOIN prompt_templates t ON t.id = b.template_id;
-
对比本文快照与线上快照,确认哪些以本地为准、哪些以线上运营配置为准。
-
先同步
system_configs和subscription_plans。 -
再同步
prompt_templates/prompt_template_versions。 -
最后同步
prompt_scene_bindings,通过目标环境的template_key -> template_id解析外键。 -
兑换码只按活动需要新建,不做整表复制。
-
上线后跑 smoke test:
-
套餐列表能返回 7 个可售套餐,且价格、原价、字数、排序正确。
- 新用户首购
monthly_1能正确计算首购价。 - 解析任务能读取
document_parsePrompt。 - 三四级目录、编写思路、章节正文分别覆盖
SERVICE/ENGINEERING/GOODS + MANUFACTURER/DISTRIBUTOR。 - 有兑换码时,下单金额按
redeem_codes.amount_cents生效且只能使用一次。
已发现的同步/代码风险¶
exector/insert_subscription_plans.sql是幂等脚本,但其中monthly_3/yearly_1/yearly_2/yearly_3/topup_1的original_price_cents与本地库当前值不一致。app/services/billing/order_pricing_service.py当前读取首购适用套餐时疑似使用了FIRST_PURCHASE_DISCOUNT_KEY,导致拿到0.85后与plan.plan_code比较;这会让首购优惠无法命中。同步数据前后都需要单独修复或验证该逻辑。- Prompt 模板表的
published_at当前本地多为NULL但status = published;如果线上要依赖发布时间排序或审计,需要补齐发布动作或统一修复数据。