Files
sub2api/backend/internal/repository/tls_fingerprint_profile_repo.go
shaw 1854050df3 feat(tls-fingerprint): 新增 TLS 指纹 Profile 数据库管理及代码质量优化
新增功能:
- 新增 TLS 指纹 Profile CRUD 管理(Ent schema + 迁移 + Admin API + 前端管理界面)
- 支持账号绑定数据库中的自定义 TLS Profile,或随机选择(profile_id=-1)
- HTTPUpstream.DoWithTLS 接口从 bool 改为 *tlsfingerprint.Profile,支持按账号指定 Profile
- AccountUsageService 注入 TLSFingerprintProfileService,统一 usage 场景与网关的 Profile 解析逻辑

代码优化:
- 删除已被 TLSFingerprintProfileService 完全取代的 registry.go 死代码(418 行)
- 提取 3 个 dialer 的重复 TLS 握手逻辑为 performTLSHandshake() 共用函数
- 修复 GetTLSFingerprintProfileID 缺少 json.Number 处理的 bug
- gateway_service.Forward 中 ResolveTLSProfile 从重试循环内重复调用改为预解析局部变量
- 删除冗余的 buildClientHelloSpec() 单行 wrapper 和 int64(e.ID) 无效转换
- tls_fingerprint_profile_cache.go 日志从 log.Printf 改为 slog 结构化日志
- dialer_capture_test.go 添加 //go:build integration 标签,防止 CI 失败
- 去重 TestProfileExpectation 类型至共享 test_types_test.go
- 修复 9 个测试文件缺少 tlsfingerprint import 的编译错误
- 修复 error_policy_integration_test.go 中 handleError 回调签名被错误替换的问题
2026-03-27 14:33:05 +08:00

214 lines
5.4 KiB
Go

package repository
import (
"context"
"github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/ent/tlsfingerprintprofile"
"github.com/Wei-Shaw/sub2api/internal/model"
"github.com/Wei-Shaw/sub2api/internal/service"
)
type tlsFingerprintProfileRepository struct {
client *ent.Client
}
// NewTLSFingerprintProfileRepository 创建 TLS 指纹模板仓库
func NewTLSFingerprintProfileRepository(client *ent.Client) service.TLSFingerprintProfileRepository {
return &tlsFingerprintProfileRepository{client: client}
}
// List 获取所有模板
func (r *tlsFingerprintProfileRepository) List(ctx context.Context) ([]*model.TLSFingerprintProfile, error) {
profiles, err := r.client.TLSFingerprintProfile.Query().
Order(ent.Asc(tlsfingerprintprofile.FieldName)).
All(ctx)
if err != nil {
return nil, err
}
result := make([]*model.TLSFingerprintProfile, len(profiles))
for i, p := range profiles {
result[i] = r.toModel(p)
}
return result, nil
}
// GetByID 根据 ID 获取模板
func (r *tlsFingerprintProfileRepository) GetByID(ctx context.Context, id int64) (*model.TLSFingerprintProfile, error) {
p, err := r.client.TLSFingerprintProfile.Get(ctx, id)
if err != nil {
if ent.IsNotFound(err) {
return nil, nil
}
return nil, err
}
return r.toModel(p), nil
}
// Create 创建模板
func (r *tlsFingerprintProfileRepository) Create(ctx context.Context, p *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) {
builder := r.client.TLSFingerprintProfile.Create().
SetName(p.Name).
SetEnableGrease(p.EnableGREASE)
if p.Description != nil {
builder.SetDescription(*p.Description)
}
if len(p.CipherSuites) > 0 {
builder.SetCipherSuites(p.CipherSuites)
}
if len(p.Curves) > 0 {
builder.SetCurves(p.Curves)
}
if len(p.PointFormats) > 0 {
builder.SetPointFormats(p.PointFormats)
}
if len(p.SignatureAlgorithms) > 0 {
builder.SetSignatureAlgorithms(p.SignatureAlgorithms)
}
if len(p.ALPNProtocols) > 0 {
builder.SetAlpnProtocols(p.ALPNProtocols)
}
if len(p.SupportedVersions) > 0 {
builder.SetSupportedVersions(p.SupportedVersions)
}
if len(p.KeyShareGroups) > 0 {
builder.SetKeyShareGroups(p.KeyShareGroups)
}
if len(p.PSKModes) > 0 {
builder.SetPskModes(p.PSKModes)
}
if len(p.Extensions) > 0 {
builder.SetExtensions(p.Extensions)
}
created, err := builder.Save(ctx)
if err != nil {
return nil, err
}
return r.toModel(created), nil
}
// Update 更新模板
func (r *tlsFingerprintProfileRepository) Update(ctx context.Context, p *model.TLSFingerprintProfile) (*model.TLSFingerprintProfile, error) {
builder := r.client.TLSFingerprintProfile.UpdateOneID(p.ID).
SetName(p.Name).
SetEnableGrease(p.EnableGREASE)
if p.Description != nil {
builder.SetDescription(*p.Description)
} else {
builder.ClearDescription()
}
if len(p.CipherSuites) > 0 {
builder.SetCipherSuites(p.CipherSuites)
} else {
builder.ClearCipherSuites()
}
if len(p.Curves) > 0 {
builder.SetCurves(p.Curves)
} else {
builder.ClearCurves()
}
if len(p.PointFormats) > 0 {
builder.SetPointFormats(p.PointFormats)
} else {
builder.ClearPointFormats()
}
if len(p.SignatureAlgorithms) > 0 {
builder.SetSignatureAlgorithms(p.SignatureAlgorithms)
} else {
builder.ClearSignatureAlgorithms()
}
if len(p.ALPNProtocols) > 0 {
builder.SetAlpnProtocols(p.ALPNProtocols)
} else {
builder.ClearAlpnProtocols()
}
if len(p.SupportedVersions) > 0 {
builder.SetSupportedVersions(p.SupportedVersions)
} else {
builder.ClearSupportedVersions()
}
if len(p.KeyShareGroups) > 0 {
builder.SetKeyShareGroups(p.KeyShareGroups)
} else {
builder.ClearKeyShareGroups()
}
if len(p.PSKModes) > 0 {
builder.SetPskModes(p.PSKModes)
} else {
builder.ClearPskModes()
}
if len(p.Extensions) > 0 {
builder.SetExtensions(p.Extensions)
} else {
builder.ClearExtensions()
}
updated, err := builder.Save(ctx)
if err != nil {
return nil, err
}
return r.toModel(updated), nil
}
// Delete 删除模板
func (r *tlsFingerprintProfileRepository) Delete(ctx context.Context, id int64) error {
return r.client.TLSFingerprintProfile.DeleteOneID(id).Exec(ctx)
}
// toModel 将 Ent 实体转换为服务模型
func (r *tlsFingerprintProfileRepository) toModel(e *ent.TLSFingerprintProfile) *model.TLSFingerprintProfile {
p := &model.TLSFingerprintProfile{
ID: e.ID,
Name: e.Name,
Description: e.Description,
EnableGREASE: e.EnableGrease,
CipherSuites: e.CipherSuites,
Curves: e.Curves,
PointFormats: e.PointFormats,
SignatureAlgorithms: e.SignatureAlgorithms,
ALPNProtocols: e.AlpnProtocols,
SupportedVersions: e.SupportedVersions,
KeyShareGroups: e.KeyShareGroups,
PSKModes: e.PskModes,
Extensions: e.Extensions,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
}
// 确保切片不为 nil
if p.CipherSuites == nil {
p.CipherSuites = []uint16{}
}
if p.Curves == nil {
p.Curves = []uint16{}
}
if p.PointFormats == nil {
p.PointFormats = []uint16{}
}
if p.SignatureAlgorithms == nil {
p.SignatureAlgorithms = []uint16{}
}
if p.ALPNProtocols == nil {
p.ALPNProtocols = []string{}
}
if p.SupportedVersions == nil {
p.SupportedVersions = []uint16{}
}
if p.KeyShareGroups == nil {
p.KeyShareGroups = []uint16{}
}
if p.PSKModes == nil {
p.PSKModes = []uint16{}
}
if p.Extensions == nil {
p.Extensions = []uint16{}
}
return p
}