Files
kra-new/internal/data/business/account.go
T

287 lines
10 KiB
Go

package business
import (
"context"
"errors"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"kra/internal/biz/business"
)
// accountPO 是 sys_users 的商务投影,表结构仍由 system 模块维护。
type accountPO struct {
ID uint `gorm:"primaryKey"`
CreatedAt, UpdatedAt time.Time
DeletedAt gorm.DeletedAt
UUID, Username, Password, NickName, RealName, Phone, Email string
QQNumber string `gorm:"column:qq_number"`
Level int
ParentID uint
IDPath string `gorm:"column:id_path"`
BusinessStatus int
RejectReason string
AuthorityID uint
Enable int
OriginSetting string
PasswordUpdatedAt *time.Time
ParentUserName string `gorm:"->;-:migration"`
}
func (accountPO) TableName() string { return "sys_users" }
func newAccount(a *business.Account) accountPO {
now := time.Now()
enable := 0
if a.Status == 0 {
enable = 1
}
return accountPO{ID: a.ID, UUID: uuid.NewString(), Username: a.Username, NickName: a.NickName, Password: a.Password,
RealName: a.RealName, Phone: a.Phone, Email: a.Email, QQNumber: a.QQNumber, Level: a.Level, ParentID: a.ParentID,
IDPath: a.IDPath, BusinessStatus: a.Status, RejectReason: a.RejectReason, AuthorityID: a.AuthorityID, Enable: enable,
OriginSetting: "{}", PasswordUpdatedAt: &now}
}
func toBizAccount(po accountPO) *business.Account {
return &business.Account{ID: po.ID, Username: po.Username, NickName: po.NickName, RealName: po.RealName, Phone: po.Phone, Email: po.Email,
QQNumber: po.QQNumber, Level: po.Level, ParentID: po.ParentID, IDPath: po.IDPath, Status: po.BusinessStatus, RejectReason: po.RejectReason,
AuthorityID: po.AuthorityID, CreatedAt: po.CreatedAt, UpdatedAt: po.UpdatedAt, ParentUserName: po.ParentUserName}
}
type accountRepo struct{ data Provider }
func NewAccountRepo(data Provider) business.AccountRepo { return &accountRepo{data: data} }
func (r *accountRepo) Find(ctx context.Context, id uint) (*business.Account, error) {
var po accountPO
if err := r.data.DB().WithContext(ctx).First(&po, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, business.ErrAccountNotFound
}
return nil, business.ErrStorage
}
return toBizAccount(po), nil
}
func applyUserScope(db *gorm.DB, scope business.UserScope, columnPrefix string) *gorm.DB {
if scope.All {
return db
}
if scope.Empty || scope.SelfID == 0 {
return db.Where("1=0")
}
id, path, parent := columnPrefix+"id", columnPrefix+"id_path", columnPrefix+"parent_id"
if scope.OnlySelf {
return db.Where(id+" = ?", scope.SelfID)
}
query := "(" + path + " = ? OR " + path + " LIKE ? OR " + parent + " = ?)"
args := []any{scope.DescendantPath, scope.DescendantPath + ",%", scope.SelfID}
if scope.IncludeSelf {
query = "(" + query + " OR " + id + " = ?)"
args = append(args, scope.SelfID)
} else {
db = db.Where(id+" <> ?", scope.SelfID)
}
return db.Where(query, args...)
}
func (r *accountRepo) List(ctx context.Context, scope business.UserScope, options ...business.ListOption) ([]*business.Account, int64, error) {
o := business.NewListOptions(options...)
filter := o.Filter
db := r.data.DB().WithContext(ctx).Model(&accountPO{}).Table("sys_users AS u").Where("u.deleted_at IS NULL")
db = applyUserScope(db, scope, "u.")
if filter.UserID != 0 {
db = db.Where("u.id = ?", filter.UserID)
}
for _, item := range []struct{ column, value string }{{"username", filter.Username}, {"nick_name", filter.NickName}, {"real_name", filter.RealName}, {"phone", filter.Phone}} {
if item.value != "" {
db = db.Where("u."+item.column+" LIKE ?", "%"+item.value+"%")
}
}
if filter.Level != nil {
db = db.Where("u.level = ?", *filter.Level)
}
if filter.Status != nil {
db = db.Where("u.business_status = ?", *filter.Status)
}
if filter.BeginTime != nil && filter.EndTime != nil {
db = db.Where("u.created_at BETWEEN ? AND ?", filter.BeginTime, filter.EndTime)
}
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, business.ErrStorage
}
columns := map[string]string{"userId": "id", "userName": "username", "realName": "real_name", "level": "level", "status": "business_status", "createTime": "created_at"}
column := columns[o.OrderBy]
if column == "" {
column = "id"
}
order := "u." + column
if o.Desc {
order += " DESC"
} else {
order += " ASC"
}
var pos []accountPO
if err := db.Select("u.*, p.username AS parent_user_name").Joins("LEFT JOIN sys_users p ON p.id=u.parent_id AND p.deleted_at IS NULL").Order(order).Order("u.id ASC").Offset(o.Offset).Limit(o.Limit).Find(&pos).Error; err != nil {
return nil, 0, business.ErrStorage
}
rows := make([]*business.Account, 0, len(pos))
for _, po := range pos {
rows = append(rows, toBizAccount(po))
}
return rows, total, nil
}
func checkAccountUnique(tx *gorm.DB, a *business.Account) error {
for _, item := range []struct {
column, value string
err error
}{{"username", a.Username, business.ErrAccountExists}, {"phone", a.Phone, business.ErrPhoneExists}, {"email", a.Email, business.ErrEmailExists}} {
if item.value == "" {
continue
}
var count int64
if err := tx.Model(&accountPO{}).Where(item.column+" = ? AND id <> ?", item.value, a.ID).Count(&count).Error; err != nil {
return business.ErrStorage
}
if count > 0 {
return item.err
}
}
return nil
}
func (r *accountRepo) Create(ctx context.Context, a *business.Account) error {
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := checkAccountUnique(tx, a); err != nil {
return err
}
po := newAccount(a)
if err := tx.Create(&po).Error; err != nil {
return business.ErrStorage
}
if a.HireDate != "" {
if err := tx.Create(&businessMemberProfilePO{UserID: po.ID, HireDate: a.HireDate, Employment: "employed"}).Error; err != nil {
return business.ErrStorage
}
}
if err := tx.Table("sys_user_authority").Create(map[string]any{"sys_user_id": po.ID, "sys_authority_authority_id": po.AuthorityID}).Error; err != nil {
return business.ErrStorage
}
on := 1
if err := tx.Create(&preferencePO{UserID: po.ID, IncludeSubPromote: &on, IDCardDedup: &on, UpdateTime: time.Now()}).Error; err != nil {
return business.ErrStorage
}
a.ID, a.CreatedAt, a.UpdatedAt = po.ID, po.CreatedAt, po.UpdatedAt
return nil
})
return storageError(err)
}
func (r *accountRepo) Update(ctx context.Context, a *business.Account) error {
err := r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := checkAccountUnique(tx, a); err != nil {
return err
}
enable := 0
if a.Status == 0 {
enable = 1
}
result := tx.Model(&accountPO{}).Where("id = ?", a.ID).Updates(map[string]any{
"username": a.Username, "nick_name": a.NickName, "real_name": a.RealName, "phone": a.Phone, "email": a.Email, "qq_number": a.QQNumber,
"business_status": a.Status, "enable": enable, "reject_reason": a.RejectReason,
})
if result.Error != nil {
return business.ErrStorage
}
if result.RowsAffected == 0 {
return business.ErrAccountNotFound
}
return nil
})
return storageError(err)
}
func (r *accountRepo) AuthorityForLevel(ctx context.Context, level int) (uint, error) {
// 原角色 ID 仅用于查迁移映射,不作为 Kra 角色 ID 使用。
legacy := map[int]string{1: "2083497078545207297", 2: "2087435436573081601", 3: "2087361690139152385"}
oldID, ok := legacy[level]
if !ok {
return 0, business.ErrBusinessRole
}
db := r.data.DB().WithContext(ctx)
var id uint
if db.Migrator().HasTable("sw_kra_id_map") {
if err := db.Table("sw_kra_id_map m").Select("m.new_id").Joins("JOIN sys_authorities a ON a.authority_id=m.new_id AND a.deleted_at IS NULL").Where("m.entity = ? AND m.old_id = ?", "role", oldID).Scan(&id).Error; err != nil {
return 0, business.ErrStorage
}
if id != 0 && id != 888 {
return id, nil
}
}
// 新安装可使用对应中文角色;匹配不唯一时停止创建,避免错授权限。
names := map[int]string{1: "一级商务", 2: "二级商务", 3: "三级商务"}
var ids []uint
if err := db.Table("sys_authorities").Where("authority_name = ? AND deleted_at IS NULL AND authority_id <> ?", names[level], 888).Pluck("authority_id", &ids).Error; err != nil {
return 0, business.ErrStorage
}
if len(ids) != 1 {
return 0, business.ErrBusinessRole
}
return ids[0], nil
}
func (r *accountRepo) DirectCounts(ctx context.Context, ids []uint) (map[uint]business.AccountCounts, error) {
result := map[uint]business.AccountCounts{}
if len(ids) == 0 {
return result, nil
}
db := r.data.DB().WithContext(ctx)
for _, id := range ids {
zero := int64(0)
result[id] = business.AccountCounts{PromoteCount: &zero}
}
var binds []struct {
UserID uint
Total int64
}
if err := db.Table("sys_user_bind_platform").Select("user_id, COUNT(*) AS total").Where("user_id IN ? AND status = ?", ids, "0").Group("user_id").Scan(&binds).Error; err != nil {
return nil, business.ErrStorage
}
if len(binds) == 0 {
return result, nil
}
for _, b := range binds {
zero := int64(0)
result[b.UserID] = business.AccountCounts{PromoteCount: &zero, BindCount: b.Total}
}
// 系统数据演练库尚未包含渠道表时,基础资料与已通过绑定数仍可使用。
available, err := businessTableAvailable(db, "tab_promote")
if err != nil {
return nil, business.ErrStorage
}
if !available {
for _, b := range binds {
value := result[b.UserID]
value.PromoteCount = nil
result[b.UserID] = value
}
return result, nil
}
var counts []struct {
UserID uint
Total int64
}
if err := db.Table("sys_user_bind_platform b").Select("b.user_id, COUNT(*) AS total").Joins("JOIN tab_promote p ON p.source_db=b.source_db AND p.busier_id=b.platform_busier_id AND p.promote_level=1").Where("b.user_id IN ? AND b.status = ?", ids, "0").Group("b.user_id").Scan(&counts).Error; err != nil {
return nil, business.ErrStorage
}
for _, c := range counts {
value := result[c.UserID]
count := c.Total
value.PromoteCount = &count
result[c.UserID] = value
}
return result, nil
}