2026-04-29 12:43:05 +08:00

119 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package mysql 实现 user-service 的 MySQL 持久化边界。
package mysql
import (
"context"
"database/sql"
"errors"
"strings"
mysqldriver "github.com/go-sql-driver/mysql"
"hyapp/pkg/xerr"
)
// Repository 是 user-service 的 MySQL 持久化入口。
// 方法按领域拆到 user_repository、identity_repository 和 auth_repository避免一个文件混合多条业务线。
type Repository struct {
// db 是标准库连接池,事务和普通查询都从这里创建。
db *sql.DB
}
// Open 创建 MySQL 连接池并做启动 ping。
func Open(ctx context.Context, dsn string) (*Repository, error) {
if strings.TrimSpace(dsn) == "" {
// 本地和线上运行都必须显式提供 DSN空 DSN 代表应用装配错误。
return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required")
}
// sql.Open 只创建连接池,真实连通性由 PingContext 验证。
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
if err := db.PingContext(ctx); err != nil {
// 启动探测失败时关闭连接池,避免返回半初始化 repository。
_ = db.Close()
return nil, err
}
return &Repository{db: db}, nil
}
// Close 释放 MySQL 连接池。
func (r *Repository) Close() error {
if r == nil || r.db == nil {
// Close 允许重复调用和 nil 调用,方便 App.Close 幂等释放。
return nil
}
return r.db.Close()
}
// Ping 验证 MySQL 当前可用。
func (r *Repository) Ping(ctx context.Context) error {
if r == nil || r.db == nil {
// 没有连接池时明确返回 unavailable。
return xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
return r.db.PingContext(ctx)
}
// mapDuplicateError 把通用 display_user_id 唯一冲突转换成稳定领域 reason。
func mapDuplicateError(err error) error {
var mysqlErr *mysqldriver.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
// 默认短号冲突对 service 层使用稳定领域错误。
return xerr.New(xerr.DisplayUserIDExists, "display_user_id already exists")
}
return err
}
// mapPrettyDuplicateError 把靓号租约相关唯一冲突转换成靓号不可用。
func mapPrettyDuplicateError(err error) error {
var mysqlErr *mysqldriver.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
// 靓号唯一约束冲突和默认短号冲突使用不同 reason。
return xerr.New(xerr.DisplayUserIDPrettyNotAvailable, "pretty display_user_id is not available")
}
return err
}
// nullableString 统一把空字符串写成 SQL NULL避免空值参与唯一性或审计语义。
func nullableString(value string) any {
if strings.TrimSpace(value) == "" {
// 空字符串统一写 NULL避免唯一索引或审计字段产生假值。
return nil
}
return value
}
// nullableOperator 让系统修改短号时可以不携带 operator_user_id。
func nullableOperator(operatorUserID int64) any {
if operatorUserID <= 0 {
// 系统任务或懒过期不一定有操作者。
return nil
}
return operatorUserID
}
// nullableRegionID 统一把无区域归属写成 SQL NULL查询投影再转换回 0。
func nullableRegionID(regionID int64) any {
if regionID <= 0 {
return nil
}
return regionID
}
// queryer 抽象 *sql.DB 和 *sql.Tx 的 QueryRowContext便于读方法复用事务锁定查询。
type queryer interface {
// QueryRowContext 是 *sql.DB 和 *sql.Tx 共同具备的方法。
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}