Files
gochat/docs/architecture/P2B-数据库设计.md
T
2026-06-04 15:44:48 +08:00

48 KiB
Raw Blame History

P2B — GoChat 数据库设计文档

版本: v1.0 | 作者: CTO | 日期: 2026-05-22 参照: Chatwoot db/schema.rb (87表) + M1-M12需求文档 技术选型: GORM + PostgreSQL


设计原则

  1. 精简合并 — 去除Rails元数据表(schema_migrations, ar_internal_metadata, active_storage系列, action_mailbox系列),Chatwoot87表 → GoChat约70表
  2. Go惯例 — snake_case表名,模型CamelCase,枚举用string+常量而非Rails integer flags
  3. GORM特性 — 利用gorm:"type:jsonb"替代Rails serialized属性,gorm:"index"替代add_index
  4. 时间统一 — time.Time替代Rails datetime/timestamp
  5. 外键声明式 — GORM关联替代Rails references/belongs_to
  6. 企业版标注🔒标记企业版表/字段

M1: 核心基础表

users

type User struct {
    ID                uint      `gorm:"primaryKey"`
    Name              string    `gorm:"size:255;not null"`
    Email             string    `gorm:"size:255;uniqueIndex;not null"`
    PasswordDigest    string    `gorm:"size:255;not null"` // bcrypt
    Provider          string    `gorm:"size:255;default:'email'"` // email/google/saml 🔒
    UID               string    `gorm:"size:255"` // OAuth UID
    AvatarURL         string    `gorm:"size:512"`
    Availability      string    `gorm:"size:50;default:'offline'"` // online/offline/busy
    AutoOffline       bool      `gorm:"default:false"`
    Type              string    `gorm:"size:50;default:'user'"` // user/super_admin
    Locale            string    `gorm:"size:10;default:'en'"`
    DisplayName       string    `gorm:"size:255"`
    PhoneNumber       string    `gorm:"size:50"`
    ConfirmedAt       *time.Time
    LastSignInAt      *time.Time
    LastSignInIP      string    `gorm:"size:255"`
    CurrentSignInIP   string    `gorm:"size:255"`
    SignInCount       int       `gorm:"default:0"`
    TOTPSecret        string    `gorm:"size:255"` // 🔒 MFA
    TOTPEnabled       bool      `gorm:"default:false"` // 🔒 MFA
    CustomAttributes  json.RawMessage `gorm:"type:jsonb"`
    CreatedAt         time.Time
    UpdatedAt         time.Time
    
    AccountUsers  []AccountUser  `gorm:"foreignKey:UserID"`
    AssignedConvs []Conversation `gorm:"foreignKey:AssigneeID"`
}

对比Chatwoot: Devise字段合并(encrypted_password→PasswordDigest, current_sign_in_at→LastSignInAt等);删除Devise冗余字段(reset_password_token/remember_token等);新增TOTP字段直接内嵌而非独立模型

accounts

type Account struct {
    ID               uint      `gorm:"primaryKey"`
    Name             string    `gorm:"size:255;not null"`
    Locale           string    `gorm:"size:10;default:'en'"`
    Domain           string    `gorm:"size:255"`
    SupportEmail     string    `gorm:"size:255"`
    LogoURL          string    `gorm:"size:512"`
    Features         json.RawMessage `gorm:"type:jsonb;default:'{}'"` // Feature flags
    AutoResolveDuration int    `gorm:"default:0"` // 自动关闭天数
    Namespace        string    `gorm:"size:255"`
    BrandingEnabled  bool      `gorm:"default:false"` // 🔒
    BrandingColors   json.RawMessage `gorm:"type:jsonb"` // 🔒
    BrandingLogoURL  string    `gorm:"size:512"` // 🔒
    BrandingPageURL  string    `gorm:"size:512"` // 🔒
    CustomCustomAttributesEnabled bool `gorm:"default:false"` // 🔒
    CreatedAt        time.Time
    UpdatedAt        time.Time
    
    Users        []AccountUser `gorm:"foreignKey:AccountID"`
    Inboxes      []Inbox       `gorm:"foreignKey:AccountID"`
    Teams        []Team        `gorm:"foreignKey:AccountID"`
    Conversations []Conversation `gorm:"foreignKey:AccountID"`
}

对比: features从FlagShihTzu位运算→jsonb KV存储,更直观;branding字段从独立方法→内嵌字段

account_users

type AccountUser struct {
    ID              uint      `gorm:"primaryKey"`
    AccountID       uint      `gorm:"not null;index"`
    UserID          uint      `gorm:"not null;index"`
    Role            string    `gorm:"size:50;not null;default:'agent'"` // agent/administrator 🔒+custom_role_id
    CustomRoleID    uint      `gorm:"index"` // 🔒 企业版自定义角色
    Availability    string    `gorm:"size:50;default:'offline'"` // online/offline/busy
    AutoOffline     bool      `gorm:"default:false"`
    ActiveAt        *time.Time
    InvitedByID     uint      `gorm:"index"`
    CreatedAt       time.Time
    UpdatedAt       time.Time
    
    Account    Account   `gorm:"foreignKey:AccountID"`
    User       User      `gorm:"foreignKey:UserID"`
    CustomRole *CustomRole `gorm:"foreignKey:CustomRoleID"` // 🔒
}

custom_roles 🔒

type CustomRole struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Permissions json.RawMessage `gorm:"type:jsonb;not null"` // 6维度权限矩阵
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account     Account      `gorm:"foreignKey:AccountID"`
    AccountUsers []AccountUser `gorm:"foreignKey:CustomRoleID"`
}

companies 🔒

type Company struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    Name           string    `gorm:"size:255;not null"`
    Description    string    `gorm:"type:text"`
    WebsiteURL     string    `gorm:"size:512"`
    FaviconURL     string    `gorm:"size:512"`
    Domain         string    `gorm:"size:255"`
    LastActivityAt *time.Time
    CustomAttributes json.RawMessage `gorm:"type:jsonb"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
    
    Account  Account   `gorm:"foreignKey:AccountID"`
    Contacts []Contact `gorm:"many2many:company_contacts"` // 🔒
}

agent_capacity_policies 🔒

type AgentCapacityPolicy struct {
    ID              uint      `gorm:"primaryKey"`
    AccountID       uint      `gorm:"not null;index"`
    Name            string    `gorm:"size:255;not null"`
    AssignmentLogic string    `gorm:"size:50"` // round_robin/least_busy
    ExclusionRules  json.RawMessage `gorm:"type:jsonb"`
    CreatedAt       time.Time
    UpdatedAt       time.Time
    
    Account     Account      `gorm:"foreignKey:AccountID"`
    AccountUsers []AccountUser `gorm:"foreignKey:AgentCapacityID"` // 🔒
}

account_saml_settings 🔒

type AccountSamlSettings struct {
    ID                uint      `gorm:"primaryKey"`
    AccountID         uint      `gorm:"uniqueIndex;not null"`
    IdpEntityID       string    `gorm:"size:512"`
    IdpSsoTargetURL   string    `gorm:"size:512"`
    IdpSloTargetURL   string    `gorm:"size:512"`
    IdpCertificate    string    `gorm:"type:text"`
    SpEntityID        string    `gorm:"size:512"`
    SpX509Certificate string    `gorm:"type:text"`
    SpPrivateKey      string    `gorm:"type:text"`
    RoleMappings      json.RawMessage `gorm:"type:jsonb"`
    Active            bool      `gorm:"default:true"`
    CreatedAt         time.Time
    UpdatedAt         time.Time
    
    Account Account `gorm:"foreignKey:AccountID"`
}

M2: Inbox 与渠道表

inboxes

type Inbox struct {
    ID            uint      `gorm:"primaryKey"`
    AccountID     uint      `gorm:"not null;index"`
    Name          string    `gorm:"size:255;not null"`
    ChannelType   string    `gorm:"size:50;not null;index"` // Channel::WebWidget等
    ChannelID     uint      `gorm:"not null;index"` // polymorphic channel关联
    AvatarURL     string    `gorm:"size:512"`
    Greeting      string    `gorm:"type:text"`
    GreetingEnabled bool    `gorm:"default:false"`
    WelcomeMessage string   `gorm:"type:text"`
    WelcomeTitle   string   `gorm:"size:255"`
    CSATEnabled   bool      `gorm:"default:false"`
    CSATMessage   string    `gorm:"type:text"`
    AutoAssignmentEnabled bool `gorm:"default:false"`
    EnableEmailCollect bool  `gorm:"default:true"`
    AgentBotID    *uint     `gorm:"index"`
    WorkingHoursEnabled bool `gorm:"default:false"` // 🔒
    OutOfOfficeMessage  string `gorm:"type:text"`
    LockChatDuringOOO   bool  `gorm:"default:false"` // 🔒
    EmailAddress   string   `gorm:"size:255"`
    EnableAutoTopicCreationOnReopen bool `gorm:"default:false"`
    AllowMessagesAfterResolved bool `gorm:"default:false"`
    CreatedAt     time.Time
    UpdatedAt     time.Time
    
    Account       Account      `gorm:"foreignKey:AccountID"`
    Channel       Channelable  `gorm:"polymorphic:Channel"` // 多态关联
    Members       []InboxMember `gorm:"foreignKey:InboxID"`
    Conversations []Conversation `gorm:"foreignKey:InboxID"`
}

对比: Chatwoot的polymorphic channelable concern → GORM polymorphic关联;lock_chat_after_resolved合并多个标志位

inbox_members

type InboxMember struct {
    ID        uint      `gorm:"primaryKey"`
    InboxID   uint      `gorm:"not null;index"`
    UserID    uint      `gorm:"not null;index"`
    CreatedAt time.Time
    UpdatedAt time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
    User  User  `gorm:"foreignKey:UserID"`
}

agent_bots

type AgentBot struct {
    ID           uint      `gorm:"primaryKey"`
    AccountID    *uint     `gorm:"index"`
    Name         string    `gorm:"size:255;not null"`
    AvatarURL    string    `gorm:"size:512"`
    BotType      string    `gorm:"size:50"` // web_widget/api
    Config       json.RawMessage `gorm:"type:jsonb"`
    CreatedAt    time.Time
    UpdatedAt    time.Time
    
    Inboxes []AgentBotInbox `gorm:"foreignKey:AgentBotID"`
}

agent_bot_inboxes

type AgentBotInbox struct {
    ID         uint      `gorm:"primaryKey"`
    AgentBotID uint      `gorm:"not null;index"`
    InboxID    uint      `gorm:"not null;index"`
    CreatedAt  time.Time
    UpdatedAt  time.Time
}

channel_web_widgets

type ChannelWebWidget struct {
    ID                 uint      `gorm:"primaryKey"`
    InboxID             uint      `gorm:"uniqueIndex;not null"`
    WebsiteToken        string    `gorm:"size:255;uniqueIndex;not null"`
    WebsiteURL          string    `gorm:"size:512"`
    WelcomeHeading      string    `gorm:"size:255"`
    WelcomeTagline      string    `gorm:"type:text"`
    PreChatFormEnabled  bool      `gorm:"default:false"`
    PreChatFormMessage  string    `gorm:"type:text"`
    PreChatFormOptions  json.RawMessage `gorm:"type:jsonb"`
    HMACToken           string    `gorm:"size:255"`
    ReplyTime           string    `gorm:"size:50"` // in_a_few_minutes/in_a_few_hours/in_a_day
    ContinuousPolyfill  bool      `gorm:"default:false"`
    FeatureFlags        json.RawMessage `gorm:"type:jsonb"`
    ReferrerHost        string    `gorm:"size:255"`
    WidgetColor         string    `gorm:"size:50"`
    AvatarURL           string    `gorm:"size:512"`
    CreatedAt           time.Time
    UpdatedAt           time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_telegrams

type ChannelTelegram struct {
    ID          uint      `gorm:"primaryKey"`
    InboxID     uint      `gorm:"uniqueIndex;not null"`
    BotToken    string    `gorm:"size:255;not null"`
    BotName     string    `gorm:"size:255"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_facebook_pages

type ChannelFacebookPage struct {
    ID                 uint      `gorm:"primaryKey"`
    InboxID            uint      `gorm:"uniqueIndex;not null"`
    PageID             string    `gorm:"size:255;not null"`
    UserAccessToken    string    `gorm:"size:512;not null"` // encrypted 🔒
    PageAccessToken    string    `gorm:"size:512;not null"` // encrypted 🔒
    ReauthorizationRequired bool `gorm:"default:false"` // 🔒
    MarkSeen           bool      `gorm:"default:true"`
    CreatedAt          time.Time
    UpdatedAt          time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_whatsapp

type ChannelWhatsapp struct {
    ID               uint      `gorm:"primaryKey"`
    InboxID          uint      `gorm:"uniqueIndex;not null"`
    PhoneNumberID    string    `gorm:"size:255"`
    BusinessID       string    `gorm:"size:255"`
    WhatsAppBusinessAccountID string `gorm:"size:255"`
    Provider          string    `gorm:"size:50;default:'whatsapp_cloud'"` // 360dialog/whatsapp_cloud
    ProviderConfig    json.RawMessage `gorm:"type:jsonb"`
    MessageTemplates  json.RawMessage `gorm:"type:jsonb"`
    CreatedAt         time.Time
    UpdatedAt         time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_email

type ChannelEmail struct {
    ID           uint      `gorm:"primaryKey"`
    InboxID      uint      `gorm:"uniqueIndex;not null"`
    Email        string    `gorm:"size:255;not null"`
    IMAPAddress  string    `gorm:"size:255"`
    IMAPPort     int
    IMAPLogin    string    `gorm:"size:255"`
    IMAPPassword string    `gorm:"size:255"` // encrypted
    IMAPSSL      bool      `gorm:"default:true"`
    SMTPAddress  string    `gorm:"size:255"`
    SMTPPort     int
    SMTPLogin    string    `gorm:"size:255"`
    SMTPPassword string    `gorm:"size:255"` // encrypted
    SMTPSSL      bool      `gorm:"default:true"`
    CreatedAt    time.Time
    UpdatedAt    time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_api

type ChannelAPI struct {
    ID        uint      `gorm:"primaryKey"`
    InboxID   uint      `gorm:"uniqueIndex;not null"`
    CreatedAt time.Time
    UpdatedAt time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_twilio_sms

type ChannelTwilioSMS struct {
    ID            uint      `gorm:"primaryKey"`
    InboxID       uint      `gorm:"uniqueIndex;not null"`
    PhoneNumber   string    `gorm:"size:255;not null"`
    AccountSID    string    `gorm:"size:255;not null"`
    AuthToken     string    `gorm:"size:255;not null"` // encrypted
    Medium        string    `gorm:"size:50;default:'sms'"` // sms/whatsapp
    ContentSID    string    `gorm:"size:255"`
    ContentVariables string `gorm:"type:text"`
    CreatedAt     time.Time
    UpdatedAt     time.Time
    
    Inbox Inbox `gorm:"foreignKey:InboxID"`
}

channel_line / channel_sms / channel_instagram / channel_tiktok

// 结构类似,各有渠道特有字段
type ChannelLine struct {
    ID           uint `gorm:"primaryKey"`
    InboxID      uint `gorm:"uniqueIndex;not null"`
    ChannelID    string `gorm:"size:255;not null"`
    ChannelSecret string `gorm:"size:255;not null"`
    CallbackURL  string `gorm:"size:512"`
    CreatedAt    time.Time; UpdatedAt time.Time
}

type ChannelSms struct {
    ID           uint `gorm:"primaryKey"`
    InboxID      uint `gorm:"uniqueIndex;not null"`
    PhoneNumber  string `gorm:"size:255;not null"`
    Provider     string `gorm:"size:50"` // bandwidth
    ProviderConfig json.RawMessage `gorm:"type:jsonb"`
    CreatedAt    time.Time; UpdatedAt time.Time
}

type ChannelInstagram struct {
    ID                uint `gorm:"primaryKey"`
    InboxID           uint `gorm:"uniqueIndex;not null"`
    IGBusinessAccountID string `gorm:"size:255;not null"`
    AccessToken       string `gorm:"size:512"` // encrypted 🔒
    ReauthorizationRequired bool `gorm:"default:false"` // 🔒
    CreatedAt         time.Time; UpdatedAt time.Time
}

type ChannelTiktok struct {
    ID           uint `gorm:"primaryKey"`
    InboxID      uint `gorm:"uniqueIndex;not null"`
    AccessToken  string `gorm:"size:512"` // encrypted 🔒
    CreatedAt    time.Time; UpdatedAt time.Time
}

inbox_assignment_policies 🔒 / inbox_capacity_limits 🔒

type InboxAssignmentPolicy struct {
    ID          uint `gorm:"primaryKey"`
    AccountID   uint `gorm:"not null;index"`
    InboxID     uint `gorm:"not null;index"`
    PolicyType  string `gorm:"size:50"` // round_robin/least_busy
    Active      bool `gorm:"default:true"`
    CreatedAt   time.Time; UpdatedAt time.Time
}

type InboxCapacityLimit struct {
    ID          uint `gorm:"primaryKey"`
    AccountID   uint `gorm:"not null;index"`
    InboxID     uint `gorm:"not null;index"`
    CapacityLimit int `gorm:"not null"`
    CreatedAt   time.Time; UpdatedAt time.Time
}

working_hours

type WorkingHour struct {
    ID        uint   `gorm:"primaryKey"`
    InboxID   uint   `gorm:"not null;index"`
    DayOfWeek int    `gorm:"not null"` // 0-6
    OpenHour  int    `gorm:"not null"`
    OpenMin   int    `gorm:"not null"`
    CloseHour int    `gorm:"not null"`
    CloseMin  int    `gorm:"not null"`
    ClosedAllDay bool `gorm:"default:false"`
    CreatedAt time.Time; UpdatedAt time.Time
}

M3: 对话与消息表

conversations

type Conversation struct {
    ID              uint      `gorm:"primaryKey"`
    AccountID       uint      `gorm:"not null;index"`
    InboxID         uint      `gorm:"not null;index"`
    AssigneeID      *uint     `gorm:"index"`
    TeamID          *uint     `gorm:"index"`
    ContactID       uint      `gorm:"not null;index"`
    ContactInboxID  uint      `gorm:"not null;index"`
    Status          string    `gorm:"size:50;not null;default:'open';index"` // open/resolved/pending/snoozed
    Priority        *string   `gorm:"size:50"` // urgent/high/medium/low
    UUID            string    `gorm:"size:255;uniqueIndex"`
    LastMessageID   *uint
    LastMessageAt   *time.Time `gorm:"index"`
    LastNonActivityMessageAt *time.Time
    AdditionalAttributes json.RawMessage `gorm:"type:jsonb"`
    CustomAttributes     json.RawMessage `gorm:"type:jsonb"`
    LabelList           []string `gorm:"type:text[];serializer:json"` // PG array
    Muted               bool     `gorm:"default:false"`
    SnoozedUntil        *time.Time
    UnreadCount         int      `gorm:"default:0"`
    FirstReplyCreatedAt *time.Time
    SLAAppliedID        *uint     `gorm:"index"` // 🔒
    CreatedAt           time.Time
    UpdatedAt           time.Time
    
    Account       Account      `gorm:"foreignKey:AccountID"`
    Inbox         Inbox        `gorm:"foreignKey:InboxID"`
    Assignee      *User        `gorm:"foreignKey:AssigneeID"`
    Team          *Team        `gorm:"foreignKey:TeamID"`
    Contact       Contact      `gorm:"foreignKey:ContactID"`
    ContactInbox  ContactInbox `gorm:"foreignKey:ContactInboxID"`
    Messages      []Message    `gorm:"foreignKey:ConversationID"`
    Participants  []ConversationParticipant `gorm:"foreignKey:ConversationID"`
}

messages

type Message struct {
    ID                uint      `gorm:"primaryKey"`
    AccountID         uint      `gorm:"not null;index"`
    InboxID           uint      `gorm:"not null;index"`
    ConversationID    uint      `gorm:"not null;index"`
    MessageType       string    `gorm:"size:50;not null;index"` // incoming/outgoing/template/activity/private
    Content           string    `gorm:"type:text"`
    ContentType       string    `gorm:"size:50;default:'text'"` // text/image/audio/video/file/location/emoji
    SourceID          string    `gorm:"size:255;index"`
    SenderID          *uint     `gorm:"index"`
    SenderType        string    `gorm:"size:50"` // Contact/User/AgentBot
    Private           bool      `gorm:"default:false"`
    Status            string    `gorm:"size:50;default:'sent'"` // sent/delivered/read/failed
    ExternalSourceID  string    `gorm:"size:255"`
    AdditionalAttributes json.RawMessage `gorm:"type:jsonb"`
    CustomAttributes     json.RawMessage `gorm:"type:jsonb"`
    Conversation       Conversation `gorm:"foreignKey:ConversationID"`
    ParentMessageID    *uint     `gorm:"index"` // 回复引用
    Sender             Senderable `gorm:"polymorphic:Sender"`
    Attachments        []Attachment `gorm:"foreignKey:MessageID"`
    RepliedBy          []Message  `gorm:"foreignKey:ParentMessageID"`
    CreatedAt          time.Time
    UpdatedAt          time.Time
}

conversation_participants

type ConversationParticipant struct {
    ID             uint      `gorm:"primaryKey"`
    ConversationID uint      `gorm:"not null;index"`
    UserID         uint      `gorm:"not null;index"`
    LastReadAt     *time.Time
    CreatedAt      time.Time
    UpdatedAt      time.Time
    
    Conversation Conversation `gorm:"foreignKey:ConversationID"`
    User         User         `gorm:"foreignKey:UserID"`
}

attachments

type Attachment struct {
    ID             uint      `gorm:"primaryKey"`
    MessageID      uint      `gorm:"not null;index"`
    FileType       string    `gorm:"size:50"` // image/audio/video/file
    ExternalURL    string    `gorm:"size:512"`
    FileURL        string    `gorm:"size:512"` // 本地存储路径
    FileSize       int64
    ThumbURL       string    `gorm:"size:512"`
    FileName       string    `gorm:"size:255"`
    Width          int
    Height         int
    CreatedAt      time.Time
    
    Message Message `gorm:"foreignKey:MessageID"`
}

对比: Chatwoot用ActiveStorage(独立blob/attachment表)→ GoChat简化为单一Attachment表,FileURL直接指向本地/云存储路径

message_endorsements

// 简化:仅保留copilot建议的认可记录
type MessageEndorsement struct {
    ID        uint      `gorm:"primaryKey"`
    MessageID uint      `gorm:"not null;index"`
    UserID    uint      `gorm:"not null;index"`
    CreatedAt time.Time
}

M4: 联系人管理表

contacts

type Contact struct {
    ID                uint      `gorm:"primaryKey"`
    AccountID         uint      `gorm:"not null;index"`
    Name              string    `gorm:"size:255"`
    Email             string    `gorm:"size:255"`
    PhoneNumber       string    `gorm:"size:50"`
    AvatarURL         string    `gorm:"size:512"`
    SourceID          string    `gorm:"size:255"`
    Source            string    `gorm:"size:50;default:'chatwoot'"` // chatwoot/FB/WA/TG等
    AdditionalAttributes json.RawMessage `gorm:"type:jsonb"`
    CustomAttributes     json.RawMessage `gorm:"type:jsonb"`
    LastActivityAt     *time.Time `gorm:"index"`
    CompanyID          *uint      `gorm:"index"` // 🔒
    LabelList          []string   `gorm:"type:text[];serializer:json"`
    Blocked            bool       `gorm:"default:false"`
    CreatedAt          time.Time
    UpdatedAt          time.Time
    
    Account      Account       `gorm:"foreignKey:AccountID"`
    ContactInboxes []ContactInbox `gorm:"foreignKey:ContactID"`
    Conversations []Conversation `gorm:"foreignKey:ContactID"`
    Company      *Company      `gorm:"foreignKey:CompanyID"` // 🔒
}

contact_inboxes

type ContactInbox struct {
    ID          uint      `gorm:"primaryKey"`
    ContactID   uint      `gorm:"not null;index"`
    InboxID     uint      `gorm:"not null;index"`
    SourceID    string    `gorm:"size:255;index"` // 渠道侧联系人ID
    HmacVerified bool     `gorm:"default:false"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Contact Contact `gorm:"foreignKey:ContactID"`
    Inbox   Inbox   `gorm:"foreignKey:InboxID"`
}

custom_attribute_definitions

type CustomAttributeDefinition struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    AttributeModel string    `gorm:"size:50;not null"` // conversation/contact
    AttributeKey   string    `gorm:"size:255;not null"`
    AttributeName  string    `gorm:"size:255;not null"`
    AttributeType  string    `gorm:"size:50"` // text/number/date/list/checkbox
    AttributeValues json.RawMessage `gorm:"type:jsonb"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
}

custom_filters

type CustomFilter struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    UserID      uint      `gorm:"not null;index"`
    FilterType  string    `gorm:"size:50"` // conversation/contact/inbox/message
    Name        string    `gorm:"size:255;not null"`
    Query       json.RawMessage `gorm:"type:jsonb;not null"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

labels (tag系统)

// GoChat合并 tags + taggings 为 Label + 关联
type Label struct {
    ID        uint      `gorm:"primaryKey"`
    AccountID uint      `gorm:"not null;index"`
    Title     string    `gorm:"size:255;not null"`
    Color     string    `gorm:"size:50"`
    ShowOnSidebar bool  `gorm:"default:false"`
    CreatedAt time.Time
    UpdatedAt time.Time
    
    Conversations []Conversation `gorm:"many2many:conversation_labels"`
    Contacts      []Contact      `gorm:"many2many:contact_labels"`
}

对比: Chatwoot用tags+taggings两表(polymorphic)→ GoChat用Label+many2many关联表(conversation_labels/contact_labels

notes

type Note struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    ContactID   uint      `gorm:"not null;index"`
    UserID      uint      `gorm:"not null;index"`
    Content     string    `gorm:"type:text;not null"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Contact Contact `gorm:"foreignKey:ContactID"`
    User    User    `gorm:"foreignKey:UserID"`
}

M5: 团队与分配表

teams

type Team struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    AllowAutoAssign bool  `gorm:"default:false"`
    AutoAssignmentMethod string `gorm:"size:50"` // round_robin/least_busy 🔒
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account  Account      `gorm:"foreignKey:AccountID"`
    Members  []TeamMember `gorm:"foreignKey:TeamID"`
    Conversations []Conversation `gorm:"foreignKey:TeamID"`
}

team_members

type TeamMember struct {
    ID        uint      `gorm:"primaryKey"`
    TeamID    uint      `gorm:"not null;index"`
    UserID    uint      `gorm:"not null;index"`
    CreatedAt time.Time
    UpdatedAt time.Time
    
    Team Team `gorm:"foreignKey:TeamID"`
    User User `gorm:"foreignKey:UserID"`
}

assignment_policies 🔒

type AssignmentPolicy struct {
    ID            uint      `gorm:"primaryKey"`
    AccountID     uint      `gorm:"not null;index"`
    TeamID        uint      `gorm:"not null;index"`
    PolicyType    string    `gorm:"size:50;not null"` // round_robin/least_busy
    Active        bool      `gorm:"default:true"`
    CreatedAt     time.Time
    UpdatedAt     time.Time
}

M6: 自动化与模板表

automation_rules

type AutomationRule struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    EventType   string    `gorm:"size:50;index"` // message_created/conversation_created/conversation_updated
    Conditions  json.RawMessage `gorm:"type:jsonb;not null"`
    Actions     json.RawMessage `gorm:"type:jsonb;not null"`
    Active      bool      `gorm:"default:true;index"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account Account `gorm:"foreignKey:AccountID"`
}

macros

type Macro struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Actions     json.RawMessage `gorm:"type:jsonb;not null"`
    Visibility  string    `gorm:"size:50;default:'personal'"` // personal/team/global 🔒
    CreatedByID uint      `gorm:"index"`
    UpdatedByID uint      `gorm:"index"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

canned_responses

type CannedResponse struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    ShortCode   string    `gorm:"size:255;not null"`
    Content     string    `gorm:"type:text;not null"`
    CategoryID  *uint     `gorm:"index"` // 🔒
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

M7: 报告与CSAT表

csat_survey_responses

type CsatSurveyResponse struct {
    ID              uint      `gorm:"primaryKey"`
    AccountID       uint      `gorm:"not null;index"`
    ConversationID  uint      `gorm:"not null;index"`
    MessageID       uint      `gorm:"not null;index"`
    ContactID       uint      `gorm:"not null;index"`
    AssigneeID      *uint     `gorm:"index"`
    Rating          int       `gorm:"not null"` // 1-5
    FeedbackMessage string    `gorm:"type:text"`
    CreatedAt       time.Time
    
    Conversation Conversation `gorm:"foreignKey:ConversationID"`
    Message      Message      `gorm:"foreignKey:MessageID"`
}

campaigns

type Campaign struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    InboxID     uint      `gorm:"not null;index"`
    Title       string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    CampaignType string   `gorm:"size:50;not null"` // ongoing/one_off
    TriggerRules json.RawMessage `gorm:"type:jsonb"`
    SenderID    *uint     `gorm:"index"`
    Message     string    `gorm:"type:text"`
    TemplateID  *uint     `gorm:"index"` // WA模板
    ScheduledAt *time.Time `gorm:"index"`
    CompletedAt *time.Time
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account Account `gorm:"foreignKey:AccountID"`
    Inbox   Inbox   `gorm:"foreignKey:InboxID"`
}

reporting_events

type ReportingEvent struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    InboxID     *uint     `gorm:"index"`
    UserID      *uint     `gorm:"index"`
    ConversationID *uint  `gorm:"index"`
    EventType   string    `gorm:"size:50;not null;index"`
    Value       float64
    CreatedAt   time.Time `gorm:"index"`
    
    Account Account `gorm:"foreignKey:AccountID"`
}

reporting_events_rollups

type ReportingEventsRollup struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    InboxID        *uint     `gorm:"index"`
    UserID         *uint     `gorm:"index"`
    RollupType     string    `gorm:"size:50;not null;index"` // daily/weekly/monthly
    Value          float64
    MetricName     string    `gorm:"size:100;not null;index"`
    StartDate      time.Time `gorm:"not null;index"`
    EndDate        time.Time `gorm:"not null;index"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
}

M8: 通知与Webhook表

notifications

type Notification struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    UserID      uint      `gorm:"not null;index"`
    PrimaryActorType string `gorm:"size:50"`
    PrimaryActorID   uint   `gorm:"index"`
    SecondaryActorType string `gorm:"size:50"`
    SecondaryActorID   *uint  `gorm:"index"`
    PushEnabled bool      `gorm:"default:true"`
    EmailEnabled bool     `gorm:"default:true"`
    ReadAt      *time.Time
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account Account `gorm:"foreignKey:AccountID"`
    User    User    `gorm:"foreignKey:UserID"`
}

notification_settings

type NotificationSetting struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    UserID      uint      `gorm:"not null;index"`
    EventType   string    `gorm:"size:100;not null;index"`
    PushEnabled bool      `gorm:"default:false"`
    EmailEnabled bool     `gorm:"default:false"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

notification_subscriptions

type NotificationSubscription struct {
    ID          uint      `gorm:"primaryKey"`
    UserID      uint      `gorm:"not null;index"`
    SubscriptionType string `gorm:"size:50"` // push/email/webpush
    SubscriptionToken string `gorm:"size:512;not null"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

integrations_hooks (Webhook)

type IntegrationsHook struct {
    ID           uint      `gorm:"primaryKey"`
    AccountID    uint      `gorm:"not null;index"`
    InboxID      *uint     `gorm:"index"`
    HookType     string    `gorm:"size:50;not null"` // inbox_outgoing/account_outgoing
    URL          string    `gorm:"size:512;not null"`
    Settings     json.RawMessage `gorm:"type:jsonb"`
    Secret       string    `gorm:"size:255"`
    Status       string    `gorm:"size:50;default:'active'"` // active/disabled
    ProcessID    *uint     `gorm:"index"` // kbase process
    AppID        string    `gorm:"size:255"`
    ReferenceID  string    `gorm:"size:255"`
    AccessToken  string    `gorm:"size:255"`
    CreatedAt    time.Time
    UpdatedAt    time.Time
}

webhooks

type Webhook struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    URL         string    `gorm:"size:512;not null"`
    Events      json.RawMessage `gorm:"type:jsonb"` // 订阅的事件列表
    Secret      string    `gorm:"size:255"`
    Active      bool      `gorm:"default:true"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

platform_apps

type PlatformApp struct {
    ID          uint      `gorm:"primaryKey"`
    Name        string    `gorm:"size:255;not null"`
    Platform    string    `gorm:"size:50"` // facebook/slack/etc
    Config      json.RawMessage `gorm:"type:jsonb"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

platform_app_permissibles

type PlatformAppPermissible struct {
    ID             uint      `gorm:"primaryKey"`
    PlatformAppID  uint      `gorm:"not null;index"`
    PermissibleType string   `gorm:"size:50"` // Account/User/Inbox
    PermissibleID   uint     `gorm:"index"`
    CreatedAt      time.Time
}

platform_banners

type PlatformBanner struct {
    ID          uint      `gorm:"primaryKey"`
    Message     string    `gorm:"type:text;not null"`
    Active      bool      `gorm:"default:true"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

M9: 知识库与帮助中心表

portals

type Portal struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    Name           string    `gorm:"size:255;not null"`
    Slug           string    `gorm:"size:255;uniqueIndex;not null"`
    Description    string    `gorm:"type:text"`
    CustomDomain   string    `gorm:"size:255"`
    LogoURL        string    `gorm:"size:512"`
    HeroIconURL    string    `gorm:"size:512"`
    HomepageLayout string    `gorm:"size:100"`
    Color          string    `gorm:"size:50"`
    Locale         string    `gorm:"size:10;default:'en'"`
    Archived       bool      `gorm:"default:false"`
    AllowedLocales []string  `gorm:"type:text[];serializer:json"`
    Config         json.RawMessage `gorm:"type:jsonb"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
    
    Account   Account         `gorm:"foreignKey:AccountID"`
    Categories []Category     `gorm:"foreignKey:PortalID"`
    Articles   []Article      `gorm:"foreignKey:PortalID"`
}

categories

type Category struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    PortalID    uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Slug        string    `gorm:"size:255;uniqueIndex"`
    Description string    `gorm:"type:text"`
    Position    int       `gorm:"default:0"`
    Locale      string    `gorm:"size:10;default:'en'"`
    ParentID    *uint     `gorm:"index"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Portal     Portal    `gorm:"foreignKey:PortalID"`
    Parent     *Category `gorm:"foreignKey:ParentID"`
    Articles   []Article `gorm:"foreignKey:CategoryID"`
    RelatedCategories []RelatedCategory `gorm:"foreignKey:CategoryID"`
}

articles

type Article struct {
    ID           uint      `gorm:"primaryKey"`
    AccountID    uint      `gorm:"not null;index"`
    PortalID     uint      `gorm:"not null;index"`
    CategoryID   *uint     `gorm:"index"`
    FolderID     *uint     `gorm:"index"`
    Title        string    `gorm:"size:255;not null"`
    Slug         string    `gorm:"size:255;uniqueIndex"`
    Content      string    `gorm:"type:text"`
    Description  string    `gorm:"type:text"`
    Status       string    `gorm:"size:50;default:'draft'"` // draft/published/archived
    AuthorID     *uint     `gorm:"index"`
    Views        int       `gorm:"default:0"`
    Locale       string    `gorm:"size:10;default:'en'"`
    AssociatedArticleID *uint `gorm:"index"` // 关联翻译文章
    CreatedAt    time.Time
    UpdatedAt    time.Time
    
    Portal   Portal    `gorm:"foreignKey:PortalID"`
    Category *Category `gorm:"foreignKey:CategoryID"`
    Folder   *Folder   `gorm:"foreignKey:FolderID"`
    Embedding *ArticleEmbedding `gorm:"foreignKey:ArticleID"`
}

article_embeddings 🔒

type ArticleEmbedding struct {
    ID          uint      `gorm:"primaryKey"`
    ArticleID   uint      `gorm:"uniqueIndex;not null"`
    Embedding   []float64 `gorm:"type:vector(1536)"` // pg_vector
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Article Article `gorm:"foreignKey:ArticleID"`
}
type RelatedCategory struct {
    ID               uint `gorm:"primaryKey"`
    CategoryID       uint `gorm:"not null;index"`
    RelatedCategoryID uint `gorm:"not null;index"`
    CreatedAt        time.Time
}

type Folder struct {
    ID          uint `gorm:"primaryKey"`
    AccountID   uint `gorm:"not null;index"`
    PortalID    uint `gorm:"not null;index"`
    Name        string `gorm:"size:255;not null"`
    Slug        string `gorm:"size:255"`
    CreatedAt   time.Time; UpdatedAt time.Time
}

type PortalMember struct {
    ID        uint `gorm:"primaryKey"`
    PortalID  uint `gorm:"not null;index"`
    UserID    uint `gorm:"not null;index"`
    CreatedAt time.Time
}

M10: Captain AI 与 Copilot 表 🔒

captain_assistants

type CaptainAssistant struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    SystemPrompt string   `gorm:"type:text"`
    ModelConfig json.RawMessage `gorm:"type:jsonb"` // LLM配置(model/temperature等)
    Active      bool      `gorm:"default:true"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
    
    Account    Account          `gorm:"foreignKey:AccountID"`
    Documents  []CaptainDocument `gorm:"foreignKey:AssistantID"`
    Scenarios  []CaptainScenario `gorm:"foreignKey:AssistantID"`
    CaptainInboxes []CaptainInbox `gorm:"foreignKey:AssistantID"`
}

captain_documents

type CaptainDocument struct {
    ID            uint      `gorm:"primaryKey"`
    AssistantID   uint      `gorm:"not null;index"`
    AccountID     uint      `gorm:"not null;index"`
    ExternalLink  string    `gorm:"size:512"`
    Content       string    `gorm:"type:text"`
    Status        string    `gorm:"size:50;default:'in_progress'"` // in_progress/completed/failed
    Embedding     []float64 `gorm:"type:vector(1536)"` // pg_vector
    FAQ           json.RawMessage `gorm:"type:jsonb"` // 自动生成的FAQ
    LastSyncedAt  *time.Time
    CreatedAt     time.Time
    UpdatedAt     time.Time
    
    Assistant CaptainAssistant `gorm:"foreignKey:AssistantID"`
}

captain_scenarios

type CaptainScenario struct {
    ID           uint      `gorm:"primaryKey"`
    AssistantID  uint      `gorm:"not null;index"`
    AccountID    uint      `gorm:"not null;index"`
    Name         string    `gorm:"size:255;not null"`
    Description  string    `gorm:"type:text"`
    TriggerRules json.RawMessage `gorm:"type:jsonb"`
    Active       bool      `gorm:"default:true"`
    CreatedAt    time.Time
    UpdatedAt    time.Time
}

captain_inboxes / captain_assistant_responses / captain_custom_tools

type CaptainInbox struct {
    ID           uint `gorm:"primaryKey"`
    AssistantID  uint `gorm:"not null;index"`
    InboxID      uint `gorm:"not null;index"`
    CreatedAt    time.Time
}

type CaptainAssistantResponse struct {
    ID           uint `gorm:"primaryKey"`
    AssistantID  uint `gorm:"not null;index"`
    ConversationID uint `gorm:"not null;index"`
    Content      string `gorm:"type:text"`
    CreatedAt    time.Time
}

type CaptainCustomTool struct {
    ID          uint `gorm:"primaryKey"`
    AccountID   uint `gorm:"not null;index"`
    Name        string `gorm:"size:255;not null"`
    Description string `gorm:"type:text"`
    Endpoint    string `gorm:"size:512"`
    Method      string `gorm:"size:10"` // GET/POST
    Headers     json.RawMessage `gorm:"type:jsonb"`
    BodyTemplate string `gorm:"type:text"`
    CreatedAt   time.Time; UpdatedAt time.Time
}

copilot_messages / copilot_threads

type CopilotThread struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    UserID         uint      `gorm:"not null;index"`
    ConversationID uint      `gorm:"not null;index"`
    Active         bool      `gorm:"default:true"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
    
    Messages []CopilotMessage `gorm:"foreignKey:ThreadID"`
}

type CopilotMessage struct {
    ID           uint      `gorm:"primaryKey"`
    ThreadID     uint      `gorm:"not null;index"`
    AccountID    uint      `gorm:"not null;index"`
    Role         string    `gorm:"size:50;not null"` // user/assistant/tool
    Content      string    `gorm:"type:text"`
    MessageType  string    `gorm:"size:50"` // reply_suggestion/summarize/rewrite
    CreatedAt    time.Time
    
    Thread CopilotThread `gorm:"foreignKey:ThreadID"`
}

M11: 企业版功能表 🔒

sla_policies / applied_slas / sla_events

type SlaPolicy struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Name        string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    FRT         int       `gorm:"not null"` // First Response Time (minutes)
    NRT         int       `gorm:"not null"` // Next Response Time (minutes)
    RT          int       `gorm:"not null"` // Resolution Time (minutes)
    OnlyBusinessHours bool `gorm:"default:false"`
    Active      bool      `gorm:"default:true"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

type AppliedSla struct {
    ID            uint      `gorm:"primaryKey"`
    AccountID     uint      `gorm:"not null;index"`
    SlaPolicyID   uint      `gorm:"not null;index"`
    ConversationID uint     `gorm:"not null;index"`
    FRTAt         *time.Time
    NRTAt         *time.Time
    RTAt          *time.Time
    SLAStatus     string    `gorm:"size:50;default:'active'"` // active/violated/completed
    CreatedAt     time.Time
    UpdatedAt     time.Time
    
    SlaPolicy   SlaPolicy    `gorm:"foreignKey:SlaPolicyID"`
    Conversation Conversation `gorm:"foreignKey:ConversationID"`
}

type SlaEvent struct {
    ID            uint      `gorm:"primaryKey"`
    AppliedSlaID  uint      `gorm:"not null;index"`
    EventType     string    `gorm:"size:50;not null"` // frt_violated/nrt_violated/rt_violated/applied/replied/resolved
    CreatedAt     time.Time
    
    AppliedSla AppliedSla `gorm:"foreignKey:AppliedSlaID"`
}

audits 🔒

type Audit struct {
    ID          uint      `gorm:"primaryKey"`
    AuditableID   uint     `gorm:"index"`
    AuditableType string   `gorm:"size:100;index"`
    AssociatedID   *uint
    AssociatedType string `gorm:"size:100"`
    UserID      *uint     `gorm:"index"`
    UserType    string    `gorm:"size:100"`
    Action      string    `gorm:"size:50;not null;index"` // create/update/delete
    AuditedChanges json.RawMessage `gorm:"type:jsonb"`
    Comment     string    `gorm:"type:text"`
    RemoteIP    string    `gorm:"size:50"`
    RequestUUID string    `gorm:"size:255"`
    CreatedAt   time.Time
}

对比: Chatwoot用Audited gem → GoChat自实现审计log,存储变更jsonb

calls 🔒

type Call struct {
    ID             uint      `gorm:"primaryKey"`
    AccountID      uint      `gorm:"not null;index"`
    ConversationID uint      `gorm:"not null;index"`
    CallerID       *uint     `gorm:"index"`
    CallType       string    `gorm:"size:50"` // inbound/outbound
    Status         string    `gorm:"size:50;default:'ringing'"` // ringing/ongoing/completed/failed
    Duration       int       // seconds
    CallSID        string    `gorm:"size:255"` // Twilio Call SID
    RecordingURL   string    `gorm:"size:512"`
    CreatedAt      time.Time
    UpdatedAt      time.Time
}

email_templates 🔒

type EmailTemplate struct {
    ID        uint      `gorm:"primaryKey"`
    AccountID uint      `gorm:"not null;index"`
    Name      string    `gorm:"size:255;not null"`
    Subject   string    `gorm:"size:255"`
    Body      string    `gorm:"type:text"`
    Locale    string    `gorm:"size:10;default:'en'"`
    CreatedAt time.Time
    UpdatedAt time.Time
}

M12: 平台与集成表

access_tokens

type AccessToken struct {
    ID          uint      `gorm:"primaryKey"`
    Token       string    `gorm:"size:255;uniqueIndex;not null"`
    OwnerID     uint      `gorm:"index"`
    OwnerType   string    `gorm:"size:100;index"` // User/PlatformApp/AgentBot
    CreatedAt   time.Time
}

data_imports

type DataImport struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    DataType    string    `gorm:"size:50;not null"` // contacts/conversations
    Status      string    `gorm:"size:50;default:'pending'"` // pending/processing/completed/failed
    ProcessingErrors json.RawMessage `gorm:"type:jsonb"`
    TotalRecords int
    ImportedRecords int
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

installation_configs

type InstallationConfig struct {
    ID          uint      `gorm:"primaryKey"`
    Name        string    `gorm:"size:255;uniqueIndex;not null"`
    Value       json.RawMessage `gorm:"type:jsonb;not null"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

对比: Chatwoot用此表存储全局配置(DISPLAY_MANIFEST等)→ GoChat同样用途

dashboard_apps 🔒

type DashboardApp struct {
    ID          uint      `gorm:"primaryKey"`
    AccountID   uint      `gorm:"not null;index"`
    Title       string    `gorm:"size:255;not null"`
    Description string    `gorm:"type:text"`
    URL         string    `gorm:"size:512;not null"`
    IconURL     string    `gorm:"size:512"`
    Position    int       `gorm:"default:0"`
    Active      bool      `gorm:"default:true"`
    CreatedAt   time.Time
    UpdatedAt   time.Time
}

去除的Chatwoot表

原表 原因 GoChat替代
schema_migrations Rails元数据 GORM AutoMigrate
ar_internal_metadata Rails元数据 不需要
active_storage_blobs ActiveStorage独立存储 文件直接存本地/S3Attachment表内嵌FileURL
active_storage_attachments 同上 Attachment表内嵌
active_storage_variant_records 同上 图片缩略图URL
action_mailbox_inbound_emails Rails ActionMailbox Email渠道直接处理
mentions 对话内@提及 Message内容解析提取
leaves Call参与者离开 Call模型内嵌
channel_twitter_profiles Twitter API已关 不实现

表统计

模块 GoChat表数 Chatwoot原表数 变化
M1 核心 7 8+ 合并Devise字段
M2 渠道 17 17 保留
M3 对话 5 7 合并active_storage
M4 联系人 6 8 合并tags+taggings
M5 团队 3 3 保留
M6 自动化 3 3 保留
M7 报告 4 4 保留
M8 通知 8 8 保留
M9 知识库 6 7 合并
M10 AI 8 8 保留
M11 企业 6 6 保留
M12 平台 4 4 保留
合计 ~68 87 -19

🔒 = 企业版功能表 下一步: P2C(路由与API设计)