mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-11 07:46:37 +08:00
feat: merge conflict resolution, site management, UI fixes
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
// Package service — PT 站点管理服务。
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/ShukeBta/MediaStationGo/internal/model"
|
||||
"github.com/ShukeBta/MediaStationGo/internal/repository"
|
||||
)
|
||||
|
||||
// 站点管理错误码。
|
||||
var (
|
||||
ErrSiteNotFound = errors.New("site not found")
|
||||
ErrSiteAuthFailed = errors.New("site authentication failed")
|
||||
ErrSiteTypeInvalid = errors.New("invalid site type")
|
||||
ErrSiteAuthInvalid = errors.New("invalid auth type")
|
||||
)
|
||||
|
||||
// SiteService 站点管理服务。
|
||||
type SiteService struct {
|
||||
log *zap.Logger
|
||||
repo *repository.Container
|
||||
crypto *CryptoService
|
||||
}
|
||||
|
||||
// NewSiteService 创建站点管理服务。
|
||||
func NewSiteService(log *zap.Logger, repo *repository.Container, crypto *CryptoService) *SiteService {
|
||||
return &SiteService{log: log, repo: repo, crypto: crypto}
|
||||
}
|
||||
|
||||
// Create 创建站点,加密敏感字段。
|
||||
func (s *SiteService) Create(ctx context.Context, site *model.Site) (*model.Site, error) {
|
||||
if !isValidSiteType(site.Type) {
|
||||
return nil, ErrSiteTypeInvalid
|
||||
}
|
||||
if !isValidAuthType(site.AuthType) {
|
||||
return nil, ErrSiteAuthInvalid
|
||||
}
|
||||
|
||||
// 加密敏感字段
|
||||
s.encryptSite(site)
|
||||
|
||||
if err := s.repo.Site.Create(ctx, site); err != nil {
|
||||
s.log.Error("create site failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return site, nil
|
||||
}
|
||||
|
||||
// GetByID 获取站点(敏感字段解密)。
|
||||
func (s *SiteService) GetByID(ctx context.Context, id string) (*model.Site, error) {
|
||||
site, err := s.repo.Site.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if site == nil {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
|
||||
s.decryptSite(site)
|
||||
return site, nil
|
||||
}
|
||||
|
||||
// List 获取所有站点(不含敏感字段)。
|
||||
func (s *SiteService) List(ctx context.Context) ([]model.Site, error) {
|
||||
sites, err := s.repo.Site.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sites, nil
|
||||
}
|
||||
|
||||
// Update 更新站点。
|
||||
func (s *SiteService) Update(ctx context.Context, site *model.Site) (*model.Site, error) {
|
||||
existing, err := s.repo.Site.FindByID(ctx, site.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing == nil {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
|
||||
if !isValidSiteType(site.Type) {
|
||||
return nil, ErrSiteTypeInvalid
|
||||
}
|
||||
if !isValidAuthType(site.AuthType) {
|
||||
return nil, ErrSiteAuthInvalid
|
||||
}
|
||||
|
||||
s.encryptSite(site)
|
||||
|
||||
if err := s.repo.Site.Update(ctx, site); err != nil {
|
||||
s.log.Error("update site failed", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return site, nil
|
||||
}
|
||||
|
||||
// Delete 删除站点。
|
||||
func (s *SiteService) Delete(ctx context.Context, id string) error {
|
||||
existing, err := s.repo.Site.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return ErrSiteNotFound
|
||||
}
|
||||
return s.repo.Site.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Authenticate 测试站点认证。
|
||||
func (s *SiteService) Authenticate(ctx context.Context, id string) error {
|
||||
site, err := s.repo.Site.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if site == nil {
|
||||
return ErrSiteNotFound
|
||||
}
|
||||
|
||||
cfg, err := s.toSiteConfig(site)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
adapter := GetAdapterForType(site.Type)
|
||||
if err := adapter.Authenticate(ctx, *cfg); err != nil {
|
||||
// 更新错误状态
|
||||
now := time.Now()
|
||||
site.LastError = err.Error()
|
||||
site.LastCheckAt = &now
|
||||
_ = s.repo.Site.Update(ctx, site)
|
||||
return ErrSiteAuthFailed
|
||||
}
|
||||
|
||||
// 清除错误状态
|
||||
now := time.Now()
|
||||
site.LastError = ""
|
||||
site.LastCheckAt = &now
|
||||
_ = s.repo.Site.Update(ctx, site)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSiteConfig 获取解密后的站点配置(供内部使用)。
|
||||
func (s *SiteService) GetSiteConfig(ctx context.Context, id string) (*SiteConfig, error) {
|
||||
site, err := s.repo.Site.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if site == nil {
|
||||
return nil, ErrSiteNotFound
|
||||
}
|
||||
return s.toSiteConfig(site)
|
||||
}
|
||||
|
||||
// encryptSite 加密站点敏感字段。
|
||||
func (s *SiteService) encryptSite(site *model.Site) {
|
||||
if site.Cookie != "" {
|
||||
site.Cookie = s.crypto.Encrypt(site.Cookie)
|
||||
}
|
||||
if site.APIKey != "" {
|
||||
site.APIKey = s.crypto.Encrypt(site.APIKey)
|
||||
}
|
||||
if site.AuthHeader != "" {
|
||||
site.AuthHeader = s.crypto.Encrypt(site.AuthHeader)
|
||||
}
|
||||
if site.Extra != "" {
|
||||
site.Extra = s.crypto.Encrypt(site.Extra)
|
||||
}
|
||||
}
|
||||
|
||||
// decryptSite 解密站点敏感字段。
|
||||
func (s *SiteService) decryptSite(site *model.Site) {
|
||||
if site.Cookie != "" {
|
||||
site.Cookie = s.crypto.Decrypt(site.Cookie)
|
||||
}
|
||||
if site.APIKey != "" {
|
||||
site.APIKey = s.crypto.Decrypt(site.APIKey)
|
||||
}
|
||||
if site.AuthHeader != "" {
|
||||
site.AuthHeader = s.crypto.Decrypt(site.AuthHeader)
|
||||
}
|
||||
if site.Extra != "" {
|
||||
site.Extra = s.crypto.Decrypt(site.Extra)
|
||||
}
|
||||
}
|
||||
|
||||
// toSiteConfig 将 model.Site 转换为 SiteConfig(解密后)。
|
||||
func (s *SiteService) toSiteConfig(site *model.Site) (*SiteConfig, error) {
|
||||
cfg := &SiteConfig{
|
||||
Name: site.Name,
|
||||
Type: site.Type,
|
||||
URL: strings.TrimRight(site.URL, "/"),
|
||||
AuthType: site.AuthType,
|
||||
Extra: map[string]string{},
|
||||
}
|
||||
|
||||
// 解密
|
||||
if site.Cookie != "" {
|
||||
cfg.Cookie = s.crypto.Decrypt(site.Cookie)
|
||||
}
|
||||
if site.APIKey != "" {
|
||||
cfg.APIKey = s.crypto.Decrypt(site.APIKey)
|
||||
}
|
||||
if site.AuthHeader != "" {
|
||||
cfg.AuthHeader = s.crypto.Decrypt(site.AuthHeader)
|
||||
}
|
||||
if site.Extra != "" {
|
||||
dec := s.crypto.Decrypt(site.Extra)
|
||||
if dec != "" {
|
||||
if err := json.Unmarshal([]byte(dec), &cfg.Extra); err != nil {
|
||||
s.log.Warn("parse site extra config failed", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// isValidSiteType 检查站点类型是否有效。
|
||||
func isValidSiteType(siteType string) bool {
|
||||
for _, t := range model.SiteTypes() {
|
||||
if t == siteType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isValidAuthType 检查认证方式是否有效。
|
||||
func isValidAuthType(authType string) bool {
|
||||
for _, t := range model.AuthTypes() {
|
||||
if t == authType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user