# 数据结构定义

本文档定义项目管理模块中所有核心数据结构，包括实体、DTO、JSONB 存储结构。

---

## 1. 项目主表实体 (Project)

```go
// Project 项目主表
type Project struct {
    ID                int64           `gorm:"primaryKey;autoIncrement" json:"id"`
    ProjectID         string          `gorm:"type:varchar(64);uniqueIndex;not null" json:"project_id"`
    Name              string          `gorm:"type:varchar(128);not null" json:"name"`
    Namespace         string          `gorm:"type:varchar(64);uniqueIndex;not null" json:"namespace"`

    // 生命周期状态: INIT/DRAFTING/REVIEWING/RELEASED/MODIFYING/ARCHIVED
    LifecycleStatus   string          `gorm:"type:varchar(32);default:'INIT'" json:"lifecycle_status"`
    // 认证状态: draft/pending/official
    CertificationStatus string        `gorm:"type:varchar(32);default:'draft'" json:"certification_status"`

    // 当前正式版本号
    CurrentVersion    int             `gorm:"default:0" json:"current_version"`

    // 主版本数据 (使用JSONB存储，便于版本快照)
    BasicInfo         json.RawMessage `gorm:"type:jsonb" json:"basic_info"`
    DeployBusiness    json.RawMessage `gorm:"type:jsonb" json:"deploy_business"`
    DeployEnv         json.RawMessage `gorm:"type:jsonb" json:"deploy_env"`
    DeployMiddleware  json.RawMessage `gorm:"type:jsonb" json:"deploy_middleware"`

    // 项目填写人
    DetailFillerID    int64           `json:"detail_filler_id"`
    DetailFillerName  string          `gorm:"type:varchar(64)" json:"detail_filler_name"`

    // 审计字段
    CreatedBy         int64           `json:"created_by"`
    CreatedByName     string          `gorm:"type:varchar(64)" json:"created_by_name"`

    common.BaseModel // CreatedAt, UpdatedAt, DeletedAt
}

func (Project) TableName() string {
    return "projects"
}
```

---

## 2. 版本表实体 (ProjectVersion)

```go
// ProjectVersion 项目版本表 (含草稿)
type ProjectVersion struct {
    ID            int64           `gorm:"primaryKey;autoIncrement" json:"id"`
    ProjectID     string          `gorm:"type:varchar(64);index;not null" json:"project_id"`

    // 版本号 (正式版本递增, 草稿为0)
    Version       int             `gorm:"not null;default:0" json:"version"`

    // 版本类型: official/fill_draft/modify_draft
    VersionType   string          `gorm:"type:varchar(32);not null" json:"version_type"`

    // 基准版本号（草稿基于哪个正式版本创建，用于乐观锁冲突检测）
    BaseVersion   int             `gorm:"default:0" json:"base_version"`

    // 草稿所属用户ID (仅草稿类型有值)
    UserID        int64           `gorm:"index" json:"user_id"`
    UserName      string          `gorm:"type:varchar(64)" json:"user_name"`

    // 关联工单ID (1:1关系)
    WorkflowID    string          `gorm:"type:varchar(64);index" json:"workflow_id"`

    // 完整快照数据
    SnapshotData  json.RawMessage `gorm:"type:jsonb" json:"snapshot_data"`

    // 变更信息
    CommitMessage string          `gorm:"type:varchar(255)" json:"commit_message"`
    CommitterID   int64           `json:"committer_id"`
    CommitterName string          `gorm:"type:varchar(64)" json:"committer_name"`

    CreatedAt     time.Time       `json:"created_at"`
    UpdatedAt     time.Time       `json:"updated_at"`
}

func (ProjectVersion) TableName() string {
    return "project_versions"
}
```

---

## 3. 项目工单关联表 (ProjectWorkflow)

```go
// ProjectWorkflow 项目与工单关联表
type ProjectWorkflow struct {
    ID           int64     `gorm:"primaryKey;autoIncrement" json:"id"`
    ProjectID    string    `gorm:"type:varchar(64);index;not null" json:"project_id"`
    WorkflowID   string    `gorm:"type:varchar(64);uniqueIndex;not null" json:"workflow_id"`

    // 工单类型: fill(填写)/modify(修改)
    WorkflowType string    `gorm:"type:varchar(32);not null" json:"workflow_type"`

    // 工单状态 (冗余存储，便于查询)
    Status       string    `gorm:"type:varchar(32)" json:"status"`

    CreatedAt    time.Time `json:"created_at"`
    UpdatedAt    time.Time `json:"updated_at"`
}
```

### 项目与工单关系说明

| 关系类型 | 项目状态 | 约束 |
|:---|:---|:---|
| 项目:填写工单 = 1:1 | INIT/DRAFTING | 项目创建时只能有一个填写工单 |
| 项目:修改工单 = 1:N | RELEASED/MODIFYING | 已发布项目可以有多个修改工单 |
| 用户:修改工单 = 1:1 (per project) | - | 非SuperAdmin用户同一项目只能有一个活跃修改工单 |

---

## 4. 授权配置表 (ProjectAuthConfig)

```go
// ProjectAuthConfig 项目授权配置
type ProjectAuthConfig struct {
    ID            int64     `gorm:"primaryKey;autoIncrement" json:"id"`
    ProjectID     string    `gorm:"type:varchar(64);uniqueIndex;not null" json:"project_id"`

    // 一级授权 (项目管理模块管理)
    TierOneSecret string    `gorm:"type:varchar(128)" json:"tier_one_secret"` // 加密存储
    TimeOffset    int       `gorm:"default:30" json:"time_offset"`            // 允许时间偏移(秒)
    TOTPEnabled   bool      `gorm:"default:false" json:"totp_enabled"`

    // 二级授权 (来自 Watchdog)
    TierTwoSecret string    `gorm:"type:varchar(128)" json:"tier_two_secret"` // 加密存储

    // 授权状态
    AuthType      string    `gorm:"type:varchar(32)" json:"auth_type"`   // permanent/time_limited
    AuthDays      int       `json:"auth_days"`                           // 授权有效期(天)
    AuthorizedAt  time.Time `json:"authorized_at"`
    RevokedAt     time.Time `json:"revoked_at"`
    IsOffline     bool      `gorm:"default:false" json:"is_offline"`     // 是否离线授权

    CreatedAt     time.Time `json:"created_at"`
    UpdatedAt     time.Time `json:"updated_at"`
}
```

---

## 5. JSONB 结构定义

### 5.1 基本信息 (BasicInfo)

```go
type BasicInfo struct {
    Province        string `json:"province"`         // 省份（枚举，参见省市列表）
    City            string `json:"city"`             // 城市（级联选择）
    IndustryContact string `json:"industry_contact"` // 行业组人员姓名
    IndustryPhone   string `json:"industry_phone"`   // 行业组人员电话
    ProjectNature   string `json:"project_nature"`   // 项目性质（枚举）
}
```

**项目性质枚举**:

| 值 | 说明 |
|:---|:---|
| `research` | 科研 |
| `test` | 测试 |
| `trial` | 试用 |
| `market` | 市场化 |
| `sub_platform` | 二级平台 |

### 5.2 部署业务 (DeployBusiness)

```go
type DeployBusiness struct {
    DeployerName    string `json:"deployer_name"`       // 部署人姓名
    DeployerPhone   string `json:"deployer_phone"`      // 部署人电话
    DeployStartTime string `json:"deploy_start_time"`   // 部署开始时间 (YYYY-MM-DD)
    DeployEndTime   string `json:"deploy_end_time"`     // 部署结束时间 (YYYY-MM-DD)
    SystemVersion   string `json:"system_version"`      // 部署系统版本
    SystemType      string `json:"system_type"`         // 系统类型（枚举）
    MainEntrance    string `json:"main_entrance"`       // 业务主要入口URL
    AdminUsername   string `json:"admin_username"`      // 系统超管用户名
    AdminPassword   string `json:"admin_password"`      // 系统超管密码 ⚠️加密存储
}
```

**系统类型枚举**:

| 值 | 说明 |
|:---|:---|
| `business` | 老行业平台 |
| `fly-control` | 新飞控平台 |
| `supervisor` | 监管平台 |

### 5.3 部署环境 (DeployEnv)

```go
type DeployEnv struct {
    // 主机信息列表
    Hosts           []HostInfo `json:"hosts"`

    // 网络环境
    NetworkType     string     `json:"network_type"`     // 网络类型（枚举）
    MainPublicIP    string     `json:"main_public_ip"`   // 主要公网IP
    DomainURL       string     `json:"domain_url"`       // 域名URL
    SSLEnabled      bool       `json:"ssl_enabled"`      // 是否开启SSL

    // 管理方式
    ManagementType  string     `json:"management_type"`  // 管理类型（枚举）
    ManagementURL   string     `json:"management_url"`   // 管理后台URL
    ManagementUser  string     `json:"management_user"`  // 管理后台用户名
    ManagementPwd   string     `json:"management_pwd"`   // 管理后台密码 ⚠️加密存储

    // 统计信息
    HostCount       int        `json:"host_count"`       // 主机台数
    TotalCPU        int        `json:"total_cpu"`        // CPU总核数
    CPUModel        string     `json:"cpu_model"`        // CPU型号
    TotalMemory     int        `json:"total_memory"`     // 内存总大小(GB)
    TotalStorage    int        `json:"total_storage"`    // 存储总大小(GB)
}

type HostInfo struct {
    Hostname        string `json:"hostname"`          // 主机名
    InternalIP      string `json:"internal_ip"`       // 内网IP
    PublicIP        string `json:"public_ip"`         // 公网IP（可选）
    CanAccessPublic bool   `json:"can_access_public"` // 能否访问公网
    SSHPort         int    `json:"ssh_port"`          // SSH端口
    SSHUser         string `json:"ssh_user"`          // SSH用户名
    SSHPwd          string `json:"ssh_pwd"`           // SSH密码 ⚠️加密存储
    Role            string `json:"role"`              // 主机角色（枚举）
}
```

**网络类型枚举**:

| 值 | 说明 |
|:---|:---|
| `internal` | 完全内网 |
| `single_public` | 单主机公网 |
| `full_public` | 全访问公网 |

**管理类型枚举**:

| 值 | 说明 |
|:---|:---|
| `bastion` | 堡垒机 |
| `whitelist` | 白名单 |
| `vpn` | VPN |

**主机角色枚举**:

| 值 | 说明 |
|:---|:---|
| `master` | 主节点 |
| `worker` | 工作节点 |
| `storage` | 存储节点 |

### 5.4 部署中间件 (DeployMiddleware)

```go
type DeployMiddleware struct {
    MySQL        MiddlewareInfo `json:"mysql"`
    Redis        MiddlewareInfo `json:"redis"`
    EMQX         MiddlewareInfo `json:"emqx"`
    MinIO        MiddlewareInfo `json:"minio"`
    InfluxDB     MiddlewareInfo `json:"influxdb"`
    Nacos        MiddlewareInfo `json:"nacos"`
    K8SDashboard MiddlewareInfo `json:"k8s_dashboard"`
}

// MiddlewareInfo 通用中间件信息
type MiddlewareInfo struct {
    PublicIP     string `json:"public_ip"`      // 公网IP
    PublicPort   int    `json:"public_port"`    // 公网端口
    InternalIP   string `json:"internal_ip"`    // 内网IP
    InternalPort int    `json:"internal_port"`  // 内网端口
    K8SAddress   string `json:"k8s_address"`    // K8S集群内访问地址 (Service Name)
    K8SPort      int    `json:"k8s_port"`       // K8S端口
    AdminUser    string `json:"admin_user"`     // 超管用户名
    AdminPwd     string `json:"admin_pwd"`      // 超管密码 ⚠️加密存储
    Version      string `json:"version"`        // 中间件版本
}
```

---

## 6. 版本快照结构 (VersionSnapshot)

```go
// VersionSnapshot 版本快照结构（存储在 project_versions.snapshot_data）
type VersionSnapshot struct {
    BasicInfo        *BasicInfo        `json:"basic_info"`
    DeployBusiness   *DeployBusiness   `json:"deploy_business"`
    DeployEnv        *DeployEnv        `json:"deploy_env"`
    DeployMiddleware *DeployMiddleware `json:"deploy_middleware"`
}
```

---

## 7. 状态常量定义

```go
// 生命周期状态
const (
    LifecycleInit      = "INIT"      // 已创建，等待填写
    LifecycleDrafting  = "DRAFTING"  // 填写中
    LifecycleReviewing = "REVIEWING" // 审核中
    LifecycleReleased  = "RELEASED"  // 已发布
    LifecycleModifying = "MODIFYING" // 变更中
    LifecycleArchived  = "ARCHIVED"  // 已归档
)

// 认证状态
const (
    CertificationDraft    = "draft"    // 草稿
    CertificationPending  = "pending"  // 待审核
    CertificationOfficial = "official" // 正式
)

// 版本类型
const (
    VersionTypeOfficial    = "official"     // 正式版本
    VersionTypeFillDraft   = "fill_draft"   // 填写草稿
    VersionTypeModifyDraft = "modify_draft" // 修改草稿
)

// 工单类型
const (
    WorkflowTypeFill   = "fill"   // 填写工单
    WorkflowTypeModify = "modify" // 修改工单
)
```

---

## 8. 敏感字段加密说明

以下字段必须使用 **AES-256** 加密存储，密钥使用项目的 `TierOneSecret`：

| 结构体 | 字段 | 说明 |
|:---|:---|:---|
| DeployBusiness | `admin_password` | 系统超管密码 |
| DeployEnv | `management_pwd` | 管理后台密码 |
| HostInfo | `ssh_pwd` | SSH密码 |
| MiddlewareInfo | `admin_pwd` | 中间件超管密码 |
| ProjectAuthConfig | `tier_one_secret` | 一级TOTP密钥 |
| ProjectAuthConfig | `tier_two_secret` | 二级TOTP密钥 |

### 加密/解密示例

```go
// 加密敏感字段
func (s *CryptoService) EncryptSensitiveFields(data *DeployBusiness, key []byte) error {
    if data.AdminPassword != "" {
        encrypted, err := s.EncryptAES256(data.AdminPassword, key)
        if err != nil {
            return err
        }
        data.AdminPassword = encrypted
    }
    return nil
}

// 解密敏感字段（返回给前端时脱敏）
func (s *CryptoService) MaskSensitiveFields(data *DeployBusiness) {
    if data.AdminPassword != "" {
        data.AdminPassword = "********" // 脱敏处理
    }
}
```

---

## 9. 字段校验规则

### Namespace 校验 (RFC 1123 DNS 标签规范)

```go
var namespaceRegex = regexp.MustCompile(`^[a-z][a-z0-9.-]{0,251}[a-z0-9]$`)

func ValidateNamespace(namespace string) error {
    if len(namespace) > 253 {
        return errors.New("命名空间长度不能超过253个字符")
    }
    if !namespaceRegex.MatchString(namespace) {
        return errors.New("命名空间只能包含小写字母、数字、'-'和'.'，必须以字母开头，以字母或数字结尾")
    }
    return nil
}
```

### IP 地址校验

```go
func ValidateIP(ip string) error {
    if ip == "" || ip == "无" {
        return nil // 允许空值
    }
    if net.ParseIP(ip) == nil {
        return errors.New("无效的IP地址格式")
    }
    return nil
}
```

### 省市级联校验

```go
// 后端需维护省市对应关系表，校验城市是否属于所选省份
func ValidateProvinceCity(province, city string) error {
    validCities, ok := provinceCityMap[province]
    if !ok {
        return errors.New("无效的省份")
    }
    for _, c := range validCities {
        if c == city {
            return nil
        }
    }
    return errors.New("城市不属于所选省份")
}
```
