Initial commit
This commit is contained in:
@@ -0,0 +1,689 @@
|
||||
# ExternalService 三层架构设计详解
|
||||
|
||||
## 概述
|
||||
|
||||
ExternalService 架构采用 **JSON 存储 + Schema 验证 + Struct 类型转换** 三层设计,实现了灵活、可扩展的外部服务管理体系。
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ExternalService 三层架构 │
|
||||
│ │
|
||||
│ 第一层:JSON 存储层(Database Layer) │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ external_services.Config (TEXT) │ │
|
||||
│ │ │ │
|
||||
│ │ 优势: │ │
|
||||
│ │ ✅ 一张表容纳所有异构配置 │ │
|
||||
│ │ ✅ 不改表结构,支持无限扩展 │ │
|
||||
│ │ ✅ 向后兼容,旧数据不受影响 │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ 第二层:Schema 验证层(Validation Layer) │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ JSON Schema │ │
|
||||
│ │ │ │
|
||||
│ │ 作用: │ │
|
||||
│ │ ✅ 前端动态表单渲染 │ │
|
||||
│ │ ✅ 输入验证(必填、格式、枚举、正则) │ │
|
||||
│ │ ✅ 前后端统一验证规则 │ │
|
||||
│ │ ✅ 零代码新增服务类型 │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ ↓ │
|
||||
│ 第三层:Struct 类型转换层(Type Safety Layer) │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ Go Struct + ValidateConfig() + BuildConfig() │ │
|
||||
│ │ │ │
|
||||
│ │ 作用: │ │
|
||||
│ │ ✅ 编译期类型检查 │ │
|
||||
│ │ ✅ 业务逻辑验证(比 Schema 更复杂) │ │
|
||||
│ │ ✅ 设置默认值 │ │
|
||||
│ │ ✅ 返回标准接口(TransportConfig 等) │ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第一层:JSON 存储层(Database Layer)
|
||||
|
||||
### 设计思想
|
||||
|
||||
传统的数据库设计要求每个服务类型单独建表:
|
||||
- `stun_servers` 表
|
||||
- `turn_servers` 表
|
||||
- `ddns_configs` 表
|
||||
- ...
|
||||
|
||||
**问题**:
|
||||
1. 表越来越多,难以维护
|
||||
2. 每次新增服务都需要改表结构
|
||||
3. 历史数据迁移困难
|
||||
4. 无法支持动态扩展
|
||||
|
||||
### 解决方案
|
||||
|
||||
使用一个 TEXT 字段存储 JSON:
|
||||
|
||||
```go
|
||||
type ExternalService struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id"`
|
||||
Category string `gorm:"type:varchar(32);not null;index" json:"category"`
|
||||
ServiceType string `gorm:"type:varchar(64);not null;index" json:"serviceType"`
|
||||
Name string `gorm:"type:varchar(64);not null" json:"name"`
|
||||
Config string `gorm:"type:text;not null" json:"config"` // ← JSON 字符串
|
||||
// ... 其他字段
|
||||
}
|
||||
```
|
||||
|
||||
### 示例数据
|
||||
|
||||
**FRP 穿透服务**:
|
||||
```json
|
||||
{
|
||||
"category": "networking",
|
||||
"serviceType": "frp_server",
|
||||
"name": "我的 FRP 服务器",
|
||||
"config": "{\"server_addr\":\"frp.example.com\",\"token\":\"xxx\"}"
|
||||
}
|
||||
```
|
||||
|
||||
**SSL ACME 证书**:
|
||||
```json
|
||||
{
|
||||
"category": "security",
|
||||
"serviceType": "ssl_acme",
|
||||
"name": "Let's Encrypt 证书",
|
||||
"config": "{\"ca_provider\":\"letsencrypt\",\"email\":\"admin@example.com\",\"domains\":[\"*.example.com\"]}"
|
||||
}
|
||||
```
|
||||
|
||||
**TURN 服务器(长期凭证)**:
|
||||
```json
|
||||
{
|
||||
"category": "networking",
|
||||
"serviceType": "turn_server",
|
||||
"name": "Coturn 服务器",
|
||||
"config": "{\"server_addr\":\"turn.example.com\",\"auth_type\":\"long_term\",\"long_term\":{\"username\":\"user\",\"password\":\"***\"}}"
|
||||
}
|
||||
```
|
||||
|
||||
### 优势总结
|
||||
|
||||
| 维度 | 传统方式 | JSON 存储 |
|
||||
|------|---------|-----------|
|
||||
| **表数量** | N 张表(每类服务一张) | 1 张表 |
|
||||
| **扩展性** | ❌ 需要 ALTER TABLE | ✅ 无需改表 |
|
||||
| **数据迁移** | ❌ 复杂且危险 | ✅ 无迁移成本 |
|
||||
| **向后兼容** | ❌ 可能破坏旧数据 | ✅ 完全兼容 |
|
||||
|
||||
---
|
||||
|
||||
## 第二层:Schema 验证层(Validation Layer)
|
||||
|
||||
### 设计思想
|
||||
|
||||
如果只有 JSON 存储,用户可能会乱填数据。如何防止?
|
||||
|
||||
**错误做法**:后端手动校验每个字段
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
if config["server_addr"] == "" {
|
||||
return errors.New("服务器地址不能为空")
|
||||
}
|
||||
if config["token"] == "" {
|
||||
return errors.New("token 不能为空")
|
||||
}
|
||||
// ... 需要写几十个这样的校验
|
||||
```
|
||||
|
||||
**正确做法**:JSON Schema 自动验证
|
||||
|
||||
### JSON Schema 是什么?
|
||||
|
||||
JSON Schema 是一种描述 JSON 数据结构的标准(RFC Draft),用于:
|
||||
1. 验证 JSON 数据格式
|
||||
2. 自动生成文档
|
||||
3. 生成前端表单
|
||||
|
||||
### FRP 服务器配置 Schema
|
||||
|
||||
```go
|
||||
func (p *FRPServerProvider) ConfigSchema() string {
|
||||
return `{
|
||||
"type": "object",
|
||||
"required": ["server_addr", "token"],
|
||||
"properties": {
|
||||
"server_addr": {
|
||||
"type": "string",
|
||||
"title": "FRP 服务器地址",
|
||||
"description": "FRP 服务器的域名或 IP 地址"
|
||||
},
|
||||
"server_port": {
|
||||
"type": "integer",
|
||||
"title": "服务器端口",
|
||||
"default": 7000,
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"title": "认证令牌",
|
||||
"format": "password",
|
||||
"minLength": 8
|
||||
},
|
||||
"protocol": {
|
||||
"type": "string",
|
||||
"enum": ["tcp", "kcp", "quic"],
|
||||
"default": "tcp",
|
||||
"title": "传输协议"
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
```
|
||||
|
||||
### Schema 验证规则说明
|
||||
|
||||
| 关键字 | 作用 | 示例 |
|
||||
|--------|------|------|
|
||||
| `type` | 数据类型 | `"string"`, `"integer"`, `"boolean"`, `"array"`, `"object"` |
|
||||
| `required` | 必填字段 | `["server_addr", "token"]` |
|
||||
| `minimum` / `maximum` | 数值范围 | `minimum: 1, maximum: 65535` |
|
||||
| `minLength` / `maxLength` | 字符串长度 | `minLength: 8` |
|
||||
| `enum` | 枚举值 | `["tcp", "kcp", "quic"]` |
|
||||
| `format` | 特殊格式 | `"email"`, `"uri"`, `"password"` |
|
||||
| `pattern` | 正则表达式 | `"pattern": "^[a-z0-9.-]+$"` |
|
||||
| `default` | 默认值 | `"default": 7000` |
|
||||
|
||||
### 前端动态表单渲染
|
||||
|
||||
**Vue 3 + Element Plus 实现**:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-form :model="formData" label-width="120px">
|
||||
<!-- 根据 Schema 动态渲染字段 -->
|
||||
|
||||
<!-- server_addr: string -->
|
||||
<el-form-item label="服务器地址" required>
|
||||
<el-input v-model="formData.server_addr" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- server_port: integer (带范围) -->
|
||||
<el-form-item label="服务器端口">
|
||||
<el-input-number
|
||||
v-model="formData.server_port"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- token: string (密码格式) -->
|
||||
<el-form-item label="认证令牌" required>
|
||||
<el-input
|
||||
v-model="formData.token"
|
||||
type="password"
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- protocol: enum (下拉框) -->
|
||||
<el-form-item label="传输协议">
|
||||
<el-select v-model="formData.protocol">
|
||||
<el-option label="TCP" value="tcp" />
|
||||
<el-option label="KCP" value="kcp" />
|
||||
<el-option label="QUIC" value="quic" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 自动验证流程
|
||||
|
||||
```javascript
|
||||
// 前端验证(基于 Schema)
|
||||
import Ajv from 'ajv'
|
||||
|
||||
const ajv = new Ajv()
|
||||
const validate = ajv.compile(schema)
|
||||
|
||||
const valid = validate(formData)
|
||||
if (!valid) {
|
||||
console.error(validate.errors)
|
||||
// [
|
||||
// {
|
||||
// instancePath: "/server_addr",
|
||||
// message: "is required"
|
||||
// },
|
||||
// {
|
||||
// instancePath: "/token",
|
||||
// message: "must NOT have fewer than 8 characters"
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
```
|
||||
|
||||
### 优势总结
|
||||
|
||||
| 维度 | 手动验证 | Schema 验证 |
|
||||
|------|---------|-------------|
|
||||
| **代码量** | ❌ 每个服务写几十行验证 | ✅ 声明式定义 |
|
||||
| **一致性** | ❌ 容易遗漏或矛盾 | ✅ 前后端统一 |
|
||||
| **可维护性** | ❌ 分散在各处 | ✅ 集中管理 |
|
||||
| **前端表单** | ❌ 硬编码每个表单 | ✅ 动态渲染 |
|
||||
| **新增服务** | ❌ 前后端都要改 | ✅ 零代码 |
|
||||
|
||||
---
|
||||
|
||||
## 第三层:Struct 类型转换层(Type Safety Layer)
|
||||
|
||||
### 设计思想
|
||||
|
||||
虽然 JSON 很灵活,但 Go 是强类型语言。如何在业务逻辑中安全使用?
|
||||
|
||||
**错误做法**:全程使用 `map[string]interface{}`
|
||||
```go
|
||||
// ❌ 不推荐
|
||||
func UseTURN(config map[string]interface{}) error {
|
||||
addr := config["server_addr"].(string) // 类型断言,可能 panic
|
||||
port := config["server_port"].(int) // 可能是 float64!
|
||||
}
|
||||
```
|
||||
|
||||
**正确做法**:转换为 Go Struct
|
||||
|
||||
### 定义配置结构体
|
||||
|
||||
```go
|
||||
// internal/service_impl/networking/frp_server.go
|
||||
type FRPConfig struct {
|
||||
ServerAddr string `json:"server_addr"`
|
||||
ServerPort int `json:"server_port,omitempty"`
|
||||
Token string `json:"token"`
|
||||
Protocol string `json:"protocol,omitempty"` // tcp/kcp/quic
|
||||
}
|
||||
```
|
||||
|
||||
### ValidateConfig() 业务验证
|
||||
|
||||
```go
|
||||
type FRPServerProvider struct{}
|
||||
|
||||
func (p *FRPServerProvider) ValidateConfig(configJSON string) error {
|
||||
var cfg FRPConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil {
|
||||
return fmt.Errorf("配置解析失败:%w", err)
|
||||
}
|
||||
|
||||
// 业务逻辑校验(比 Schema 更复杂)
|
||||
if cfg.ServerAddr == "" {
|
||||
return errors.New("服务器地址不能为空")
|
||||
}
|
||||
if cfg.Token == "" {
|
||||
return errors.New("认证令牌不能为空")
|
||||
}
|
||||
if len(cfg.Token) < 8 {
|
||||
return errors.New("认证令牌长度至少 8 位")
|
||||
}
|
||||
if cfg.Protocol != "" && !isValidProtocol(cfg.Protocol) {
|
||||
return fmt.Errorf("不支持的协议:%s", cfg.Protocol)
|
||||
}
|
||||
|
||||
// 检查服务器是否可达
|
||||
conn, err := net.DialTimeout("tcp", cfg.ServerAddr+":7000", 5*time.Second)
|
||||
if err != nil {
|
||||
return fmt.Errorf("服务器不可达:%w", err)
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### BuildConfig() 构建对象 + 设置默认值
|
||||
|
||||
```go
|
||||
func (p *FRPServerProvider) BuildConfig(configJSON string) (*FRPConfig, error) {
|
||||
var cfg FRPConfig
|
||||
if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if cfg.ServerPort == 0 {
|
||||
cfg.ServerPort = 7000 // 默认 7000
|
||||
}
|
||||
if cfg.Protocol == "" {
|
||||
cfg.Protocol = "tcp" // 默认 TCP
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 在业务逻辑中使用
|
||||
|
||||
```go
|
||||
// internal/service/network.go
|
||||
func (s *NetworkService) CreateRelay(config *FRPConfig) error {
|
||||
// 现在可以安全地使用强类型
|
||||
fmt.Printf("Connecting to %s:%d\n", config.ServerAddr, config.ServerPort)
|
||||
fmt.Printf("Using protocol: %s\n", config.Protocol)
|
||||
|
||||
// 类型安全,编译器会检查
|
||||
tunnel := frp.NewTunnel(config.ServerAddr, config.ServerPort, config.Protocol)
|
||||
return tunnel.Connect(config.Token)
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂场景:TURN 多种认证方式
|
||||
|
||||
```go
|
||||
type TURNConfig struct {
|
||||
ServerAddr string `json:"server_addr"`
|
||||
Realm string `json:"realm,omitempty"`
|
||||
|
||||
// 认证方式(互斥)
|
||||
AuthType string `json:"auth_type"` // long_term | short_term | auth_secret
|
||||
LongTerm *LongTermAuth `json:"long_term,omitempty"`
|
||||
ShortTerm *ShortTermAuth `json:"short_term,omitempty"`
|
||||
}
|
||||
|
||||
type LongTermAuth struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ShortTermAuth struct {
|
||||
Username string `json:"username"`
|
||||
AuthSecret string `json:"auth_secret"`
|
||||
ExpiresIn int `json:"expires_in,omitempty"`
|
||||
}
|
||||
|
||||
// GetCredentials 动态获取凭证
|
||||
func (p *TURNServerProvider) GetCredentials(configJSON string) (Credentials, error) {
|
||||
var cfg TURNConfig
|
||||
json.Unmarshal([]byte(configJSON), &cfg)
|
||||
|
||||
switch cfg.AuthType {
|
||||
case "long_term":
|
||||
// 返回固定的用户名密码
|
||||
return &LongTermCredentials{
|
||||
Username: cfg.LongTerm.Username,
|
||||
Password: cfg.LongTerm.Password,
|
||||
}, nil
|
||||
|
||||
case "short_term":
|
||||
// 动态生成短期凭证(HMAC-SHA1)
|
||||
now := time.Now()
|
||||
expiry := now.Add(time.Duration(cfg.ShortTerm.ExpiresIn) * time.Second)
|
||||
|
||||
hmac := hmac.New(sha1.New, []byte(cfg.ShortTerm.AuthSecret))
|
||||
hmac.Write([]byte(cfg.ShortTerm.Username))
|
||||
hmac.Write([]byte(now.Format(time.RFC3339)))
|
||||
password := base64.StdEncoding.EncodeToString(hmac.Sum(nil))
|
||||
|
||||
return &ShortTermCredentials{
|
||||
Username: cfg.ShortTerm.Username,
|
||||
Password: password,
|
||||
ExpiresAt: expiry,
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("不支持的认证方式")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 优势总结
|
||||
|
||||
| 维度 | map[string]interface{} | Go Struct |
|
||||
|------|------------------------|-----------|
|
||||
| **类型安全** | ❌ 运行时才能发现错误 | ✅ 编译期检查 |
|
||||
| **IDE 支持** | ❌ 没有自动补全 | ✅ 完整的智能提示 |
|
||||
| **重构友好** | ❌ 容易遗漏 | ✅ 自动更新所有引用 |
|
||||
| **文档化** | ❌ 字段含义不明确 | ✅ 注释即文档 |
|
||||
| **默认值** | ❌ 需要手动处理 | ✅ 统一设置 |
|
||||
|
||||
---
|
||||
|
||||
## 完整使用流程示例
|
||||
|
||||
### 场景:创建 FRP 穿透服务
|
||||
|
||||
#### 步骤 1:用户选择服务类型
|
||||
|
||||
前端 UI:
|
||||
```
|
||||
请选择服务类型:
|
||||
○ STUN 服务器
|
||||
● FRP 穿透服务器
|
||||
○ SSL 证书
|
||||
○ 阿里云 DDNS
|
||||
```
|
||||
|
||||
#### 步骤 2:前端请求 Schema
|
||||
|
||||
```javascript
|
||||
// GET /services/schema/frp_server
|
||||
const response = await fetch('/api/services/schema/frp_server')
|
||||
const schema = await response.json()
|
||||
// schema = {
|
||||
// "type": "object",
|
||||
// "required": ["server_addr", "token"],
|
||||
// "properties": {...}
|
||||
// }
|
||||
```
|
||||
|
||||
#### 步骤 3:前端动态渲染表单
|
||||
|
||||
```vue
|
||||
<DynamicForm :schema="schema" v-model="formData" />
|
||||
```
|
||||
|
||||
渲染结果:
|
||||
```
|
||||
┌─────────────────────────────────┐
|
||||
│ FRP 服务器地址:[____________] │
|
||||
│ 服务器端口: [7000 ] │
|
||||
│ 认证令牌: [••••••••] │
|
||||
│ 传输协议: [TCP ▼ ] │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 步骤 4:用户填写并提交
|
||||
|
||||
```javascript
|
||||
formData = {
|
||||
server_addr: "frp.example.com",
|
||||
server_port: 7000,
|
||||
token: "mytoken123",
|
||||
protocol: "tcp"
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 5:前端 Schema 验证
|
||||
|
||||
```javascript
|
||||
const valid = validate(formData)
|
||||
if (!valid) {
|
||||
showError(validate.errors)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 6:发送到后端
|
||||
|
||||
```javascript
|
||||
POST /api/services
|
||||
{
|
||||
"category": "networking",
|
||||
"serviceType": "frp_server",
|
||||
"name": "我的 FRP 服务器",
|
||||
"config": formData
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 7:后端 ValidateConfig()
|
||||
|
||||
```go
|
||||
provider := registry.Get("frp_server")
|
||||
err := provider.ValidateConfig(configJSON)
|
||||
if err != nil {
|
||||
return err // 返回 400 错误
|
||||
}
|
||||
```
|
||||
|
||||
#### 步骤 8:保存到数据库
|
||||
|
||||
```go
|
||||
service := &ExternalService{
|
||||
Category: "networking",
|
||||
ServiceType: "frp_server",
|
||||
Name: "我的 FRP 服务器",
|
||||
Config: configJSON, // JSON 字符串
|
||||
}
|
||||
db.Create(service)
|
||||
```
|
||||
|
||||
#### 步骤 9:业务逻辑使用
|
||||
|
||||
```go
|
||||
// 后续使用时,通过 BuildConfig() 获取类型安全的对象
|
||||
config, _ := provider.BuildConfig(service.Config)
|
||||
fmt.Printf("FRP Server: %s:%d\n", config.ServerAddr, config.ServerPort)
|
||||
// 输出:FRP Server: frp.example.com:7000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 架构优势对比
|
||||
|
||||
### 新增 FRP 穿透服务
|
||||
|
||||
**传统方式(3 天)**:
|
||||
|
||||
1. 创建 `frp_servers` 表
|
||||
```sql
|
||||
CREATE TABLE frp_servers (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
server_addr VARCHAR(255) NOT NULL,
|
||||
server_port INT DEFAULT 7000,
|
||||
token VARCHAR(255) NOT NULL,
|
||||
protocol VARCHAR(16) DEFAULT 'tcp',
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
2. 编写 CRUD Handler
|
||||
```go
|
||||
type FRPServerHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (h *FRPServerHandler) Create(c *gin.Context) {
|
||||
var req FRPServerRequest
|
||||
c.ShouldBindJSON(&req)
|
||||
|
||||
server := &FRPServer{
|
||||
ServerAddr: req.ServerAddr,
|
||||
// ...
|
||||
}
|
||||
h.db.Create(server)
|
||||
}
|
||||
```
|
||||
|
||||
3. 开发前端管理页面
|
||||
- `FRPList.vue` - 列表页
|
||||
- `FRPCreate.vue` - 创建页
|
||||
- `FRPEdit.vue` - 编辑页
|
||||
|
||||
4. 编写表单验证逻辑
|
||||
```vue
|
||||
const rules = {
|
||||
server_addr: [{ required: true, message: '请输入服务器地址' }],
|
||||
token: [
|
||||
{ required: true, message: '请输入认证令牌' },
|
||||
{ min: 8, message: '长度至少 8 位' }
|
||||
],
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
5. 测试 + 修改 Bug(约半天)
|
||||
|
||||
**总计**:约 15-20 小时
|
||||
|
||||
---
|
||||
|
||||
**三层架构(30 分钟)**:
|
||||
|
||||
1. 实现 `FRPServerProvider`
|
||||
```go
|
||||
type FRPServerProvider struct{}
|
||||
|
||||
func (p *FRPServerProvider) ConfigSchema() string {
|
||||
return `{...}` // JSON Schema
|
||||
}
|
||||
|
||||
func (p *FRPServerProvider) ValidateConfig(configJSON string) error {
|
||||
// 业务验证逻辑
|
||||
}
|
||||
```
|
||||
|
||||
2. 注册到 Registry
|
||||
```go
|
||||
func init() {
|
||||
DefaultRegistry.Register(&FRPServerProvider{})
|
||||
}
|
||||
```
|
||||
|
||||
3. 完成!
|
||||
|
||||
**前端自动适配**:
|
||||
- ✅ 自动获取 Schema
|
||||
- ✅ 自动渲染表单
|
||||
- ✅ 自动验证输入
|
||||
|
||||
**总计**:约 30 分钟
|
||||
|
||||
---
|
||||
|
||||
### 效果对比总结
|
||||
|
||||
| 维度 | 传统方式 | 三层架构 | 提升 |
|
||||
|------|---------|----------|------|
|
||||
| **开发时间** | 3 天 | 30 分钟 | **12 倍** |
|
||||
| **数据库变更** | ✅ 需要 | ❌ 不需要 | - |
|
||||
| **前端开发** | ✅ 需要 | ❌ 自动 | - |
|
||||
| **代码复用** | ❌ 低 | ✅ 高 | - |
|
||||
| **维护成本** | ❌ 高 | ✅ 低 | - |
|
||||
| **扩展难度** | ❌ 困难 | ✅ 简单 | - |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
**三层架构的核心价值**:
|
||||
|
||||
1. **JSON 存储层** → 解决**灵活性**问题
|
||||
- 一张表容纳所有异构配置
|
||||
- 支持无限扩展,不改表结构
|
||||
|
||||
2. **Schema 验证层** → 解决**规范性**问题
|
||||
- 前后端统一验证规则
|
||||
- 动态表单渲染,零代码新增
|
||||
|
||||
3. **Struct 类型转换层** → 解决**安全性**问题
|
||||
- 编译期类型检查
|
||||
- 业务逻辑验证,默认值处理
|
||||
|
||||
**最终效果**:
|
||||
- ✅ **开发效率提升 12 倍**
|
||||
- ✅ **零代码新增服务类型**
|
||||
- ✅ **前后端自动适配**
|
||||
- ✅ **类型安全 + 业务验证**
|
||||
|
||||
这就是为什么我们需要 **JSON 存储 + Schema 验证 + Struct 类型转换** 三层架构!
|
||||
Reference in New Issue
Block a user