This commit is contained in:
xinian
2026-04-27 00:51:28 +08:00
parent 6781178f6c
commit f97275cb54
12 changed files with 250 additions and 34 deletions

View File

@@ -1,5 +1,6 @@
-- base_sys_user_role 角色授权去重
-- 保留每组 userId + roleId id 最小的一条删除其余重复记录
-- 只处理未软删除的有效授权删除历史记录不参与去重和唯一约束
-- 保留每组有效 userId + roleId id 最小的一条删除其余重复记录
-- 1. 执行前查看重复数据
SELECT
@@ -9,6 +10,7 @@ SELECT
MIN(id) AS keep_id,
ARRAY_AGG(id ORDER BY id) AS ids
FROM base_sys_user_role
WHERE deleted_at IS NULL
GROUP BY "userId", "roleId"
HAVING COUNT(*) > 1
ORDER BY cnt DESC, "userId", "roleId";
@@ -18,6 +20,8 @@ DELETE FROM base_sys_user_role a
USING base_sys_user_role b
WHERE a."userId" = b."userId"
AND a."roleId" = b."roleId"
AND a.deleted_at IS NULL
AND b.deleted_at IS NULL
AND a.id > b.id;
-- 3. 执行后复查应返回 0
@@ -26,5 +30,16 @@ SELECT
"roleId",
COUNT(*) AS cnt
FROM base_sys_user_role
WHERE deleted_at IS NULL
GROUP BY "userId", "roleId"
HAVING COUNT(*) > 1;
-- 4. 建议约束只限制未软删除授权唯一允许历史软删除授权保留
ALTER TABLE base_sys_user_role
DROP CONSTRAINT IF EXISTS uk_base_sys_user_role_user_role;
DROP INDEX IF EXISTS uk_base_sys_user_role_user_role;
CREATE UNIQUE INDEX IF NOT EXISTS uk_base_sys_user_role_user_role
ON base_sys_user_role ("userId", "roleId")
WHERE deleted_at IS NULL;

View File

@@ -1,53 +1,254 @@
-- 唯一约束修复
-- 规则所有带 deleted_at 的业务唯一约束只约束未软删除记录
-- 玩家+物品+VIP状态 联合唯一
ALTER TABLE player_item
ADD CONSTRAINT uk_player_item_player_item_vip
UNIQUE (player_id, item_id, is_vip);
DROP CONSTRAINT IF EXISTS uk_player_item_player_item_vip;
DROP INDEX IF EXISTS uk_player_item_player_item_vip;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_item_player_item_vip
ON player_item (player_id, item_id, is_vip)
WHERE deleted_at IS NULL;
-- 玩家+挖矿 联合唯一
CREATE UNIQUE INDEX uk_talk_player ON player_talk (talk_id, player_id);
ALTER TABLE player_talk
DROP CONSTRAINT IF EXISTS uk_talk_player;
DROP INDEX IF EXISTS uk_talk_player;
CREATE UNIQUE INDEX IF NOT EXISTS uk_talk_player
ON player_talk (talk_id, player_id)
WHERE deleted_at IS NULL;
-- 玩家+任务 联合唯一
CREATE UNIQUE INDEX uk_player_task ON player_task (player_id, task_id);
ALTER TABLE player_task
DROP CONSTRAINT IF EXISTS uk_player_task;
DROP INDEX IF EXISTS uk_player_task;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_task
ON player_task (player_id, task_id)
WHERE deleted_at IS NULL;
-- 玩家+称号 联合唯一
CREATE UNIQUE INDEX uk_player_title ON player_title (player_id, is_vip) WHERE deleted_at IS NULL;
ALTER TABLE player_title
DROP CONSTRAINT IF EXISTS uk_player_title;
DROP INDEX IF EXISTS uk_player_title;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_title
ON player_title (player_id, is_vip)
WHERE deleted_at IS NULL;
-- 玩家+精灵 联合唯一
CREATE UNIQUE INDEX uk_player_pet ON player_pet (player_id, is_vip, catch_time) WHERE deleted_at IS NULL;
ALTER TABLE player_pet
DROP CONSTRAINT IF EXISTS uk_player_pet;
DROP INDEX IF EXISTS uk_player_pet;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pet
ON player_pet (player_id, is_vip, catch_time)
WHERE deleted_at IS NULL;
-- 玩家+CDK 联合唯一
CREATE UNIQUE INDEX uk_player_cdk_log
ALTER TABLE player_cdk_log
DROP CONSTRAINT IF EXISTS uk_player_cdk_log;
DROP INDEX IF EXISTS uk_player_cdk_log;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_cdk_log
ON player_cdk_log (player_id, code_id, is_vip)
WHERE deleted_at IS NULL;
-- 玩家孵蛋 联合唯一
CREATE UNIQUE INDEX uk_player_egg
ON player_egg (player_id, is_vip)
ALTER TABLE player_egg
DROP CONSTRAINT IF EXISTS uk_player_egg;
DROP INDEX IF EXISTS uk_player_egg;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_egg
ON player_egg (player_id, is_vip)
WHERE deleted_at IS NULL;
---PVP索引
CREATE UNIQUE INDEX uk_player_pvp
-- PVP索引
ALTER TABLE player_pvp
DROP CONSTRAINT IF EXISTS uk_player_pvp;
DROP INDEX IF EXISTS uk_player_pvp;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_pvp
ON player_pvp (player_id, season)
WHERE deleted_at IS NULL;
--签到
CREATE UNIQUE INDEX uk_player_sign_in_log
-- 签到
ALTER TABLE player_sign_in_log
DROP CONSTRAINT IF EXISTS uk_player_sign_in_log;
DROP INDEX IF EXISTS uk_player_sign_in_log;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_sign_in_log
ON player_sign_in_log (player_id, sign_in_id, is_vip)
WHERE deleted_at IS NULL;
--房间索引
CREATE UNIQUE INDEX uk_player_room_house
-- 房间索引
ALTER TABLE player_room_house
DROP CONSTRAINT IF EXISTS uk_player_room_house;
DROP INDEX IF EXISTS uk_player_room_house;
CREATE UNIQUE INDEX IF NOT EXISTS uk_player_room_house
ON player_room_house (player_id, is_vip)
WHERE deleted_at IS NULL;
-- 集市权限角色 联合唯一
-- 先清理历史重复授权保留每组 userId + roleId id 最小的一条
-- 先清理有效重复授权保留每组 userId + roleId id 最小的一条
DELETE FROM base_sys_user_role a
USING base_sys_user_role b
WHERE a."userId" = b."userId"
AND a."roleId" = b."roleId"
AND a.deleted_at IS NULL
AND b.deleted_at IS NULL
AND a.id > b.id;
ALTER TABLE base_sys_user_role
DROP CONSTRAINT IF EXISTS uk_base_sys_user_role_user_role;
DROP INDEX IF EXISTS uk_base_sys_user_role_user_role;
CREATE UNIQUE INDEX IF NOT EXISTS uk_base_sys_user_role_user_role
ON base_sys_user_role ("userId", "roleId");
ON base_sys_user_role ("userId", "roleId")
WHERE deleted_at IS NULL;
-- CDK配置 编号唯一
ALTER TABLE config_gift_cdk
DROP CONSTRAINT IF EXISTS idx_config_gift_cdk_cdk_code;
DROP INDEX IF EXISTS idx_config_gift_cdk_cdk_code;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_gift_cdk_cdk_code
ON config_gift_cdk (cdk_code)
WHERE deleted_at IS NULL;
-- 战斗规则 规则索引唯一
ALTER TABLE config_fight_rule
DROP CONSTRAINT IF EXISTS idx_rule_idx;
DROP INDEX IF EXISTS idx_rule_idx;
CREATE UNIQUE INDEX IF NOT EXISTS idx_rule_idx
ON config_fight_rule (rule_idx)
WHERE deleted_at IS NULL;
-- 天选配置 玩家+精灵唯一
ALTER TABLE config_peak_tianxuan
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_player_pet;
DROP INDEX IF EXISTS idx_peak_tianxuan_player_pet;
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_player_pet
ON config_peak_tianxuan (player_id, pet_id)
WHERE deleted_at IS NULL;
-- 天选投票 周期+玩家唯一
ALTER TABLE config_peak_tianxuan_vote
DROP CONSTRAINT IF EXISTS idx_peak_tianxuan_vote_week_player;
DROP INDEX IF EXISTS idx_peak_tianxuan_vote_week_player;
CREATE UNIQUE INDEX IF NOT EXISTS idx_peak_tianxuan_vote_week_player
ON config_peak_tianxuan_vote (week_index, player_id)
WHERE deleted_at IS NULL;
-- 服务器冠名 同服同属主唯一
ALTER TABLE server_show
DROP CONSTRAINT IF EXISTS idx_server_show_server_owner;
DROP INDEX IF EXISTS idx_server_show_server_owner;
CREATE UNIQUE INDEX IF NOT EXISTS idx_server_show_server_owner
ON server_show (server_id, owner)
WHERE deleted_at IS NULL;
-- 商店 商品ID唯一
ALTER TABLE config_shop
DROP CONSTRAINT IF EXISTS idx_config_shop_product_id;
DROP INDEX IF EXISTS idx_config_shop_product_id;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_shop_product_id
ON config_shop (product_id)
WHERE deleted_at IS NULL;
-- 签到配置 签到类别+阶段唯一
ALTER TABLE config_sign_in
DROP CONSTRAINT IF EXISTS idx_sign_type_stage;
DROP INDEX IF EXISTS idx_sign_type_stage;
CREATE UNIQUE INDEX IF NOT EXISTS idx_sign_type_stage
ON config_sign_in (sign_type, stage_days)
WHERE deleted_at IS NULL;
-- 签到配置 CDK唯一
ALTER TABLE config_sign_in
DROP CONSTRAINT IF EXISTS idx_config_sign_in_cdk_id;
DROP INDEX IF EXISTS idx_config_sign_in_cdk_id;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_sign_in_cdk_id
ON config_sign_in (cdk_id)
WHERE deleted_at IS NULL;
-- SPT配置 任务ID唯一
ALTER TABLE config_spt
DROP CONSTRAINT IF EXISTS idx_config_spt_task_id;
DROP INDEX IF EXISTS idx_config_spt_task_id;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_spt_task_id
ON config_spt (task_id)
WHERE deleted_at IS NULL;
-- 爬塔配置 层级唯一
ALTER TABLE config_tower_1
DROP CONSTRAINT IF EXISTS idx_config_tower_1_tower_level;
DROP INDEX IF EXISTS idx_config_tower_1_tower_level;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_1_tower_level
ON config_tower_1 (tower_level)
WHERE deleted_at IS NULL;
ALTER TABLE config_tower_110
DROP CONSTRAINT IF EXISTS idx_config_tower_110_tower_level;
DROP INDEX IF EXISTS idx_config_tower_110_tower_level;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_110_tower_level
ON config_tower_110 (tower_level)
WHERE deleted_at IS NULL;
ALTER TABLE config_tower_500
DROP CONSTRAINT IF EXISTS idx_config_tower_500_tower_level;
DROP INDEX IF EXISTS idx_config_tower_500_tower_level;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_500_tower_level
ON config_tower_500 (tower_level)
WHERE deleted_at IS NULL;
ALTER TABLE config_tower_600
DROP CONSTRAINT IF EXISTS idx_config_tower_600_tower_level;
DROP INDEX IF EXISTS idx_config_tower_600_tower_level;
CREATE UNIQUE INDEX IF NOT EXISTS idx_config_tower_600_tower_level
ON config_tower_600 (tower_level)
WHERE deleted_at IS NULL;
-- 玩家信息 角色ID唯一
ALTER TABLE player_info
DROP CONSTRAINT IF EXISTS idx_player_info_player_id;
DROP INDEX IF EXISTS idx_player_info_player_id;
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_info_player_id
ON player_info (player_id)
WHERE deleted_at IS NULL;

View File

@@ -15,7 +15,7 @@ type CDKConfig struct {
*cool.Model
// 核心字段
CDKCode string `gorm:"not null;size:16;uniqueIndex;comment:'CDK编号唯一标识用于玩家兑换'" json:"cdk_code" description:"CDK编号"`
CDKCode string `gorm:"not null;size:16;uniqueIndex:idx_config_gift_cdk_cdk_code,where:deleted_at IS NULL;comment:'CDK编号唯一标识用于玩家兑换'" json:"cdk_code" description:"CDK编号"`
Type uint32 `gorm:"column:type;not null;default:0;comment:'CDK类型:0普通奖励,1服务器冠名'" json:"type" description:"CDK类型"`
//cdk可兑换次数where不等于0

View File

@@ -12,7 +12,7 @@ const TableNameFightRule = "config_fight_rule"
type FightRule struct {
*cool.Model // 嵌入基础Model包含主键、创建/更新时间等通用字段)
RuleIdx uint32 `gorm:"not null;uniqueIndex:idx_rule_idx;comment:'规则索引300-316'" json:"rule_idx"`
RuleIdx uint32 `gorm:"not null;uniqueIndex:idx_rule_idx,where:deleted_at IS NULL;comment:'规则索引300-316'" json:"rule_idx"`
Args []int `gorm:"type:jsonb;comment:'规则参数JSON数组对应RuleBase.args'" json:"args"`
Desc string `gorm:"type:varchar(255);default:'';comment:'规则描述'" json:"desc"`
}

View File

@@ -11,9 +11,9 @@ const (
type PeakTianxuan struct {
*BaseConfig
PlayerID uint32 `gorm:"not null;index:idx_peak_tianxuan_player;uniqueIndex:idx_peak_tianxuan_player_pet;comment:'所属玩家ID'" json:"player_id"`
PlayerID uint32 `gorm:"not null;index:idx_peak_tianxuan_player;uniqueIndex:idx_peak_tianxuan_player_pet,where:deleted_at IS NULL;comment:'所属玩家ID'" json:"player_id"`
DisplayOrder uint32 `gorm:"not null;default:0;comment:'展示顺序'" json:"display_order"`
PetID uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_player_pet;comment:'天选精灵ID'" json:"pet_id"`
PetID uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_player_pet,where:deleted_at IS NULL;comment:'天选精灵ID'" json:"pet_id"`
PresetName string `gorm:"type:varchar(64);not null;default:'';comment:'预设显示名'" json:"preset_name"`
Level uint32 `gorm:"not null;default:100;comment:'预设等级'" json:"level"`
Nature uint32 `gorm:"not null;default:0;comment:'预设性格'" json:"nature"`

View File

@@ -11,8 +11,8 @@ const (
type PeakTianxuanVote struct {
*cool.Model
WeekIndex uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_vote_week_player;index:idx_peak_tianxuan_vote_week_pet;comment:'周序号'" json:"week_index"`
PlayerID uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_vote_week_player;comment:'投票玩家ID'" json:"player_id"`
WeekIndex uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_vote_week_player,where:deleted_at IS NULL;index:idx_peak_tianxuan_vote_week_pet;comment:'周序号'" json:"week_index"`
PlayerID uint32 `gorm:"not null;uniqueIndex:idx_peak_tianxuan_vote_week_player,where:deleted_at IS NULL;comment:'投票玩家ID'" json:"player_id"`
PetID uint32 `gorm:"not null;index:idx_peak_tianxuan_vote_week_pet;comment:'投票精灵ID'" json:"pet_id"`
}

View File

@@ -10,9 +10,9 @@ const TableNameServerShow = "server_show"
// ServerShow 绑定服务器展示信息(冠名、属主、到期时间)。
type ServerShow struct {
*cool.Model
ServerID uint32 `gorm:"column:server_id;comment:'服务器ID';index:idx_server_show_server_id;uniqueIndex:idx_server_show_server_owner" json:"server_id"`
ServerID uint32 `gorm:"column:server_id;comment:'服务器ID';index:idx_server_show_server_id;uniqueIndex:idx_server_show_server_owner,where:deleted_at IS NULL" json:"server_id"`
Name string `gorm:"comment:'服务器展示名'" json:"name"`
Owner uint32 `gorm:"comment:'服务器属主';uniqueIndex:idx_server_show_server_owner" json:"owner"`
Owner uint32 `gorm:"comment:'服务器属主';uniqueIndex:idx_server_show_server_owner,where:deleted_at IS NULL" json:"owner"`
ExpireTime time.Time `gorm:"column:expire_time;default:0;comment:'展示到期时间'" json:"expire_time"`
}

View File

@@ -17,7 +17,7 @@ type ShopConfig struct {
ProductType uint32 `gorm:"not null;default:0;comment:'商品类型'" json:"product_type" description:"商品类型"`
//商品ID
ProductID int64 `gorm:"not null;uniqueIndex;comment:'商品ID'" json:"product_id" description:"商品ID"`
ProductID int64 `gorm:"not null;uniqueIndex:idx_config_shop_product_id,where:deleted_at IS NULL;comment:'商品ID'" json:"product_id" description:"商品ID"`
// 价格信息 -1代表不允许购买,0表示不支持购买
SeerdouPrice int32 `gorm:"not null;default:0;comment:'骄阳豆价格'" json:"seerdou_price" description:"骄阳豆价格"`

View File

@@ -12,9 +12,9 @@ const (
// SignIn 签到阶段配置表。
type SignIn struct {
*BaseConfig
SignType uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到类别1-累计 2-连续)'" json:"sign_type"`
StageDays uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage;comment:'签到阶段天数0/1/3/7/14/30'" json:"stage_days"`
CdkID uint32 `gorm:"not null;uniqueIndex;comment:'绑定的CDK配置ID'" json:"cdk_id"`
SignType uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage,where:deleted_at IS NULL;comment:'签到类别1-累计 2-连续)'" json:"sign_type"`
StageDays uint32 `gorm:"not null;default:1;uniqueIndex:idx_sign_type_stage,where:deleted_at IS NULL;comment:'签到阶段天数0/1/3/7/14/30'" json:"stage_days"`
CdkID uint32 `gorm:"not null;uniqueIndex:idx_config_sign_in_cdk_id,where:deleted_at IS NULL;comment:'绑定的CDK配置ID'" json:"cdk_id"`
}
func (*SignIn) TableName() string {

View File

@@ -10,7 +10,7 @@ const (
type SptConfig struct {
*BaseConfig
TaskID uint32 `gorm:"not null;uniqueIndex;comment:'SPT任务ID'" json:"task_id" description:"SPT任务ID"`
TaskID uint32 `gorm:"not null;uniqueIndex:idx_config_spt_task_id,where:deleted_at IS NULL;comment:'SPT任务ID'" json:"task_id" description:"SPT任务ID"`
Title string `gorm:"type:varchar(64);not null;default:'';comment:'SPT名字'" json:"title" description:"SPT名字"`
PetID uint32 `gorm:"not null;default:0;comment:'SPT精灵ID'" json:"pet_id" description:"SPT精灵ID"`
Online int32 `gorm:"not null;default:1;comment:'是否开放(1开放/0未开放)'" json:"online" description:"是否开放"`

View File

@@ -14,7 +14,7 @@ const (
type BaseTowerConfig struct {
*BaseConfig
Name string `gorm:"type:varchar(100);default:'';comment:'name'" json:"name" description:"name"`
TowerLevel uint32 `gorm:"not null;default:0;uniqueIndex;comment:'tower level'" json:"tower_level"`
TowerLevel uint32 `gorm:"not null;default:0;uniqueIndex:,where:deleted_at IS NULL;comment:'tower level'" json:"tower_level"`
BossIds []uint32 `gorm:"type:jsonb;comment:'boss ids'" json:"boss_ids"`
}

View File

@@ -13,7 +13,7 @@ const TableNamePlayerInfo = "player_info"
type Player struct {
*cool.Model
PlayerID uint64 `gorm:"not null;uniqueIndex;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"`
PlayerID uint64 `gorm:"not null;uniqueIndex:idx_player_info_player_id,where:deleted_at IS NULL;index:idx_pet_by_player_id;comment:'所属玩家ID'" json:"player_id"`
LastResetTime *gtime.Time `struc:"skip" json:"last_reset_time"` // 重置时间,比如电池和每日任务
WeekLastResetTime *gtime.Time `struc:"skip" json:"week_last_reset_time"`
Data PlayerInfo `gorm:"type:jsonb;not null;comment:'全部数据'" json:"data"`