Files
chiyiandDevin 834ecacdb9 cyi-box: Memphis-style toolbox admin platform
Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway
(Epay-compatible + CDK stock), license/card system (per-app appid, domain
binding), AI memory bank with built-in MCP server (/mcp, FTS5 search,
signed read links), server SSH monitor + terminal + SFTP, proxy pool with
protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL
autocomplete + import/export), disk share, vault (AES-GCM + TOTP),
bookmarks, dev tools.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-01 01:58:47 +08:00

83 lines
2.6 KiB
Go

// license_routes.go 卡密公开校验端点:GET/POST /v1/license?appid=xx&card=xx&domain=xx
// 首次调用激活并绑定域名(开始计时),之后仅限同域名校验;供其他项目做网络验证。
package service
import (
"context"
"strings"
"time"
"github.com/cyi-cc/fun"
"github.com/cyi-cc/cyi-box/backend/internal/db"
"github.com/cyi-cc/cyi-box/backend/internal/store"
)
func LicenseVerifyHandler(st *store.Store) fun.RouteHandler {
return func(c *fun.RouteCtx) error {
appid := strings.TrimSpace(c.Param("appid"))
card := strings.ToUpper(strings.TrimSpace(c.Param("card")))
domain := normalizeDomain(c.Param("domain"))
if appid == "" || card == "" || domain == "" {
return licenseJSON(c, 0, "缺少参数:appid / card / domain", nil)
}
ctx := context.Background()
app, err := st.Q().GetLicenseAppByAppid(ctx, appid)
if err != nil || app.Enabled != 1 {
return licenseJSON(c, 0, "项目不存在或已停用", nil)
}
lc, err := st.Q().GetLicenseCard(ctx, db.GetLicenseCardParams{AppID: app.ID, Card: card})
if err != nil {
return licenseJSON(c, 0, "卡密无效", nil)
}
now := time.Now().Unix()
if lc.ActivatedAt == 0 {
// 首激:绑域名 + 计时开始;hours=0 为永久卡(expires_at=0 永不过期)
expires := int64(0)
if lc.Hours > 0 {
expires = now + lc.Hours*3600
}
if err := st.Q().ActivateLicenseCard(ctx, db.ActivateLicenseCardParams{
Domain: domain, ActivatedAt: now, ExpiresAt: expires, ID: lc.ID,
}); err != nil {
return licenseJSON(c, 0, "激活失败", nil)
}
return licenseJSON(c, 1, "激活成功", map[string]any{
"domain": domain, "hours": lc.Hours, "expires_at": expires,
})
}
if lc.Domain != domain {
return licenseJSON(c, 0, "卡密已绑定其他域名", nil)
}
if lc.ExpiresAt > 0 && lc.ExpiresAt <= now {
return licenseJSON(c, 0, "卡密已过期", map[string]any{"expires_at": lc.ExpiresAt})
}
return licenseJSON(c, 1, "验证通过", map[string]any{
"domain": lc.Domain, "hours": lc.Hours, "expires_at": lc.ExpiresAt,
})
}
}
// normalizeDomain 取纯主机名:去协议/路径/端口/末尾点,小写。
func normalizeDomain(raw string) string {
d := strings.ToLower(strings.TrimSpace(raw))
if i := strings.Index(d, "://"); i >= 0 {
d = d[i+3:]
}
if i := strings.IndexAny(d, "/?#"); i >= 0 {
d = d[:i]
}
if i := strings.Index(d, ":"); i >= 0 {
d = d[:i]
}
return strings.TrimSuffix(d, ".")
}
func licenseJSON(c *fun.RouteCtx, code int, msg string, extra map[string]any) error {
obj := map[string]any{"code": code, "msg": msg}
for k, v := range extra {
obj[k] = v
}
return epayJSON(c, obj)
}