系统配置模块

系统配置是管理后台中最「安静」却最不可或缺的模块。它不像用户管理那样有高频交互,也不像数据分析那样产出直观价值,但一旦配置缺失或出错,整个系统的邮件发送、支付收款、第三方集成都会停摆。对于一个出海 SaaS 产品来说,系统配置模块承载的是产品运行的基础参数——从品牌信息到 SMTP 凭据,从 Stripe API Key 到 Webhook 签名密钥,所有「需要管理员设置但不需要每天改动」的内容,都应该在这里统一管理。

本文从功能设计出发,逐层拆解基础配置、邮件配置、支付配置和第三方集成四个子模块的实现方法,并给出配置管理的工程最佳实践。

一、核心功能设计

系统配置模块的功能可以拆成四个子模块,每个子模块对应一组独立的配置域。

子模块核心职责典型配置项影响范围
基础配置产品信息、品牌设置、联系方式站点名称、Logo、时区、语言、版权信息全局展示、邮件落款、SEO
邮件配置SMTP 连接、发送策略、模板管理SMTP 主机/端口/账号、发件人地址、发送频率限制注册验证、密码重置、通知邮件
支付配置支付网关、货币、税务Stripe/PayPal API Key、默认货币、税率、发票模板订阅、计费、账单
第三方集成OAuth、API Key、WebhookGoogle/GitHub OAuth 凭据、OpenAI API Key、Webhook 签名密钥登录、AI 能力、事件推送

这四个子模块有一个共同特征:配置项数量不多(通常 20-50 项),但每项都敏感。一项错误的 SMTP 配置会让所有用户收不到验证码,一项错误的 Stripe Key 会让支付直接失败。因此配置模块的设计原则不是「功能多」,而是「改得对、改得安全」。

二、基础配置

基础配置是最接近「产品信息」的一层设置。它的变更频率低,但影响面广——站点名称和 Logo 会出现在前端页面、邮件落款、浏览器标签页、OG 图片等多个位置。

2.1 配置项清单

配置项类型默认值说明
site_namestring"My AI Platform"站点名称,用于页面 title、邮件签名
site_descriptionstring""站点描述,用于 SEO meta 和 OG 标签
logo_urlstring"/logo.svg"品牌 Logo URL,支持 SVG 和 PNG
favicon_urlstring"/favicon.ico"浏览器标签图标
primary_colorstring"#6366F1"品牌主色,用于邮件模板和部分 UI 覆盖
default_localestring"en"默认语言,影响未登录用户的界面语言
default_timezonestring"UTC"默认时区,影响数据展示和定时任务
support_emailstring"[email protected]"客服邮箱,出现在错误页面和邮件底部
copyright_textstring"© 2026 Company Inc."页脚版权信息
terms_urlstring"/terms"服务条款页面链接
privacy_urlstring"/privacy"隐私政策页面链接

2.2 实现要点

基础配置的存储方案有两种主流选择:

方案一:单行 JSON 存储。 将所有基础配置存入数据库的一行记录,字段类型为 JSONB。读取时一次查询拿到全部配置,写入时整体更新。

CREATE TABLE system_settings (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  category   VARCHAR(50) NOT NULL UNIQUE,  -- 'basic' / 'email' / 'payment' / 'integration'
  config     JSONB NOT NULL DEFAULT '{}',
  updated_by UUID NOT NULL REFERENCES users(id),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
 
-- 示例数据
INSERT INTO system_settings (category, config) VALUES (
  'basic',
  '{"site_name": "My AI Platform", "default_locale": "en", "default_timezone": "UTC"}'
);

这种方案的优点是读取简单、一次查询搞定;缺点是 JSONB 内部字段无法直接加数据库约束,字段校验完全依赖应用层。

方案二:Key-Value 存储。 每个配置项独立一行,key 为配置名,value 为配置值。

CREATE TABLE system_settings (
  key        VARCHAR(100) PRIMARY KEY,
  value      TEXT NOT NULL,
  value_type VARCHAR(20) NOT NULL DEFAULT 'string',  -- string / number / boolean / json
  category   VARCHAR(50) NOT NULL,
  updated_by UUID NOT NULL REFERENCES users(id),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

这种方案的优点是每个字段可以独立加约束、独立审计;缺点是读取时需要多行查询或聚合。对于配置项在 50 个以内的场景,方案一已经足够。

无论哪种方案,都需要在应用层做两件事:

类型安全的配置读取。 不要把 process.env 或数据库查询散落在业务代码中。应该在 config/ 目录下提供一个类型安全的配置读取层:

// config/system.ts
export interface BasicConfig {
  siteName: string
  siteDescription: string
  logoUrl: string
  faviconUrl: string
  primaryColor: string
  defaultLocale: string
  defaultTimezone: string
  supportEmail: string
  copyrightText: string
  termsUrl: string
  privacyUrl: string
}
 
const DEFAULT_BASIC_CONFIG: BasicConfig = {
  siteName: 'My AI Platform',
  siteDescription: '',
  logoUrl: '/logo.svg',
  faviconUrl: '/favicon.ico',
  primaryColor: '#6366F1',
  defaultLocale: 'en',
  defaultTimezone: 'UTC',
  supportEmail: '[email protected]',
  copyrightText: '© 2026 Company Inc.',
  termsUrl: '/terms',
  privacyUrl: '/privacy',
}
 
export async function getBasicConfig(): Promise<BasicConfig> {
  const row = await db.query(
    "SELECT config FROM system_settings WHERE category = 'basic'"
  )
  if (!row) return DEFAULT_BASIC_CONFIG
  return { ...DEFAULT_BASIC_CONFIG, ...row.config }
}

这段代码的关键在于 默认值兜底。数据库里可能没有记录(首次部署),也可能某个字段缺失(新增配置项后未迁移),默认值确保系统不会因为配置缺失而崩溃。

配置缓存。 基础配置的读取频率远高于写入频率。每次页面渲染都查一次数据库是不必要的。推荐的做法是在应用启动时加载一次配置到内存,配置变更时通过事件通知刷新缓存。对于多实例部署,可以用 Redis Pub/Sub 或数据库 trigger 通知所有实例刷新。

三、邮件配置

邮件是出海产品与用户沟通的核心渠道。注册验证、密码重置、订阅确认、账单通知——这些场景全部依赖邮件。邮件配置模块需要管理三件事:SMTP 连接、发送策略和模板管理。

3.1 SMTP 配置

SMTP 是邮件发送的底层通道。出海产品常用的邮件服务商包括 SendGrid、AWS SES、Postmark 和 Mailgun,它们都提供标准的 SMTP 接口。

配置项类型必填说明
smtp_hoststringSMTP 服务器地址,如 smtp.sendgrid.net
smtp_portnumber端口号,通常为 587(STARTTLS)或 465(SSL)
smtp_userstring认证用户名
smtp_passwordstring认证密码或 API Key
smtp_secureboolean是否使用 SSL/TLS,默认 true
from_addressstring发件人邮箱,如 [email protected]
from_namestring发件人名称,如 "My AI Platform"
reply_tostring回复地址,留空则使用 from_address

SMTP 密码属于敏感信息,存储时必须加密。推荐使用 AES-256 加密后存入数据库,解密密钥通过环境变量注入:

// lib/crypto.ts
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
 
const ALGORITHM = 'aes-256-cbc'
 
function getKey(): Buffer {
  const secret = process.env.CONFIG_ENCRYPTION_KEY
  if (!secret) throw new Error('CONFIG_ENCRYPTION_KEY is required')
  return Buffer.from(secret, 'hex')
}
 
export function encrypt(plaintext: string): string {
  const iv = randomBytes(16)
  const cipher = createCipheriv(ALGORITHM, getKey(), iv)
  const encrypted = cipher.update(plaintext, 'utf8', 'hex') + cipher.final('hex')
  return `${iv.toString('hex')}:${encrypted}`
}
 
export function decrypt(ciphertext: string): string {
  const [ivHex, encrypted] = ciphertext.split(':')
  const iv = Buffer.from(ivHex, 'hex')
  const decipher = createDecipheriv(ALGORITHM, getKey(), iv)
  return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8')
}

3.2 发送策略

邮件发送不是「调 SMTP 发出去」就结束了。出海产品需要关注发送频率、失败重试和送达率。

频率限制。 同一用户每分钟最多收到 3 封邮件(防止密码重置接口被滥用),每天最多 20 封(防止通知邮件泛滥)。频率限制应该在应用层实现,用 Redis 的滑动窗口计数器:

async function checkRateLimit(userId: string, template: string): Promise<boolean> {
  const key = `email_rate:${userId}:${template}`
  const count = await redis.incr(key)
  if (count === 1) await redis.expire(key, 60) // 1 分钟窗口
  return count <= 3
}

失败重试。 SMTP 发送失败时,不应立即放弃。网络抖动、SMTP 服务器临时过载都是常见原因。推荐的重试策略是指数退避:第一次等 30 秒,第二次等 2 分钟,第三次等 10 分钟,最多重试 3 次。超过重试次数后,将邮件标记为失败并通知管理员。

发送队列。 邮件发送应该异步化。用户注册后立即返回成功响应,邮件发送任务推入队列(可以用 BullMQ + Redis),由 Worker 消费。这样即使 SMTP 服务短暂不可用,也不会阻塞用户注册流程。

3.3 模板管理

出海产品的邮件模板通常需要支持多语言。模板管理模块需要解决两个问题:模板存储和变量替换。

模板存储推荐使用数据库,每个模板一条记录,支持多语言版本:

CREATE TABLE email_templates (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name        VARCHAR(100) NOT NULL,        -- 'welcome' / 'reset_password' / 'invoice'
  locale      VARCHAR(10) NOT NULL DEFAULT 'en',
  subject     TEXT NOT NULL,
  body_html   TEXT NOT NULL,
  body_text   TEXT NOT NULL,                 -- 纯文本备选,部分客户端不支持 HTML
  variables   JSONB DEFAULT '[]',            -- 模板中使用的变量列表
  is_active   BOOLEAN NOT NULL DEFAULT true,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT uq_email_templates UNIQUE (name, locale)
);

变量替换使用简单的模板引擎,避免引入过重的依赖:

function renderTemplate(template: string, vars: Record<string, string>): string {
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '')
}
 
// 使用示例
const html = renderTemplate(rawTemplate.body_html, {
  userName: 'Alice',
  resetLink: 'https://example.com/reset?token=abc123',
  siteName: 'My AI Platform',
})

四、支付配置

支付配置直接影响产品能不能收到钱。出海产品面对的支付环境比国内更复杂——多币种、多税制、多支付网关。支付配置模块需要管理三件事:支付网关配置、货币设置和税务配置。

4.1 支付网关配置

出海 SaaS 产品最常用的支付网关是 Stripe,其次是 PayPal。部分场景还会用到 Paddle(自动处理税务合规)或 Lemon Squeezy。

配置项类型说明
default_gatewaystring默认支付网关:stripe / paypal / paddle
stripe_secret_keystringStripe Secret Key,加密存储
stripe_webhook_secretstringStripe Webhook 签名密钥
stripe_publishable_keystringStripe Publishable Key,前端使用
paypal_client_idstringPayPal 客户端 ID
paypal_client_secretstringPayPal 客户端密钥
paypal_sandboxboolean是否使用 PayPal 沙箱环境

支付网关的 API Key 和 Webhook Secret 都是高敏感信息。除了加密存储之外,还需要注意:

环境隔离。 Stripe 有 Publishable Key(前端使用,非敏感)和 Secret Key(后端使用,敏感)之分。Webhook Secret 用于验证 Webhook 请求的签名,防止伪造。开发环境和生产环境必须使用不同的 Key,通过环境变量区分。

Key 轮换。 API Key 不是「设置一次就永远不改」。当 Key 泄露或定期安全审计时,需要轮换。配置模块应该支持「新 Key 写入 → 验证 → 切换」的流程,避免中间出现支付中断。Stripe 支持同时启用多个 Key,可以在新 Key 生效后再废弃旧 Key。

4.2 货币与税务配置

出海产品的货币和税务配置需要覆盖多个市场。

配置项类型说明
default_currencystring默认货币代码,如 USDEUR
supported_currenciesstring[]支持的货币列表
currency_display_localestring货币格式化使用的 locale,如 en-US
tax_enabledboolean是否启用税务计算
tax_providerstring税务计算服务商:stripe_tax / avalara / manual
tax_rate_idstring预定义的税率 ID(如 Stripe Tax Rate)
tax_inclusive_pricingboolean价格是否含税(欧洲市场通常含税)
invoice_prefixstring发票编号前缀,如 INV-
invoice_next_numbernumber下一个发票编号

税务配置是出海产品最容易踩坑的地方。不同国家/地区的税务规则差异很大:

  • 美国:各州税率不同,部分州对 SaaS 产品不征税
  • 欧盟:对数字服务征收 VAT,标准税率在 15%-27% 之间,且消费者价格必须含税
  • 日本:消费税 10%,跨境数字服务有特殊的「电子役务提供者」制度

对于中小规模的出海产品,推荐使用 Stripe Tax 自动处理税务计算。它根据客户的账单地址自动判断适用税率,并生成合规的发票。配置模块只需要提供启用/关闭开关和税务服务商选择即可。

4.3 配置验证

支付配置保存前必须做验证。不能只是检查字段非空,还要验证 Key 是否有效:

async function validateStripeConfig(secretKey: string): Promise<{ valid: boolean; error?: string }> {
  try {
    const stripe = new Stripe(secretKey, { apiVersion: '2024-12-18.acacia' })
    // 用一个轻量 API 调用验证 Key 有效性
    await stripe.balance.retrieve()
    return { valid: true }
  } catch (err) {
    return {
      valid: false,
      error: err instanceof Error ? err.message : 'Invalid API Key',
    }
  }
}

对于 SMTP 配置和第三方 API Key,也应该在保存时做类似的连通性验证,而不是等到实际使用时才发现配置错误。

五、第三方集成

出海产品的能力边界很大程度上取决于第三方集成的丰富度。OAuth 登录、AI 模型调用、Webhook 事件推送——这些能力的开关都握在配置模块手里。

5.1 OAuth 集成

OAuth 集成需要管理的配置项包括:

配置项类型说明
google_client_idstringGoogle OAuth Client ID
google_client_secretstringGoogle OAuth Client Secret
google_enabledboolean是否启用 Google 登录
github_client_idstringGitHub OAuth App Client ID
github_client_secretstringGitHub OAuth App Client Secret
github_enabledboolean是否启用 GitHub 登录

OAuth 配置有一个容易忽略的细节:回调 URL(Callback URL)。Google 和 GitHub 的 OAuth 应用配置中,需要填写准确的回调 URL。开发环境用 http://localhost:3000/auth/callback/google,生产环境用 https://example.com/auth/callback/google。配置模块应该在页面上明确提示当前环境对应的回调 URL,减少配置错误。

5.2 API Key 管理

AI 产品通常需要调用多个外部 AI 服务,每个服务都有自己的 API Key。

配置项类型说明
openai_api_keystringOpenAI API Key
anthropic_api_keystringAnthropic API Key
default_ai_providerstring默认 AI 模型提供商
default_ai_modelstring默认模型名称

API Key 管理除了加密存储之外,还需要关注 用量监控。配置模块可以提供一个简单的 API 调用次数统计面板,让管理员了解每个 Key 的消耗情况。当某个 Key 接近配额上限时,提前发出告警。

5.3 Webhook 配置

Webhook 是产品对外推送事件的通道。管理员可以配置 Webhook URL 和需要订阅的事件类型。

CREATE TABLE webhooks (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  url           TEXT NOT NULL,
  secret        VARCHAR(255) NOT NULL,      -- 用于签名,加密存储
  events        TEXT[] NOT NULL,             -- 订阅的事件列表
  is_active     BOOLEAN NOT NULL DEFAULT true,
  failure_count INT NOT NULL DEFAULT 0,      -- 连续失败次数
  last_fired_at TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

Webhook 配置需要关注的核心问题是 可靠性。发送 Webhook 时,如果目标服务器返回非 2xx 响应或超时(建议 10 秒超时),应该进入重试队列。连续失败超过 5 次后,自动禁用该 Webhook 并通知管理员。同时,配置模块应该展示每个 Webhook 的最近投递记录和失败原因,方便排查问题。

六、配置管理最佳实践

四个子模块的实现细节各不相同,但在配置管理层面上有一些通用的最佳实践。

6.1 配置变更审计

每一次配置变更都应该被记录。审计日志至少包含:操作者、操作时间、变更前的值、变更后的值。

CREATE TABLE config_audit_logs (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  category    VARCHAR(50) NOT NULL,
  config_key  VARCHAR(100) NOT NULL,
  old_value   TEXT,
  new_value   TEXT,
  operator_id UUID NOT NULL REFERENCES users(id),
  operated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  ip_address  INET
);

对于敏感配置(API Key、SMTP 密码),审计日志中不应记录明文值,只记录「已变更」状态或哈希值。

6.2 配置版本与回滚

配置被改错是常有的事。一个实用的设计是为每个配置类别维护版本号,变更时保存完整快照:

CREATE TABLE config_versions (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  category   VARCHAR(50) NOT NULL,
  version    INT NOT NULL,
  config     JSONB NOT NULL,
  created_by UUID NOT NULL REFERENCES users(id),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  CONSTRAINT uq_config_versions UNIQUE (category, version)
);

当配置变更后出现问题,管理员可以选择回滚到指定版本。回滚操作本身也应该被记录到审计日志中。

6.3 前端交互设计

配置管理页面的前端交互有几个值得注意的设计要点:

分组展示。 配置项按类别分组(基础配置、邮件配置、支付配置、第三方集成),每组独立保存。避免一个大表单包含所有配置——一个 SMTP 配置的修改不应该触发支付配置的重新提交。

表单校验前置。 在提交前完成所有可能的校验。URL 格式校验、邮箱格式校验、API Key 格式校验都应该在前端实时进行。对于 API Key 的连通性验证,可以在用户输入完成后提供一个「测试连接」按钮。

敏感字段遮罩。 API Key、密码等敏感字段在页面上默认显示为 ••••••••••••。只有用户点击「显示」按钮后才展示明文。编辑模式下,如果用户没有修改敏感字段,提交时不应该覆盖原有值。

变更确认。 对于高风险配置(支付网关切换、SMTP 服务器更换),要求管理员二次确认——输入当前密码或输入配置项名称确认。

七、案例:两个典型的配置管理场景

案例一:AI 写作工具的邮件系统配置

一个面向海外用户的 AI 写作工具,需要处理注册验证、密码重置、用量告警和账单通知四类邮件。

初始阶段,团队直接在代码中硬编码了 SendGrid 的 SMTP 配置。随着用户增长,他们遇到了几个问题:SMTP 密码过期后需要改代码重新部署;不同环境的配置混在一起导致误发测试邮件到生产用户;没有发送频率限制,恶意用户可以触发大量密码重置邮件。

解决方案是建立独立的邮件配置模块:

  1. SMTP 配置迁移到数据库,支持后台界面修改,不再需要重新部署
  2. 增加频率限制:每用户每分钟 3 封,每天 20 封
  3. 邮件发送异步化,使用 BullMQ 队列处理
  4. 邮件模板支持多语言,管理员可以在后台编辑模板内容
  5. 每次 SMTP 配置变更自动发送一封测试邮件到管理员邮箱,验证配置有效性

改造后,运营团队可以独立管理邮件模板和 SMTP 配置,技术团队不再需要介入日常的邮件配置调整。

案例二:SaaS 产品的多网关支付配置

一个 AI SaaS 产品同时服务北美和欧洲市场。北美用户使用 Stripe 支付,欧洲用户更习惯 PayPal。产品需要同时对接两个支付网关,并根据用户的地理位置自动选择。

配置模块的设计方案:

  1. 支持同时配置多个支付网关,每个网关独立管理 API Key 和 Webhook Secret
  2. 增加一个「网关路由规则」配置:按用户地区选择默认网关,管理员可以设置 US/CA → StripeEU → PayPal
  3. 货币配置支持按地区设定默认货币:北美 USD、欧洲 EUR
  4. 税务配置启用 Stripe Tax,自动处理欧洲 VAT 和美国各州销售税
  5. 每个支付网关配置变更后,使用小额($0.01)测试交易验证连通性

这个配置架构的关键在于,当某个支付网关出现故障时,管理员可以在后台快速切换默认网关,而不需要修改代码或重新部署。

八、系统配置管理流程

下面用 Mermaid 流程图展示系统配置从设置到生效的完整流程:

适读画布 · 130%
Mermaid 流程图加载中...

九、配置模块实现检查清单

在实现系统配置模块时,以下检查项可以帮你避免常见的遗漏:

#检查项说明
1敏感字段加密存储API Key、密码等使用 AES-256 加密,不存明文
2配置变更审计日志记录操作者、时间、变更前后值
3默认值兜底每个配置项都有合理的默认值,防止配置缺失导致系统崩溃
4保存前连通性验证SMTP、支付网关等关键配置保存时验证可用性
5配置缓存与刷新机制避免每次读取都查数据库,变更时及时刷新缓存
6敏感字段前端遮罩页面上默认隐藏敏感值,编辑时未修改不覆盖
7配置版本与回滚能力支持回滚到历史版本
8环境隔离开发/测试/生产使用不同配置,互不影响
9权限控制只有具备 system:config 权限的角色可以修改配置
10高风险操作二次确认支付网关切换、SMTP 更换等操作需要确认
11邮件频率限制防止滥用邮件发送接口
12Webhook 失败重试与自动禁用保证 Webhook 投递的可靠性
13配置导出与导入支持配置备份和跨环境迁移

十、小结

系统配置模块看起来简单,实际上是一个涉及安全、可靠性和可维护性的工程问题。它的核心价值不在于「能配置什么」,而在于「配置改得安全、改得可控、改得可追溯」。

对于出海产品来说,系统配置模块还需要额外关注多语言、多币种、多税制和多时区的兼容性。这些维度在设计初期就应该纳入考量,而不是等到产品进入新市场时再回头改造。

一个好的配置模块,应该让运营团队可以独立完成大部分配置调整,而不需要工程师介入。这才是管理后台该有的样子——把控制权交给最接近业务的人,同时用审计日志和版本控制守住安全底线。

参考资料