Files
opsx-licence/backend/utils/license.go
T
Logan.Li 4ae318335c feat: 完成 opsx-licence 许可证管理平台全栈实现
基于 opsx-web-lite 框架(Go+Gin+Vue3+Element Plus)构建许可证管理服务器,
业务域参考 Licence 服务器 Web 版。

【后端 - 4层架构】
- 认证:单 Admin 表 + JWT(HS256)
- 核心授权域:Product / Customer / License / Activation
  · License 生成/验证/激活/机器绑定/心跳/续期/吊销
  · 客户端契约接口:/api/v1/license/{verify,activate,deactivate,heartbeat}
- 定价订单:Plan / Order / 支付网关配置
- 防重复购买:CustomerEntitlement + 幂等支付 webhook + 手动发放
- 分析审计:Dashboard 统计 / Analytics / AuditLog / Setting 管理

【前端】
- Dashboard(ECharts 图表)、产品、客户、License、计划、订单、权益、日志、设置
- 9 个完整 CRUD 业务页面 + 登录/布局

【部署】
- SQLite + 单二进制(//go:embed 嵌入前端)
- 开发:./run.sh start(前端5181/后端9001)
- 生产:./run.sh build && ./run.sh binary start
- 默认账号:admin/admin

Author: Logan.Li
2026-08-07 15:02:00 +08:00

90 lines
2.4 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 utils
import (
"crypto/rand"
"encoding/hex"
"fmt"
"strings"
"time"
)
// GenerateLicenseKey 生成 License 密钥(格式: LIC-YYYY-XXXX-XXXX-XXXX-XXXX) // Logan.Li
func GenerateLicenseKey() string {
bytes := make([]byte, 16)
_, _ = rand.Read(bytes)
year := time.Now().Year()
part1 := hex.EncodeToString(bytes[0:4])
part2 := hex.EncodeToString(bytes[4:8])
part3 := hex.EncodeToString(bytes[8:12])
part4 := hex.EncodeToString(bytes[12:16])
return fmt.Sprintf("LIC-%d-%s-%s-%s-%s",
year,
strings.ToUpper(part1[:4]),
strings.ToUpper(part2[:4]),
strings.ToUpper(part3[:4]),
strings.ToUpper(part4[:4]))
}
// GenerateOrderNo 生成唯一订单号(格式: ORD-YYYYMMDD-XXXXXXXX) // Logan.Li
func GenerateOrderNo() string {
bytes := make([]byte, 4)
_, _ = rand.Read(bytes)
return fmt.Sprintf("ORD-%s-%s", time.Now().Format("20060102"), strings.ToUpper(hex.EncodeToString(bytes)))
}
// CalculateDaysLeft 计算剩余天数(向下取整,最小为 0) // Logan.Li
func CalculateDaysLeft(endDate time.Time) int {
diff := endDate.Sub(time.Now())
days := int(diff.Hours() / 24)
if days < 0 {
return 0
}
return days
}
// IsLicenseExpired 检查 License 是否过期 // Logan.Li
func IsLicenseExpired(endDate time.Time) bool {
return time.Now().After(endDate)
}
// IsExpiringSoon 检查 License 是否即将到期 // Logan.Li
func IsExpiringSoon(endDate time.Time, warningDays int) bool {
daysLeft := CalculateDaysLeft(endDate)
return daysLeft > 0 && daysLeft <= warningDays
}
// ParseFeatures 解析产品功能列表 JSON 为字符串切片 // Logan.Li
func ParseFeatures(featuresJSON string) []string {
if featuresJSON == "" {
return []string{}
}
trimmed := strings.Trim(featuresJSON, "[]")
if trimmed == "" {
return []string{}
}
// 简单解析:去引号后按逗号分割 // Logan.Li
trimmed = strings.ReplaceAll(trimmed, `"`, "")
parts := strings.Split(trimmed, ",")
result := make([]string, 0, len(parts))
for _, p := range parts {
if s := strings.TrimSpace(p); s != "" {
result = append(result, s)
}
}
return result
}
// FormatFeatures 将功能列表格式化为 JSON 字符串 // Logan.Li
func FormatFeatures(features []string) string {
if len(features) == 0 {
return "[]"
}
quoted := make([]string, len(features))
for i, f := range features {
quoted[i] = fmt.Sprintf(`"%s"`, f)
}
return fmt.Sprintf("[%s]", strings.Join(quoted, ","))
}