128 lines
4.7 KiB
Go
128 lines
4.7 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"hyapp/pkg/xerr"
|
|
userdomain "hyapp/services/user-service/internal/domain/user"
|
|
)
|
|
|
|
// GetUser 从 users 表读取用户主状态;调用方负责按业务时钟触发懒过期。
|
|
func (r *Repository) GetUser(ctx context.Context, userID int64) (userdomain.User, error) {
|
|
user, err := r.queryUser(ctx, r.db, "WHERE user_id = ?", userID)
|
|
if err == sql.ErrNoRows {
|
|
return userdomain.User{}, xerr.New(xerr.NotFound, "user not found")
|
|
}
|
|
if err != nil {
|
|
return userdomain.User{}, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
// BatchGetUsers 批量读取用户主状态,缺失用户不会出现在返回 map 中。
|
|
func (r *Repository) BatchGetUsers(ctx context.Context, userIDs []int64) (map[int64]userdomain.User, error) {
|
|
placeholders := make([]string, 0, len(userIDs))
|
|
args := make([]any, 0, len(userIDs))
|
|
for _, userID := range userIDs {
|
|
placeholders = append(placeholders, "?")
|
|
args = append(args, userID)
|
|
}
|
|
|
|
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(`
|
|
SELECT user_id, default_display_user_id, current_display_user_id, current_display_user_id_kind, COALESCE(current_display_user_id_expires_at_ms, 0), status, created_at_ms, updated_at_ms
|
|
FROM users
|
|
WHERE user_id IN (%s)
|
|
`, strings.Join(placeholders, ",")), args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
result := make(map[int64]userdomain.User, len(userIDs))
|
|
for rows.Next() {
|
|
user, err := scanUser(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[user.UserID] = user
|
|
}
|
|
|
|
return result, rows.Err()
|
|
}
|
|
|
|
// CreateUserWithIdentity 是用户创建的事务入口。
|
|
// 它同时写 users 和默认短号 active 记录,避免出现没有默认门牌号的用户。
|
|
func (r *Repository) CreateUserWithIdentity(ctx context.Context, user userdomain.User, identity userdomain.Identity) error {
|
|
tx, err := r.db.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if err := insertUserIdentity(ctx, tx, user, identity); err != nil {
|
|
return mapDuplicateError(err)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// queryUser 读取 users 快照字段;既服务普通查询,也服务 identity 事务中的 FOR UPDATE 读。
|
|
func (r *Repository) queryUser(ctx context.Context, q queryer, clause string, args ...any) (userdomain.User, error) {
|
|
row := q.QueryRowContext(ctx, `
|
|
SELECT user_id, default_display_user_id, current_display_user_id, current_display_user_id_kind, COALESCE(current_display_user_id_expires_at_ms, 0), status, created_at_ms, updated_at_ms
|
|
FROM users
|
|
`+clause, args...)
|
|
|
|
return scanUser(row)
|
|
}
|
|
|
|
// scanUser 固定 users 投影到领域模型的转换,并补齐迁移窗口中的默认值。
|
|
func scanUser(scanner interface {
|
|
Scan(dest ...any) error
|
|
}) (userdomain.User, error) {
|
|
var user userdomain.User
|
|
var status string
|
|
var kind string
|
|
err := scanner.Scan(&user.UserID, &user.DefaultDisplayUserID, &user.CurrentDisplayUserID, &kind, &user.CurrentDisplayUserIDExpiresAtMs, &status, &user.CreatedAtMs, &user.UpdatedAtMs)
|
|
if err != nil {
|
|
return userdomain.User{}, err
|
|
}
|
|
|
|
user.Status = userdomain.Status(status)
|
|
user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKind(kind)
|
|
return user.NormalizeDisplayUserIDState(), nil
|
|
}
|
|
|
|
// insertUserIdentity 是跨 auth/user 创建用户用例共享的事务片段。
|
|
// 它只在调用方已经开启事务后执行,避免 service 层手动拼跨表事务。
|
|
func insertUserIdentity(ctx context.Context, tx *sql.Tx, user userdomain.User, identity userdomain.Identity) error {
|
|
user = user.NormalizeDisplayUserIDState()
|
|
if user.DefaultDisplayUserID == "" {
|
|
user.DefaultDisplayUserID = identity.DisplayUserID
|
|
}
|
|
if user.CurrentDisplayUserID == "" {
|
|
user.CurrentDisplayUserID = identity.DisplayUserID
|
|
}
|
|
if user.CurrentDisplayUserIDKind == "" {
|
|
user.CurrentDisplayUserIDKind = userdomain.DisplayUserIDKindDefault
|
|
}
|
|
|
|
_, err := tx.ExecContext(ctx, `
|
|
INSERT INTO users (user_id, default_display_user_id, current_display_user_id, current_display_user_id_kind, current_display_user_id_expires_at_ms, status, created_at_ms, updated_at_ms)
|
|
VALUES (?, ?, ?, ?, NULL, ?, ?, ?)
|
|
`, user.UserID, user.DefaultDisplayUserID, user.CurrentDisplayUserID, string(user.CurrentDisplayUserIDKind), string(user.Status), user.CreatedAtMs, user.UpdatedAtMs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
INSERT INTO user_display_user_ids (display_user_id, user_id, display_user_id_kind, status, assigned_at_ms, activated_at_ms, created_at_ms, updated_at_ms)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`, identity.DisplayUserID, user.UserID, string(userdomain.DisplayUserIDKindDefault), string(userdomain.DisplayUserIDStatusActive), user.CreatedAtMs, user.CreatedAtMs, user.CreatedAtMs, user.UpdatedAtMs)
|
|
return err
|
|
}
|