feat: 接入商务用户资料和全局统计偏好

This commit is contained in:
Yvan
2026-11-03 17:44:25 +08:00
parent 89cc7761b5
commit ce4b54a2e2
39 changed files with 2562 additions and 17 deletions
+31
View File
@@ -0,0 +1,31 @@
package business
import (
"gorm.io/gorm"
"kra/pkg/database/migration"
)
func Migrations() []migration.Step {
return []migration.Step{{ID: "202611030001_business_preference", Migrate: func(db *gorm.DB) error {
// 旧表沿用;已迁移数据库不重建或重置用户偏好。
if err := migration.CreateMissingTables(db, &preferencePO{}); err != nil {
return err
}
if !db.Migrator().HasTable("sys_authorities") || !db.Migrator().HasTable("casbin_rule") {
return nil
}
return db.Transaction(func(tx *gorm.DB) error {
// 两个接口仅操作当前身份,旧系统对所有已登录用户开放。
for _, method := range []string{"GET", "PUT"} {
if err := tx.Exec(`INSERT INTO casbin_rule (ptype,v0,v1,v2,v3,v4,v5)
SELECT 'p', a.authority_id, ?, ?, '', '', '' FROM sys_authorities a
WHERE a.deleted_at IS NULL AND NOT EXISTS (
SELECT 1 FROM casbin_rule p WHERE p.ptype='p' AND p.v0=a.authority_id AND p.v1=? AND p.v2=?)`,
"/sw/user/preference", method, "/sw/user/preference", method).Error; err != nil {
return err
}
}
return nil
})
}}}
}
+62
View File
@@ -0,0 +1,62 @@
package business
import (
"context"
"errors"
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"kra/internal/biz/business"
)
// Provider 复用 Data 持有的连接,不在仓储中创建数据库客户端。
type Provider interface{ DB() *gorm.DB }
type preferencePO struct {
UserID uint `gorm:"primaryKey;autoIncrement:false"`
IncludeSubPromote *int
IDCardDedup *int `gorm:"column:idcard_dedup"`
UpdateTime time.Time
}
func (preferencePO) TableName() string { return "sys_user_preference" }
type userPreferenceRepo struct{ data Provider }
func NewUserPreferenceRepo(data Provider) business.UserPreferenceRepo {
return &userPreferenceRepo{data: data}
}
func (r *userPreferenceRepo) Find(ctx context.Context, id uint) (*business.UserPreference, error) {
var po preferencePO
err := r.data.DB().WithContext(ctx).First(&po, "user_id = ?", id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
if err != nil {
return nil, business.ErrStorage
}
return &business.UserPreference{
IncludeSubPromote: po.IncludeSubPromote == nil || *po.IncludeSubPromote != 0,
IDCardDedup: po.IDCardDedup == nil || *po.IDCardDedup != 0,
}, nil
}
func (r *userPreferenceRepo) Save(ctx context.Context, id uint, pref business.UserPreference) error {
include, dedup := 0, 0
if pref.IncludeSubPromote {
include = 1
}
if pref.IDCardDedup {
dedup = 1
}
po := preferencePO{UserID: id, IncludeSubPromote: &include, IDCardDedup: &dedup, UpdateTime: time.Now()}
if err := r.data.DB().WithContext(ctx).Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}},
DoUpdates: clause.AssignmentColumns([]string{"include_sub_promote", "idcard_dedup", "update_time"}),
}).Create(&po).Error; err != nil {
return business.ErrStorage
}
return nil
}
+72
View File
@@ -0,0 +1,72 @@
package business
import (
"context"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
"kra/internal/biz/business"
"path/filepath"
"testing"
)
type testData struct{ db *gorm.DB }
func (d testData) DB() *gorm.DB { return d.db }
func openTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(filepath.Join(t.TempDir(), "business.db")), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sqlDB.Close() })
for _, step := range Migrations() {
if err := step.Migrate(db); err != nil {
t.Fatal(err)
}
}
return db
}
func TestPreferencePreservesLegacyValuesAndUpserts(t *testing.T) {
db := openTestDB(t)
r := NewUserPreferenceRepo(testData{db})
ctx := context.Background()
if pref, err := r.Find(ctx, 5); err != nil || pref != nil {
t.Fatalf("缺省记录错误: %+v %v", pref, err)
}
if err := db.Exec("INSERT INTO sys_user_preference(user_id,include_sub_promote,idcard_dedup) VALUES (5,NULL,2),(6,1,1)").Error; err != nil {
t.Fatal(err)
}
pref, err := r.Find(ctx, 5)
if err != nil || !pref.IncludeSubPromote || !pref.IDCardDedup {
t.Fatalf("旧 NULL 和非零值应为开启: %+v %v", pref, err)
}
for i := 0; i < 2; i++ {
if err := r.Save(ctx, 5, business.UserPreference{}); err != nil {
t.Fatal(err)
}
}
for _, step := range Migrations() {
if err := step.Migrate(db); err != nil {
t.Fatal(err)
}
}
pref, err = r.Find(ctx, 5)
if err != nil || pref.IncludeSubPromote || pref.IDCardDedup {
t.Fatalf("重复迁移不能重置偏好: %+v %v", pref, err)
}
other, err := r.Find(ctx, 6)
if err != nil || !other.IncludeSubPromote || !other.IDCardDedup {
t.Fatalf("影响其他用户: %+v %v", other, err)
}
var count int64
db.Model(&preferencePO{}).Where("user_id = ?", 5).Count(&count)
if count != 1 {
t.Fatalf("重复写入产生多条记录: %d", count)
}
}