refactor(auth): modularize auth plugin with physical subpackages and decoupled services

This commit is contained in:
ryan
2026-09-03 09:12:44 +08:00
parent 2124bce7ca
commit 4407589b62
51 changed files with 3859 additions and 2915 deletions
@@ -0,0 +1,61 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package dao provides data access objects and caching for the auth domain plugin.
package dao
import (
"Wavelet/plugins/domain/auth/model/entity"
"context"
)
// ListAllAuthSources 获取全部认证源(含未启用),按 ID 升序
func (d *DAO) ListAllAuthSources(ctx context.Context) ([]entity.AuthSource, error) {
var sources []entity.AuthSource
if err := d.DB(ctx).Order("id ASC").Find(&sources).Error; err != nil {
return nil, err
}
return sources, nil
}
// GetAuthSourceByID 根据 ID 获取认证源
func (d *DAO) GetAuthSourceByID(ctx context.Context, id uint64) (*entity.AuthSource, error) {
var src entity.AuthSource
if err := d.DB(ctx).First(&src, id).Error; err != nil {
return nil, err
}
return &src, nil
}
// GetAuthSourceByName 根据名称获取认证源
func (d *DAO) GetAuthSourceByName(ctx context.Context, name string) (*entity.AuthSource, error) {
var src entity.AuthSource
if err := d.DB(ctx).Where("name = ?", name).First(&src).Error; err != nil {
return nil, err
}
return &src, nil
}
// ListActiveAuthSources 获取所有启用的认证源
func (d *DAO) ListActiveAuthSources(ctx context.Context) ([]entity.AuthSource, error) {
var sources []entity.AuthSource
if err := d.DB(ctx).Where("is_active = ?", true).Order("id ASC").Find(&sources).Error; err != nil {
return nil, err
}
return sources, nil
}
// CreateAuthSource 新建认证源记录
func (d *DAO) CreateAuthSource(ctx context.Context, source *entity.AuthSource) error {
return d.DB(ctx).Create(source).Error
}
// SaveAuthSource 全量保存认证源记录
func (d *DAO) SaveAuthSource(ctx context.Context, source *entity.AuthSource) error {
return d.DB(ctx).Save(source).Error
}
// DeleteAuthSource 删除认证源记录
func (d *DAO) DeleteAuthSource(ctx context.Context, source *entity.AuthSource) error {
return d.DB(ctx).Delete(source).Error
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package dao provides data access objects and caching for the auth domain plugin.
package dao
import (
"Wavelet/core/contracts"
"Wavelet/pkg/cache/ram"
"Wavelet/plugins/domain/auth/consts"
"Wavelet/plugins/domain/auth/model/do"
"context"
"fmt"
)
var (
tokenRAM = ram.MustNew[string, *do.CachedToken](ram.Options{MaximumSize: 2048})
userRAM = ram.MustNew[uint64, *contracts.UserDTO](ram.Options{MaximumSize: 2048})
)
func tokenCacheKey(tokenHash string) string {
return "oauth:token:" + tokenHash
}
func userCacheKey(userID uint64) string {
return fmt.Sprintf("oauth:user:%d", userID)
}
// GetCachedToken 获取缓存的 Token
//
//nolint:dupl // token and user cache lookup pattern
func (d *DAO) GetCachedToken(ctx context.Context, tokenHash string) (*do.CachedToken, error) {
if val, ok := tokenRAM.GetIfPresent(tokenHash); ok {
return val, nil
}
if cache := d.Cache(); cache != nil {
var token do.CachedToken
key := tokenCacheKey(tokenHash)
if err := cache.Get(ctx, key, &token); err == nil {
tokenRAM.Set(tokenHash, &token)
return &token, nil
}
}
return nil, fmt.Errorf("cache miss")
}
// SetCachedToken 设置 Token 缓存
func (d *DAO) SetCachedToken(ctx context.Context, tokenHash string, token *do.CachedToken) {
tokenRAM.Set(tokenHash, token)
if cache := d.Cache(); cache != nil {
key := tokenCacheKey(tokenHash)
_ = cache.Set(ctx, key, token, consts.TokenCacheTTL)
}
}
// InvalidateCachedToken 吊销/删除 token 缓存
func (d *DAO) InvalidateCachedToken(ctx context.Context, tokenHash string) {
tokenRAM.Invalidate(tokenHash)
if cache := d.Cache(); cache != nil {
key := tokenCacheKey(tokenHash)
_ = cache.Delete(ctx, key)
}
}
// GetCachedUser 获取缓存的 UserDTO
//
//nolint:dupl // token and user cache lookup pattern
func (d *DAO) GetCachedUser(ctx context.Context, userID uint64) (*contracts.UserDTO, error) {
if val, ok := userRAM.GetIfPresent(userID); ok {
return val, nil
}
if cache := d.Cache(); cache != nil {
var u contracts.UserDTO
key := userCacheKey(userID)
if err := cache.Get(ctx, key, &u); err == nil {
userRAM.Set(userID, &u)
return &u, nil
}
}
return nil, fmt.Errorf("cache miss")
}
// SetCachedUser 设置 UserDTO 缓存
func (d *DAO) SetCachedUser(ctx context.Context, userID uint64, u *contracts.UserDTO) {
userRAM.Set(userID, u)
if cache := d.Cache(); cache != nil {
key := userCacheKey(userID)
_ = cache.Set(ctx, key, u, consts.UserCacheTTL)
}
}
// InvalidateCachedUser 吊销/失效 UserDTO 缓存
func (d *DAO) InvalidateCachedUser(ctx context.Context, userID uint64) {
userRAM.Invalidate(userID)
if cache := d.Cache(); cache != nil {
key := userCacheKey(userID)
_ = cache.Delete(ctx, key)
}
}
// ResetRAMCacheForTest clears only the process-local RAM cache.
func ResetRAMCacheForTest() {
tokenRAM.InvalidateAll()
userRAM.InvalidateAll()
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package dao provides data access objects and caching for the auth domain plugin.
package dao
import (
"Wavelet/core/contracts"
"context"
"gorm.io/gorm"
)
// DAO aggregates all data access objects for the auth domain plugin.
type DAO struct {
dbSvc contracts.DBService
cacheSvc contracts.CacheService
limiterSvc contracts.LimiterService
}
// New creates a new DAO aggregate.
func New(dbSvc contracts.DBService, cacheSvc contracts.CacheService, limiterSvc contracts.LimiterService) *DAO {
return &DAO{
dbSvc: dbSvc,
cacheSvc: cacheSvc,
limiterSvc: limiterSvc,
}
}
// SetDBService updates the DBService reference.
func (d *DAO) SetDBService(db contracts.DBService) {
d.dbSvc = db
}
// SetCacheService updates the CacheService reference.
func (d *DAO) SetCacheService(cache contracts.CacheService) {
d.cacheSvc = cache
}
// SetLimiterService updates the LimiterService reference.
func (d *DAO) SetLimiterService(limiter contracts.LimiterService) {
d.limiterSvc = limiter
}
// DB returns the GORM DB instance associated with the request context.
func (d *DAO) DB(ctx context.Context) *gorm.DB {
if d.dbSvc != nil {
return d.dbSvc.DB(ctx)
}
return nil
}
// Cache returns the CacheService instance.
func (d *DAO) Cache() contracts.CacheService {
return d.cacheSvc
}
// Limiter returns the LimiterService instance.
func (d *DAO) Limiter() contracts.LimiterService {
return d.limiterSvc
}
@@ -0,0 +1,38 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package dao provides data access objects and caching for the auth domain plugin.
package dao
import (
"Wavelet/plugins/domain/auth/model/entity"
"context"
)
// FindExternalAccount 查询指定认证源的外部账号绑定
func (d *DAO) FindExternalAccount(ctx context.Context, authSourceID uint64, externalID string) (*entity.ExternalAccount, error) {
var account entity.ExternalAccount
if err := d.DB(ctx).Where("auth_source_id = ? AND external_id = ?", authSourceID, externalID).First(&account).Error; err != nil {
return nil, err
}
return &account, nil
}
// BindExternalAccount 绑定外部账号
func (d *DAO) BindExternalAccount(ctx context.Context, account *entity.ExternalAccount) error {
return d.DB(ctx).Create(account).Error
}
// ListExternalAccountsByUserID 获取用户绑定的所有外部账号
func (d *DAO) ListExternalAccountsByUserID(ctx context.Context, userID uint64) ([]entity.ExternalAccount, error) {
var accounts []entity.ExternalAccount
if err := d.DB(ctx).Where("user_id = ?", userID).Find(&accounts).Error; err != nil {
return nil, err
}
return accounts, nil
}
// UnbindExternalAccount 解绑外部账号
func (d *DAO) UnbindExternalAccount(ctx context.Context, id, userID uint64) error {
return d.DB(ctx).Where("id = ? AND user_id = ?", id, userID).Delete(&entity.ExternalAccount{}).Error
}
@@ -0,0 +1,91 @@
// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
// Package dao provides data access objects and caching for the auth domain plugin.
package dao
import (
"Wavelet/core/contracts"
"Wavelet/pkg/util"
"Wavelet/plugins/domain/auth/model/do"
"context"
"time"
)
// GetAccessTokenByHash 按令牌哈希读取访问令牌记录(仅取鉴权所需字段)
func (d *DAO) GetAccessTokenByHash(ctx context.Context, tokenHash string) (*do.CachedToken, error) {
var row struct {
ID uint64
UserID uint64
IsAdmin bool
}
if err := d.DB(ctx).Table("w_access_tokens").Where("token_hash = ?", tokenHash).First(&row).Error; err != nil {
return nil, err
}
return &do.CachedToken{
ID: row.ID,
UserID: row.UserID,
IsAdmin: row.IsAdmin,
}, nil
}
// GetActiveUserByID 读取仍处于启用状态的用户
func (d *DAO) GetActiveUserByID(ctx context.Context, userID uint64) (*contracts.UserDTO, error) {
var user contracts.UserDTO
if err := d.DB(ctx).Table("w_users").Where("id = ? AND is_active = ?", userID, true).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
// GetUserByID 按 ID 读取用户(不限制启用状态)
func (d *DAO) GetUserByID(ctx context.Context, userID uint64) (*contracts.UserDTO, error) {
var user contracts.UserDTO
if err := d.DB(ctx).Table("w_users").Where("id = ?", userID).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}
// InsertUser 新建用户记录
func (d *DAO) InsertUser(ctx context.Context, user *contracts.UserDTO) error {
return d.DB(ctx).Table("w_users").Create(user).Error
}
// TouchUserLastLogin 刷新用户最后登录时间
func (d *DAO) TouchUserLastLogin(ctx context.Context, userID uint64, at time.Time) error {
return d.DB(ctx).Table("w_users").Where("id = ?", userID).Update("last_login_at", at).Error
}
// ListSimilarUsernames 查询与基础用户名相同或带 `-序号` 后缀的用户名(用于用户名去重)
func (d *DAO) ListSimilarUsernames(ctx context.Context, base string) ([]string, error) {
var existingUsernames []string
if err := d.DB(ctx).Table("w_users").
Where("username = ? OR username LIKE ? ESCAPE '\\'", base, util.EscapeLike(base)+"-%").
Pluck("username", &existingUsernames).Error; err != nil {
return nil, err
}
return existingUsernames, nil
}
// GetSystemConfigValue 读取系统配置项原始值
func (d *DAO) GetSystemConfigValue(ctx context.Context, key string) (string, error) {
var val string
if err := d.DB(ctx).Table("w_system_configs").Where("key = ?", key).Pluck("value", &val).Error; err != nil {
return "", err
}
return val, nil
}
// ListSystemConfigsByKeys 按键批量读取系统配置项
func (d *DAO) ListSystemConfigsByKeys(ctx context.Context, keys []string) ([]do.CapConfigRecord, error) {
var records []do.CapConfigRecord
db := d.DB(ctx)
if db == nil {
return nil, nil
}
if err := db.Table("w_system_configs").Where("key IN ?", keys).Find(&records).Error; err != nil {
return nil, err
}
return records, nil
}