2026-07-03 15:58:29 +08:00
|
|
|
// Package main 程序入口
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2026-07-18 17:07:34 +08:00
|
|
|
"io"
|
2026-07-03 15:58:29 +08:00
|
|
|
"log"
|
2026-07-18 17:07:34 +08:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"time"
|
2026-07-03 15:58:29 +08:00
|
|
|
|
|
|
|
|
"seeyon-filesystem/api"
|
|
|
|
|
"seeyon-filesystem/config"
|
|
|
|
|
"seeyon-filesystem/model"
|
|
|
|
|
"seeyon-filesystem/storage"
|
2026-07-10 17:33:33 +08:00
|
|
|
"seeyon-filesystem/utils"
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
2026-07-03 15:58:29 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func main() {
|
2026-07-18 17:07:34 +08:00
|
|
|
// 日志输出: 控制台 + 内存缓冲区 + 按日期分割的文件
|
|
|
|
|
logDir := "logs"
|
|
|
|
|
os.MkdirAll(logDir, 0755)
|
|
|
|
|
logPath := filepath.Join(logDir, fmt.Sprintf("server_%s.log", time.Now().Format("20060102")))
|
|
|
|
|
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
|
|
|
if err == nil {
|
|
|
|
|
log.SetOutput(io.MultiWriter(os.Stderr, logFile, utils.LogBuf))
|
|
|
|
|
} else {
|
|
|
|
|
log.SetOutput(io.MultiWriter(os.Stderr, utils.LogBuf))
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
cfg, err := config.Load()
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("加载配置失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
// 验证授权(启动时必须通过, 否则拒绝启动)
|
|
|
|
|
if err := checkLicense(cfg.License); err != nil {
|
|
|
|
|
log.Fatalf("授权验证失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-03 15:58:29 +08:00
|
|
|
if err := config.InitDB(&cfg.Database); err != nil {
|
|
|
|
|
log.Fatalf("初始化数据库失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// if err := autoMigrate(); err != nil {
|
|
|
|
|
// log.Fatalf("数据库迁移失败: %v", err)
|
|
|
|
|
// }
|
2026-07-03 15:58:29 +08:00
|
|
|
|
|
|
|
|
initDefaultData()
|
|
|
|
|
|
|
|
|
|
storage.InitEngines(cfg.Storage.LocalBasePath)
|
|
|
|
|
|
|
|
|
|
r := api.SetupRouter()
|
|
|
|
|
|
|
|
|
|
addr := fmt.Sprintf(":%d", cfg.Server.Port)
|
|
|
|
|
log.Printf("服务启动: http://localhost%s", addr)
|
|
|
|
|
|
|
|
|
|
if err := r.Run(addr); err != nil {
|
|
|
|
|
log.Fatalf("服务启动失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func autoMigrate() error {
|
|
|
|
|
err := config.DB.AutoMigrate(
|
|
|
|
|
&model.User{},
|
|
|
|
|
&model.Folder{},
|
|
|
|
|
&model.File{},
|
|
|
|
|
&model.Share{},
|
|
|
|
|
&model.StoragePolicy{},
|
|
|
|
|
&model.FileAssociation{},
|
|
|
|
|
&model.Role{},
|
|
|
|
|
&model.Permission{},
|
|
|
|
|
&model.RolePermission{},
|
|
|
|
|
&model.UserRole{},
|
|
|
|
|
&model.SSOProvider{},
|
|
|
|
|
&model.SSOSession{},
|
|
|
|
|
&model.Department{},
|
|
|
|
|
&model.UploadSession{},
|
|
|
|
|
&model.StorageAssignment{},
|
|
|
|
|
&model.App{},
|
|
|
|
|
&model.AppUser{},
|
|
|
|
|
&model.FilePermission{},
|
|
|
|
|
&model.OperationLog{},
|
|
|
|
|
&model.BackupPolicy{},
|
|
|
|
|
&model.BackupLog{},
|
2026-07-18 10:52:32 +08:00
|
|
|
&model.UserSession{},
|
2026-07-03 15:58:29 +08:00
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加复合索引优化查询性能
|
|
|
|
|
createIndexIfNotExists("fs_file_permission", "idx_fp_user_file", "user_id, file_id")
|
|
|
|
|
createIndexIfNotExists("fs_file_permission", "idx_fp_role_file", "role_id, file_id")
|
|
|
|
|
createIndexIfNotExists("fs_file", "idx_file_folder_owner", "folder_id, owner_id, status")
|
|
|
|
|
createIndexIfNotExists("fs_file", "idx_file_status", "status")
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createIndexIfNotExists 安全创建索引(忽略已存在错误)
|
|
|
|
|
func createIndexIfNotExists(table, indexName, columns string) {
|
2026-07-18 10:52:32 +08:00
|
|
|
// 先检查索引是否已存在(兼容MySQL/PostgreSQL/SQL Server)
|
|
|
|
|
var count int64
|
|
|
|
|
switch config.AppConfig.Database.Type {
|
|
|
|
|
case "postgres":
|
|
|
|
|
config.DB.Raw("SELECT COUNT(*) FROM pg_indexes WHERE indexname = ?", indexName).Scan(&count)
|
|
|
|
|
case "sqlserver":
|
|
|
|
|
config.DB.Raw("SELECT COUNT(*) FROM sys.indexes WHERE name = ?", indexName).Scan(&count)
|
|
|
|
|
default:
|
|
|
|
|
config.DB.Raw("SELECT COUNT(*) FROM information_schema.statistics WHERE table_name = ? AND index_name = ?", table, indexName).Scan(&count)
|
|
|
|
|
}
|
|
|
|
|
if count > 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
2026-07-03 15:58:29 +08:00
|
|
|
sql := fmt.Sprintf("CREATE INDEX %s ON %s(%s)", indexName, table, columns)
|
|
|
|
|
config.DB.Exec(sql)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initDefaultData 初始化默认数据: 权限、角色、存储策略、分配规则
|
|
|
|
|
func initDefaultData() {
|
|
|
|
|
// ========== 1. 权限 ==========
|
|
|
|
|
permissions := []model.Permission{
|
|
|
|
|
{Code: "file:create", Name: "创建文件/文件夹", Resource: "file", Action: "create"},
|
|
|
|
|
{Code: "file:read", Name: "查看文件", Resource: "file", Action: "read"},
|
|
|
|
|
{Code: "file:update", Name: "修改文件", Resource: "file", Action: "update"},
|
|
|
|
|
{Code: "file:delete", Name: "删除文件", Resource: "file", Action: "delete"},
|
|
|
|
|
{Code: "file:upload", Name: "上传文件", Resource: "file", Action: "upload"},
|
|
|
|
|
{Code: "file:download", Name: "下载文件", Resource: "file", Action: "download"},
|
2026-07-10 17:33:33 +08:00
|
|
|
{Code: "file:grant", Name: "授权文件权限", Resource: "file", Action: "grant"},
|
2026-07-03 15:58:29 +08:00
|
|
|
{Code: "share:create", Name: "创建分享", Resource: "share", Action: "create"},
|
|
|
|
|
{Code: "share:manage", Name: "管理分享", Resource: "share", Action: "manage"},
|
|
|
|
|
{Code: "webdav:access", Name: "WebDAV访问", Resource: "webdav", Action: "access"},
|
|
|
|
|
{Code: "user:manage", Name: "用户管理", Resource: "user", Action: "manage"},
|
|
|
|
|
{Code: "role:manage", Name: "角色管理", Resource: "role", Action: "manage"},
|
|
|
|
|
{Code: "sso:manage", Name: "SSO管理", Resource: "sso", Action: "manage"},
|
|
|
|
|
{Code: "system:manage", Name: "系统管理", Resource: "system", Action: "manage"},
|
|
|
|
|
}
|
|
|
|
|
for _, p := range permissions {
|
2026-07-18 10:52:32 +08:00
|
|
|
var existing model.Permission
|
|
|
|
|
if config.DB.Where("code = ?", p.Code).First(&existing).Error != nil {
|
|
|
|
|
p.ID = utils.GenID()
|
|
|
|
|
config.DB.Create(&p)
|
|
|
|
|
}
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 2. 四级角色 ==========
|
|
|
|
|
roles := []model.Role{
|
2026-07-10 17:33:33 +08:00
|
|
|
{Name: "admin", DisplayName: "超级管理员", Description: "全平台最高权限, 仅用于后台运维", IsSystem: 1},
|
2026-07-03 15:58:29 +08:00
|
|
|
{Name: "staff", DisplayName: "内部员工", Description: "团队内部成员, 可上传下载分享文件", IsSystem: 1},
|
|
|
|
|
{Name: "user", DisplayName: "普通用户", Description: "注册用户, 基础存储功能", IsSystem: 1},
|
|
|
|
|
{Name: "guest", DisplayName: "访客", Description: "仅可查看公开分享资源", IsSystem: 1},
|
|
|
|
|
}
|
|
|
|
|
for _, r := range roles {
|
2026-07-18 10:52:32 +08:00
|
|
|
var existing model.Role
|
|
|
|
|
if config.DB.Where("name = ?", r.Name).First(&existing).Error != nil {
|
|
|
|
|
r.ID = utils.GenID()
|
|
|
|
|
config.DB.Create(&r)
|
|
|
|
|
} else {
|
|
|
|
|
r = existing
|
|
|
|
|
}
|
2026-07-03 15:58:29 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 角色-权限分配
|
|
|
|
|
rolePermMap := map[string][]string{
|
2026-07-10 17:33:33 +08:00
|
|
|
"admin": {"*"}, // 全部权限
|
|
|
|
|
"staff": {"file:create", "file:read", "file:update", "file:delete", "file:upload", "file:download", "file:grant", "share:create", "webdav:access"},
|
2026-07-03 15:58:29 +08:00
|
|
|
"user": {"file:create", "file:read", "file:update", "file:upload", "file:download", "share:create"},
|
|
|
|
|
"guest": {"file:read"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var allPerms []model.Permission
|
|
|
|
|
config.DB.Find(&allPerms)
|
|
|
|
|
permMap := make(map[string]uint64)
|
|
|
|
|
for _, p := range allPerms {
|
|
|
|
|
permMap[p.Code] = p.ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for roleName, permCodes := range rolePermMap {
|
|
|
|
|
var role model.Role
|
|
|
|
|
config.DB.Where("name = ?", roleName).First(&role)
|
|
|
|
|
if role.ID == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 清除旧权限
|
|
|
|
|
config.DB.Where("role_id = ?", role.ID).Delete(&model.RolePermission{})
|
|
|
|
|
|
|
|
|
|
if len(permCodes) == 1 && permCodes[0] == "*" {
|
|
|
|
|
// 全部权限
|
|
|
|
|
for _, p := range allPerms {
|
|
|
|
|
config.DB.Create(&model.RolePermission{RoleID: role.ID, PermissionID: p.ID})
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for _, code := range permCodes {
|
|
|
|
|
if pid, ok := permMap[code]; ok {
|
|
|
|
|
config.DB.Create(&model.RolePermission{RoleID: role.ID, PermissionID: pid})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 3. 四类存储策略 ==========
|
|
|
|
|
type policyDef struct {
|
|
|
|
|
Name string
|
|
|
|
|
PolicyType string
|
|
|
|
|
Type string
|
|
|
|
|
BasePath string
|
|
|
|
|
Quota int64
|
|
|
|
|
}
|
|
|
|
|
policyDefs := []policyDef{
|
|
|
|
|
{Name: "高性能热存储", PolicyType: "hot", Type: "local", BasePath: "./data/storage/hot", Quota: 53687091200}, // 50GB
|
|
|
|
|
{Name: "低成本冷存储", PolicyType: "cold", Type: "local", BasePath: "./data/storage/cold", Quota: 107374182400}, // 100GB
|
|
|
|
|
{Name: "公开资源存储", PolicyType: "public", Type: "local", BasePath: "./data/storage/public", Quota: 0}, // 不限
|
|
|
|
|
{Name: "扩展存储", PolicyType: "slave", Type: "local", BasePath: "./data/storage/slave", Quota: 214748364800}, // 200GB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, pd := range policyDefs {
|
|
|
|
|
var existing model.StoragePolicy
|
|
|
|
|
if config.DB.Where("policy_type = ?", pd.PolicyType).First(&existing).Error != nil {
|
|
|
|
|
config.DB.Create(&model.StoragePolicy{
|
2026-07-18 10:52:32 +08:00
|
|
|
ID: utils.GenID(),
|
2026-07-03 15:58:29 +08:00
|
|
|
Name: pd.Name,
|
|
|
|
|
Type: pd.Type,
|
|
|
|
|
PolicyType: pd.PolicyType,
|
|
|
|
|
Config: model.PolicyConfig{BasePath: pd.BasePath},
|
|
|
|
|
Status: 1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 4. 角色-存储策略分配(支持多策略+优先级) ==========
|
|
|
|
|
type assignDef struct {
|
|
|
|
|
RoleName string
|
|
|
|
|
PolicyType string
|
|
|
|
|
Quota int64
|
|
|
|
|
Priority int
|
|
|
|
|
}
|
|
|
|
|
assignDefs := []assignDef{
|
2026-07-10 17:33:33 +08:00
|
|
|
{RoleName: "admin", PolicyType: "hot", Quota: 0, Priority: 10},
|
2026-07-03 15:58:29 +08:00
|
|
|
{RoleName: "staff", PolicyType: "hot", Quota: 53687091200, Priority: 10},
|
|
|
|
|
{RoleName: "user", PolicyType: "cold", Quota: 10737418240, Priority: 10},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ad := range assignDefs {
|
|
|
|
|
var role model.Role
|
|
|
|
|
var policy model.StoragePolicy
|
|
|
|
|
config.DB.Where("name = ?", ad.RoleName).First(&role)
|
|
|
|
|
config.DB.Where("policy_type = ?", ad.PolicyType).First(&policy)
|
|
|
|
|
if role.ID == 0 || policy.ID == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var existing model.StorageAssignment
|
|
|
|
|
if config.DB.Where("group_id = ? AND policy_id = ?", role.ID, policy.ID).First(&existing).Error != nil {
|
|
|
|
|
config.DB.Create(&model.StorageAssignment{
|
2026-07-18 10:52:32 +08:00
|
|
|
ID: utils.GenID(),
|
2026-07-03 15:58:29 +08:00
|
|
|
PolicyID: policy.ID,
|
|
|
|
|
GroupID: role.ID,
|
|
|
|
|
StorageQuota: ad.Quota,
|
|
|
|
|
Priority: ad.Priority,
|
|
|
|
|
Status: 1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置默认存储策略(热存储)
|
|
|
|
|
var hotPolicy model.StoragePolicy
|
|
|
|
|
if config.DB.Where("policy_type = ?", "hot").First(&hotPolicy).Error == nil {
|
|
|
|
|
var defaultAssign model.StorageAssignment
|
|
|
|
|
if config.DB.Where("is_default = 1").First(&defaultAssign).Error != nil {
|
|
|
|
|
config.DB.Create(&model.StorageAssignment{
|
2026-07-18 10:52:32 +08:00
|
|
|
ID: utils.GenID(),
|
2026-07-03 15:58:29 +08:00
|
|
|
PolicyID: hotPolicy.ID,
|
|
|
|
|
IsDefault: 1,
|
|
|
|
|
Status: 1,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-10 17:33:33 +08:00
|
|
|
// ========== 5. 创建默认管理员 ==========
|
|
|
|
|
var count int64
|
|
|
|
|
config.DB.Model(&model.User{}).Where("username = ?", "admin").Count(&count)
|
|
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
if count == 0 {
|
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("admin123"), 10)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Fatalf("密码加密失败: %v", err)
|
|
|
|
|
}
|
2026-07-10 17:33:33 +08:00
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
admin := &model.User{
|
|
|
|
|
ID: utils.GenID(),
|
|
|
|
|
Username: "admin",
|
|
|
|
|
Password: string(hashedPassword),
|
|
|
|
|
Nickname: "管理员",
|
|
|
|
|
Role: "admin",
|
|
|
|
|
Status: 1,
|
|
|
|
|
StorageQuota: 107374182400, // 100GB
|
|
|
|
|
MustChangePwd: 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := config.DB.Create(admin).Error; err != nil {
|
|
|
|
|
log.Fatalf("创建 admin 失败: %v", err)
|
|
|
|
|
}
|
|
|
|
|
log.Println("默认管理员已创建: admin / admin123")
|
|
|
|
|
} else {
|
|
|
|
|
// 确保已有admin用户的Role字段为admin
|
|
|
|
|
config.DB.Model(&model.User{}).Where("username = ? AND (role IS NULL OR role = '')", "admin").Update("role", "admin")
|
|
|
|
|
log.Println("admin 用户已存在")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ========== 6. 确保admin用户有admin角色 ==========
|
|
|
|
|
var adminUser model.User
|
|
|
|
|
if config.DB.Where("username = ?", "admin").First(&adminUser).Error == nil {
|
|
|
|
|
var adminRole model.Role
|
|
|
|
|
if config.DB.Where("name = ?", "admin").First(&adminRole).Error == nil {
|
|
|
|
|
var ur model.UserRole
|
|
|
|
|
if config.DB.Where("user_id = ? AND role_id = ?", adminUser.ID, adminRole.ID).First(&ur).Error != nil {
|
|
|
|
|
config.DB.Create(&model.UserRole{UserID: adminUser.ID, RoleID: adminRole.ID})
|
|
|
|
|
log.Println("admin 已分配 admin 角色")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 7. 给没有角色的用户补上默认角色(user) ==========
|
|
|
|
|
var userRole model.Role
|
|
|
|
|
if config.DB.Where("name = ?", "user").First(&userRole).Error == nil {
|
|
|
|
|
var usersWithoutRole []model.User
|
|
|
|
|
config.DB.Where("id NOT IN (SELECT user_id FROM fs_user_role) AND username != ?", "admin").Find(&usersWithoutRole)
|
|
|
|
|
for _, u := range usersWithoutRole {
|
|
|
|
|
config.DB.Create(&model.UserRole{UserID: u.ID, RoleID: userRole.ID})
|
|
|
|
|
}
|
|
|
|
|
if len(usersWithoutRole) > 0 {
|
|
|
|
|
log.Printf("已为 %d 个无角色用户分配默认角色", len(usersWithoutRole))
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-03 15:58:29 +08:00
|
|
|
|
|
|
|
|
log.Println("默认数据初始化完成")
|
|
|
|
|
}
|
2026-07-18 10:52:32 +08:00
|
|
|
|
|
|
|
|
// checkLicense 验证产品授权
|
|
|
|
|
func checkLicense(cfg config.LicenseConfig) error {
|
|
|
|
|
machineID := utils.GetMachineID()
|
|
|
|
|
log.Printf("机器标识: %s", machineID)
|
|
|
|
|
|
|
|
|
|
licensePath := cfg.FilePath
|
|
|
|
|
if licensePath == "" {
|
|
|
|
|
licensePath = "license.json"
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-18 17:07:34 +08:00
|
|
|
// 读取授权文件
|
|
|
|
|
license, err := utils.ReadLicenseFile(licensePath)
|
2026-07-18 10:52:32 +08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 设置当前授权信息
|
2026-07-18 17:07:34 +08:00
|
|
|
utils.SetCurrentLicense(license)
|
2026-07-18 10:52:32 +08:00
|
|
|
|
2026-07-18 17:07:34 +08:00
|
|
|
// 根据网络类型选择验证方式
|
|
|
|
|
network := cfg.Network
|
|
|
|
|
if network == "" {
|
|
|
|
|
network = "internal"
|
2026-07-18 10:52:32 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-18 17:07:34 +08:00
|
|
|
if network == "external" && cfg.VerifyURL != "" {
|
|
|
|
|
// 外网: 调远程接口验证
|
|
|
|
|
resp, err := utils.ReadAndVerifyLicense(licensePath, cfg.VerifyURL, "seeyon-filesystem", "1.0")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !resp.Valid {
|
|
|
|
|
return fmt.Errorf("%s", resp.Message)
|
|
|
|
|
}
|
|
|
|
|
if resp.ExpireDays == -1 {
|
|
|
|
|
log.Printf("[授权] 企业版(外网) - 永久授权, 用户上限: %d, 功能: %s", resp.MaxUsers, license.Features)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("[授权] 企业版(外网) - 有效期剩余 %d 天, 用户上限: %d, 功能: %s", resp.ExpireDays, resp.MaxUsers, license.Features)
|
|
|
|
|
}
|
2026-07-18 10:52:32 +08:00
|
|
|
} else {
|
2026-07-18 17:07:34 +08:00
|
|
|
// 内网: 仅本地校验(解密 + 签名 + 机器码比对)
|
|
|
|
|
if err := utils.VerifyLicense(license); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
days := utils.GetLicenseExpireDays(license)
|
|
|
|
|
if days == -1 {
|
|
|
|
|
log.Printf("[授权] 企业版(内网) - 永久授权, 用户上限: %d, 功能: %s", license.MaxUsers, license.Features)
|
|
|
|
|
} else {
|
|
|
|
|
log.Printf("[授权] 企业版(内网) - 有效期剩余 %d 天, 用户上限: %d, 功能: %s", days, license.MaxUsers, license.Features)
|
|
|
|
|
}
|
2026-07-18 10:52:32 +08:00
|
|
|
}
|
2026-07-18 17:07:34 +08:00
|
|
|
|
2026-07-18 10:52:32 +08:00
|
|
|
return nil
|
|
|
|
|
}
|