Files
ssq-desk/internal/service/auth_service.go
2026-01-14 14:17:38 +08:00

216 lines
5.0 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 service
import (
"crypto/md5"
"encoding/hex"
"fmt"
"os"
"runtime"
"ssq-desk/internal/database"
"ssq-desk/internal/storage/models"
"ssq-desk/internal/storage/repository"
"time"
"gorm.io/gorm"
)
// AuthService 授权服务
type AuthService struct {
repo repository.AuthRepository
}
// NewAuthService 创建授权服务
func NewAuthService(repo repository.AuthRepository) *AuthService {
return &AuthService{repo: repo}
}
// GetDeviceID 获取设备ID基于硬件信息生成
func GetDeviceID() (string, error) {
var deviceInfo string
// 获取主机名
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown"
}
// 获取用户目录
homeDir, err := os.UserHomeDir()
if err != nil {
homeDir = "unknown"
}
// 组合设备信息
deviceInfo = fmt.Sprintf("%s-%s-%s", hostname, homeDir, runtime.GOOS)
// 生成 MD5 作为设备ID
hash := md5.Sum([]byte(deviceInfo))
deviceID := hex.EncodeToString(hash[:])
return deviceID, nil
}
// ValidateLicenseFormat 验证授权码格式
func ValidateLicenseFormat(licenseCode string) error {
if licenseCode == "" {
return fmt.Errorf("授权码不能为空")
}
// 去除空格和连字符
cleaned := ""
for _, c := range licenseCode {
if c != ' ' && c != '-' {
cleaned += string(c)
}
}
// 格式验证至少16位只包含字母和数字
if len(cleaned) < 16 {
return fmt.Errorf("授权码长度不足至少需要16位字符")
}
if len(cleaned) > 100 {
return fmt.Errorf("授权码长度过长最多100位字符")
}
// 验证字符:只允许字母和数字
for _, c := range cleaned {
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
return fmt.Errorf("授权码只能包含字母和数字")
}
}
return nil
}
// ValidateLicenseFromRemote 从远程数据库验证授权码
func ValidateLicenseFromRemote(licenseCode string) error {
// 获取 MySQL 连接
mysqlDB := database.GetMySQL()
if mysqlDB == nil {
return fmt.Errorf("无法连接远程数据库,无法验证授权码")
}
// 清理授权码(去除空格和连字符),与格式验证保持一致
cleaned := ""
for _, c := range licenseCode {
if c != ' ' && c != '-' {
cleaned += string(c)
}
}
// 查询授权码是否存在且有效(支持原始格式和清理后格式)
var auth models.Authorization
err := mysqlDB.Where("(license_code = ? OR license_code = ?) AND status = ?", licenseCode, cleaned, 1).First(&auth).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return fmt.Errorf("授权码无效或不存在")
}
return fmt.Errorf("验证授权码时发生错误: %v", err)
}
// 检查是否过期
if auth.ExpiresAt != nil && auth.ExpiresAt.Before(time.Now()) {
return fmt.Errorf("授权码已过期")
}
return nil
}
// ValidateLicense 验证授权码
func (s *AuthService) ValidateLicense(licenseCode string) error {
// 格式验证
if err := ValidateLicenseFormat(licenseCode); err != nil {
return err
}
// 从远程数据库验证授权码有效性
if err := ValidateLicenseFromRemote(licenseCode); err != nil {
return err
}
// 获取设备ID
deviceID, err := GetDeviceID()
if err != nil {
return fmt.Errorf("获取设备ID失败: %v", err)
}
// 保存授权信息到本地
auth := &models.Authorization{
LicenseCode: licenseCode,
DeviceID: deviceID,
ActivatedAt: time.Now(),
Status: 1,
}
// 检查是否已存在
existing, err := s.repo.GetByLicenseCode(licenseCode)
if err == nil && existing != nil {
// 更新现有授权
existing.DeviceID = deviceID
existing.ActivatedAt = time.Now()
existing.Status = 1
return s.repo.Update(existing)
}
// 创建新授权
return s.repo.Create(auth)
}
// CheckAuthStatus 检查授权状态
func (s *AuthService) CheckAuthStatus() (*AuthStatus, error) {
deviceID, err := GetDeviceID()
if err != nil {
return nil, fmt.Errorf("获取设备ID失败: %v", err)
}
auth, err := s.repo.GetByDeviceID(deviceID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return &AuthStatus{
IsActivated: false,
Message: "未激活",
}, nil
}
return nil, err
}
// 检查状态
if auth.Status != 1 {
return &AuthStatus{
IsActivated: false,
Message: "授权已失效",
}, nil
}
// 检查过期时间
if auth.ExpiresAt != nil && auth.ExpiresAt.Before(time.Now()) {
return &AuthStatus{
IsActivated: false,
Message: "授权已过期",
}, nil
}
return &AuthStatus{
IsActivated: true,
LicenseCode: auth.LicenseCode,
ActivatedAt: auth.ActivatedAt,
ExpiresAt: auth.ExpiresAt,
Message: "已激活",
}, nil
}
// AuthStatus 授权状态
type AuthStatus struct {
IsActivated bool `json:"is_activated"`
LicenseCode string `json:"license_code,omitempty"`
ActivatedAt time.Time `json:"activated_at,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
Message string `json:"message"`
}
// GetAuthInfo 获取授权信息
func (s *AuthService) GetAuthInfo() (*AuthStatus, error) {
return s.CheckAuthStatus()
}