mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
refactor(auth): modularize auth plugin with physical subpackages and decoupled services
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user