完善角色权限与数据隔离

This commit is contained in:
yml2213
2026-07-26 14:00:50 +08:00
parent b483cbc26d
commit 16d9ab0e5e
38 changed files with 2847 additions and 292 deletions
+86 -46
View File
@@ -24,7 +24,7 @@ type Tenant struct {
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
Role string `gorm:"size:20;default:agent" json:"role"`
Role string `gorm:"size:30;default:agent" json:"role"`
Username string `gorm:"size:50;not null;uniqueIndex" json:"username"`
PasswordHash string `gorm:"size:255;not null" json:"-"`
Nickname string `gorm:"size:50" json:"nickname"`
@@ -50,12 +50,12 @@ type Customer struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
Name string `gorm:"size:50;not null" json:"name"`
Phone string `gorm:"size:20" json:"phone"` // 主手机号(兼容列表搜索)
Tel string `gorm:"size:30" json:"tel"` // 联系电话/固话
Phone string `gorm:"size:20" json:"phone"` // 主手机号(兼容列表搜索)
Tel string `gorm:"size:30" json:"tel"` // 联系电话/固话
Email string `gorm:"size:100" json:"email"`
Wechat string `gorm:"size:50" json:"wechat"` // 微信号,默认可空
Wechat string `gorm:"size:50" json:"wechat"` // 微信号,默认可空
QQ string `gorm:"size:20;column:qq" json:"qq"`
Remark string `gorm:"type:text" json:"remark"` // 客户备注
Remark string `gorm:"type:text" json:"remark"` // 客户备注
Tags string `gorm:"type:text" json:"tags"`
Source string `gorm:"size:30" json:"source"`
Status string `gorm:"size:20;default:online" json:"status"`
@@ -95,15 +95,15 @@ type BlacklistEntry struct {
}
type Session struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
ChannelID uint `json:"channel_id"`
CustomerID uint `gorm:"index" json:"customer_id"`
AgentID *uint `gorm:"index" json:"agent_id"`
VisitorTokenHash string `gorm:"size:64;index" json:"-"`
VisitorIP string `gorm:"size:64" json:"visitor_ip"`
VisitorRegion string `gorm:"size:100" json:"visitor_region"`
UserAgent string `gorm:"size:500" json:"user_agent"`
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
ChannelID uint `json:"channel_id"`
CustomerID uint `gorm:"index" json:"customer_id"`
AgentID *uint `gorm:"index" json:"agent_id"`
VisitorTokenHash string `gorm:"size:64;index" json:"-"`
VisitorIP string `gorm:"size:64" json:"visitor_ip"`
VisitorRegion string `gorm:"size:100" json:"visitor_region"`
UserAgent string `gorm:"size:500" json:"user_agent"`
// DeviceKey 访客端持久设备指纹(localStorage),用于设备级拉黑
DeviceKey string `gorm:"size:64;index" json:"device_key"`
// 落地页 / 当前页(访客浏览轨迹)
@@ -115,11 +115,11 @@ type Session struct {
// LastSeenAt 访客最近活跃(心跳/换页),用于在线时长与在线状态
LastSeenAt *time.Time `json:"last_seen_at"`
// DraftText 访客输入框未发送草稿(实时监控用,非聊天消息)
DraftText string `gorm:"type:text" json:"draft_text"`
DraftUpdatedAt *time.Time `json:"draft_updated_at"`
LastReadSeq int `gorm:"default:0" json:"last_read_seq"`
Status string `gorm:"size:20;default:waiting" json:"status"`
Priority string `gorm:"size:20;default:normal" json:"priority"`
DraftText string `gorm:"type:text" json:"draft_text"`
DraftUpdatedAt *time.Time `json:"draft_updated_at"`
LastReadSeq int `gorm:"default:0" json:"last_read_seq"`
Status string `gorm:"size:20;default:waiting" json:"status"`
Priority string `gorm:"size:20;default:normal" json:"priority"`
SatisfactionScore *int `json:"satisfaction_score"`
SatisfactionText string `gorm:"size:500" json:"satisfaction_text"`
EndReason string `gorm:"size:50" json:"end_reason"`
@@ -133,7 +133,7 @@ type CustomerContact struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
CustomerID uint `gorm:"uniqueIndex:idx_cust_contact;not null" json:"customer_id"`
Kind string `gorm:"size:20;uniqueIndex:idx_cust_contact;not null" json:"kind"` // phone|wechat|email|qq
Kind string `gorm:"size:20;uniqueIndex:idx_cust_contact;not null" json:"kind"` // phone|wechat|email|qq
Value string `gorm:"size:100;uniqueIndex:idx_cust_contact;not null" json:"value"`
Source string `gorm:"size:30" json:"source"` // draft|message|leave|agent
CreatedAt time.Time `json:"created_at"`
@@ -165,21 +165,23 @@ type SessionEvent struct {
SessionID uint `gorm:"index;not null" json:"session_id"`
OperatorID uint `json:"operator_id"`
Action string `gorm:"size:50;not null" json:"action"`
Detail string `gorm:"size:500" json:"detail"`
Detail string `gorm:"type:text" json:"detail"`
CreatedAt time.Time `json:"created_at"`
}
type Category struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
ParentID *uint `json:"parent_id"`
Name string `gorm:"size:30;not null" json:"name"`
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
CreatedBy uint `gorm:"index" json:"created_by"`
ParentID *uint `json:"parent_id"`
Name string `gorm:"size:30;not null" json:"name"`
}
type KnowledgeEntry struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
CategoryID uint `json:"category_id"`
CreatedBy uint `gorm:"index" json:"created_by"`
Title string `gorm:"size:100;not null" json:"title"`
Content string `gorm:"type:text;not null" json:"content"`
Status string `gorm:"size:20;default:draft" json:"status"`
@@ -191,16 +193,16 @@ type KnowledgeEntry struct {
// QuickReply 团队/个人快捷回复(与知识库独立)。
// Scope=team 时 OwnerUserID 为空,全租户共享;Scope=personal 时归属 OwnerUserID。
type QuickReply struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
Scope string `gorm:"size:20;index;not null" json:"scope"` // team | personal
OwnerUserID *uint `gorm:"index" json:"owner_user_id,omitempty"`
Title string `gorm:"size:100;not null" json:"title"`
Content string `gorm:"type:text;not null" json:"content"`
Shortcut string `gorm:"size:32;index" json:"shortcut"` // 输入码,如 nh → /nh
GroupName string `gorm:"size:50" json:"group_name"`
Status string `gorm:"size:20;default:draft;index" json:"status"` // draft | published
SortOrder int `gorm:"default:0" json:"sort_order"`
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"index;not null" json:"tenant_id"`
Scope string `gorm:"size:20;index;not null" json:"scope"` // team | personal
OwnerUserID *uint `gorm:"index" json:"owner_user_id,omitempty"`
Title string `gorm:"size:100;not null" json:"title"`
Content string `gorm:"type:text;not null" json:"content"`
Shortcut string `gorm:"size:32;index" json:"shortcut"` // 输入码,如 nh → /nh
GroupName string `gorm:"size:50" json:"group_name"`
Status string `gorm:"size:20;default:draft;index" json:"status"` // draft | published
SortOrder int `gorm:"default:0" json:"sort_order"`
// UsageCount 全局累计(管理页展示);排序以个人用量为准见 QuickReplyUserUsage
UsageCount int `gorm:"default:0" json:"usage_count"`
CreatedAt time.Time `json:"created_at"`
@@ -254,26 +256,64 @@ type Announcement struct {
// TenantSetting 租户级系统设置(欢迎语、工作时间、通知开关、分配策略等)。
type TenantSetting struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"uniqueIndex;not null" json:"tenant_id"`
DisplayName string `gorm:"size:100" json:"display_name"`
AgentNickname string `gorm:"size:50" json:"agent_nickname"`
Timezone string `gorm:"size:50;default:Asia/Shanghai" json:"timezone"`
WelcomeMessage string `gorm:"size:500" json:"welcome_message"`
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"uniqueIndex;not null" json:"tenant_id"`
DisplayName string `gorm:"size:100" json:"display_name"`
AgentNickname string `gorm:"size:50" json:"agent_nickname"`
Timezone string `gorm:"size:50;default:Asia/Shanghai" json:"timezone"`
WelcomeMessage string `gorm:"size:500" json:"welcome_message"`
// WelcomeMessagesJSON 多段欢迎语 JSON[{type:text|image, content:...}, ...]
WelcomeMessagesJSON string `gorm:"type:text" json:"welcome_messages_json"`
OfflinePrompt string `gorm:"size:500" json:"offline_prompt"`
WorkHoursJSON string `gorm:"type:text" json:"work_hours_json"`
WorktimePrompt string `gorm:"size:500" json:"worktime_prompt"`
NotifyNewSession bool `gorm:"default:true" json:"notify_new_session"`
NotifyOfflineLeave bool `gorm:"default:true" json:"notify_offline_leave"`
NotifyDailyReport bool `gorm:"default:false" json:"notify_daily_report"`
WorktimePrompt string `gorm:"size:500" json:"worktime_prompt"`
NotifyNewSession bool `gorm:"default:true" json:"notify_new_session"`
NotifyOfflineLeave bool `gorm:"default:true" json:"notify_offline_leave"`
NotifyDailyReport bool `gorm:"default:false" json:"notify_daily_report"`
// AssignStrategy 自动分配策略:least_load(默认)| round_robin
AssignStrategy string `gorm:"size:30;default:least_load" json:"assign_strategy"`
// MaxActivePerAgent 每位坐席最大进行中会话数;0 表示不限制
MaxActivePerAgent int `gorm:"default:0" json:"max_active_per_agent"`
// RRLastAgentID 轮询策略的上次分配坐席游标
RRLastAgentID *uint `json:"-"`
RRLastAgentID *uint `json:"-"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Role 租户级角色定义。
// Code 为角色标识(admin/supervisor/agent 为内置,其他为自定义)。
// Type="builtin" 的内置角色不可删除、不可修改 Code。
type Role struct {
ID uint `gorm:"primaryKey" json:"id"`
TenantID uint `gorm:"uniqueIndex:idx_tenant_role_code;not null" json:"tenant_id"`
Name string `gorm:"size:50;not null" json:"name"`
Code string `gorm:"size:30;uniqueIndex:idx_tenant_role_code;not null" json:"code"`
Type string `gorm:"size:10;default:custom" json:"type"` // builtin | custom
Desc string `gorm:"size:200" json:"desc"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Permission 系统权限码表(全租户共享,不可删除)。
type Permission struct {
ID uint `gorm:"primaryKey" json:"id"`
Code string `gorm:"size:50;uniqueIndex;not null" json:"code"`
Name string `gorm:"size:50;not null" json:"name"`
Module string `gorm:"size:30;not null" json:"module"`
Category string `gorm:"size:20;not null" json:"category"` // view | operate
SortOrder int `gorm:"default:0" json:"sort_order"`
}
// RolePermission 角色-权限多对多关联。
type RolePermission struct {
RoleID uint `gorm:"primaryKey" json:"role_id"`
PermissionID uint `gorm:"primaryKey" json:"permission_id"`
}
// RoleDataScope 保存角色在业务模块中的数据范围。
// 当前支持 all/selfmodule 维度为后续 group/channel/tag 等范围扩展预留。
type RoleDataScope struct {
RoleID uint `gorm:"primaryKey" json:"role_id"`
Module string `gorm:"size:30;primaryKey" json:"module"`
Scope string `gorm:"size:20;not null" json:"scope"`
}