216 lines
5.0 KiB
Go
216 lines
5.0 KiB
Go
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()
|
||
}
|