mirror of
https://github.com/go-admin-team/go-admin.git
synced 2026-09-20 17:57:54 +00:00
sys_user.username, sys_role.role_key and sys_dict_type.dict_type had no unique index. Uniqueness was a SELECT COUNT followed by an INSERT, which two concurrent requests both pass — and login resolves a username with First, so which of the two accounts answers is whichever the database returns. The index cannot be on the key alone, because a soft-deleted row keeps occupying the name and a deleted user's username could never be used again. It has to include the delete marker, and the marker has to be non-null: two live rows are (alice, NULL) and (alice, NULL), and NULL is not equal to NULL, so an index over a nullable marker admits both. That is the worst of the three states — a constraint that reads as protection and binds nothing — and there is a test that demonstrates it rather than asserting it. ModelTime.DeletedAt is milliseconds since the epoch now, zero while the row is live. Sixteen tables carry it; the migration converts each one, preserving when each deleted row was deleted, then adds the three indexes. Written to be re-runnable rather than transactional, because DDL does not roll back on MySQL and an operator whose first attempt failed halfway should have nothing to do but run it again. It refuses before altering anything if a table already holds duplicates, naming them, rather than letting the index fail and leaving the operator to guess. The timestamp conversion happens in Go: turning a timestamp into epoch milliseconds is spelled differently by every dialect this supports, and these row counts do not justify four versions of it.
42 lines
1.2 KiB
Go
42 lines
1.2 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/plugin/soft_delete"
|
|
)
|
|
|
|
type ControlBy struct {
|
|
CreateBy int `json:"createBy" gorm:"index;comment:创建者"`
|
|
UpdateBy int `json:"updateBy" gorm:"index;comment:更新者"`
|
|
}
|
|
|
|
// SetCreateBy 设置创建人id
|
|
func (e *ControlBy) SetCreateBy(createBy int) {
|
|
e.CreateBy = createBy
|
|
}
|
|
|
|
// SetUpdateBy 设置修改人id
|
|
func (e *ControlBy) SetUpdateBy(updateBy int) {
|
|
e.UpdateBy = updateBy
|
|
}
|
|
|
|
type Model struct {
|
|
Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
|
|
}
|
|
|
|
type ModelTime struct {
|
|
CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"`
|
|
UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"`
|
|
|
|
// DeletedAt is milliseconds since the epoch, zero while the row is live,
|
|
// and never null.
|
|
//
|
|
// A nullable marker cannot take part in a unique index. Two live rows are
|
|
// (name, NULL) and (name, NULL), and NULL is not equal to NULL, so the
|
|
// index permits both — it looks like a constraint and enforces nothing.
|
|
// With zero for live rows the pair collides, while two deletions of the
|
|
// same name differ by their timestamps and both remain.
|
|
DeletedAt soft_delete.DeletedAt `json:"-" gorm:"softDelete:milli;index;comment:删除时间"`
|
|
}
|