# P2B — GoChat 数据库设计文档 > 版本: v1.1 | 更新日期: 2026-07-09 > 参照: Chatwoot db/schema.rb (87表) + M1-M12需求文档 > 技术选型: GORM + PostgreSQL > 状态:设计文档,数据库已实际落地。当前 53 个编号迁移(backend/migrations/),表结构以迁移文件为权威。 --- ## 设计原则 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 ```go 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 ```go 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 ```go 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 🔒 ```go 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 🔒 ```go 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 🔒 ```go 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 🔒 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go // 结构类似,各有渠道特有字段 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 🔒 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go // 简化:仅保留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 ```go 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 ```go 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 ```go 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 ```go 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系统) ```go // 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 ```go 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 ```go 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 ```go 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 🔒 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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) ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 🔒 ```go 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"` } ``` ### related_categories / folders / portals_members ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 🔒 ```go 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 🔒 ```go 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 🔒 ```go 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 ```go 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 ```go 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 ```go 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 🔒 ```go 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独立存储 | 文件直接存本地/S3,Attachment表内嵌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设计)