422 lines
16 KiB
Go
422 lines
16 KiB
Go
package consts
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gogf/gf/v2/errors/gcode"
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
)
|
|
|
|
// Version can be set at build time via -ldflags "-X github.com/qianfree/team-api/internal/consts.Version=x.y.z".
|
|
// Falls back to reading the VERSION file, or "dev" if unavailable.
|
|
var Version = ""
|
|
|
|
func init() {
|
|
if Version != "" {
|
|
return
|
|
}
|
|
v := readVersionFile()
|
|
if v != "" {
|
|
Version = v
|
|
} else {
|
|
Version = "dev"
|
|
}
|
|
}
|
|
|
|
// readVersionFile tries multiple locations to find the VERSION file:
|
|
// 1. Current working directory (development mode)
|
|
// 2. Same directory as the running binary (deployment mode)
|
|
func readVersionFile() string {
|
|
// Candidate paths to search
|
|
candidates := []string{"VERSION"}
|
|
|
|
// Add binary's directory
|
|
if exe, err := os.Executable(); err == nil {
|
|
if exe, err = filepath.EvalSymlinks(exe); err == nil {
|
|
candidates = append(candidates, filepath.Join(filepath.Dir(exe), "VERSION"))
|
|
}
|
|
}
|
|
|
|
for _, path := range candidates {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if v := strings.TrimSpace(string(data)); v != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Business error variables (using gerror for full stack traces)
|
|
var (
|
|
ErrUnauthorized = gerror.NewCode(gcode.New(CodeUnauthorized, MsgUnauthorized, nil), MsgUnauthorized)
|
|
ErrKeyExpired = gerror.NewCode(gcode.New(CodeKeyExpired, MsgKeyExpired, nil), MsgKeyExpired)
|
|
ErrKeyDisabled = gerror.NewCode(gcode.New(CodeKeyDisabled, "API Key 已禁用", nil), "API Key 已禁用")
|
|
ErrTenantSuspended = gerror.NewCode(gcode.New(CodeTenantSuspended, MsgTenantSuspended, nil), MsgTenantSuspended)
|
|
ErrProjectNotActive = gerror.NewCode(gcode.New(CodeProjectNotActive, MsgProjectNotActive, nil), MsgProjectNotActive)
|
|
ErrChannelUnavailable = gerror.NewCode(gcode.New(CodeChannelUnavailable, MsgChannelUnavailable, nil), MsgChannelUnavailable)
|
|
ErrModelDisabled = gerror.NewCode(gcode.New(CodeModelDisabled, MsgModelDisabled, nil), MsgModelDisabled)
|
|
ErrTotpRequired = gerror.NewCode(gcode.New(CodeTotpRequired, MsgTotpRequired, nil), MsgTotpRequired)
|
|
ErrTotpInvalid = gerror.NewCode(gcode.New(CodeTotpInvalid, MsgTotpInvalid, nil), MsgTotpInvalid)
|
|
ErrTurnstileFailed = gerror.NewCode(gcode.New(CodeTurnstileFailed, MsgTurnstileFailed, nil), MsgTurnstileFailed)
|
|
ErrIpRestricted = gerror.NewCode(gcode.New(CodeIpRestricted, MsgIpRestricted, nil), MsgIpRestricted)
|
|
ErrCaptchaRequired = gerror.NewCode(gcode.New(CodeCaptchaRequired, MsgCaptchaRequired, nil), MsgCaptchaRequired)
|
|
ErrCaptchaFailed = gerror.NewCode(gcode.New(CodeCaptchaFailed, MsgCaptchaFailed, nil), MsgCaptchaFailed)
|
|
)
|
|
|
|
// Error codes for business logic
|
|
//
|
|
// 编号策略说明(解释段内出现的"空洞"与跳跃,避免误判为漏号):
|
|
// - HTTP 语义码(400/401/403/404/429/500)直接复用,不归入业务段。
|
|
// - 业务码从 10000 起,按功能域分段(每段预留约 100 号),新增域从下一个
|
|
// 百位段起分配(如 10400+),方便聚合统计与前端按域归类。
|
|
// - 段内编号按"先来先得"分配,删除/废弃的码不回收复用(避免历史日志里的旧码
|
|
// 被赋予新含义造成歧义),因此段内会出现空洞(如 10067/10070-72/10099)。
|
|
// - 跨段跳跃(如 10097→10403)源于不同功能域在不同迭代落地,分段时间线不同。
|
|
// - Demo/Plugin/Update/Team 等较新域集中在 10400+,与早期 10xxx 域拉开距离,
|
|
// 便于后续按前缀做特性灰度与告警分组。
|
|
//
|
|
// 新增错误码时:先归属功能域,在该域已用最大值后递增分配;新域从下一个空闲百位段起。
|
|
const (
|
|
BusinessErrorCodePrefix = 10000
|
|
|
|
// Common errors
|
|
CodeSuccess = 0
|
|
CodeBadRequest = 400
|
|
CodeUnauthorized = 401
|
|
CodeForbidden = 403
|
|
CodeNotFound = 404
|
|
CodeTooManyRequests = 429
|
|
CodeInternalServerError = 500
|
|
|
|
// Business errors (10000+)
|
|
CodeInsufficientBalance = 10001
|
|
CodeQuotaExceeded = 10002
|
|
CodeChannelUnavailable = 10003
|
|
CodeModelDisabled = 10004
|
|
CodeTenantSuspended = 10005
|
|
CodeKeyExpired = 10006
|
|
CodeKeyScopeDenied = 10007
|
|
CodeInvitationExpired = 10008
|
|
CodeInvitationUsed = 10009
|
|
CodeAccountLocked = 10010
|
|
CodeInvalidCredentials = 10011
|
|
CodeEmailVerifyExpired = 10012
|
|
CodeEmailVerifyUsed = 10013
|
|
CodeRateLimitExceeded = 10014
|
|
CodePasswordTooWeak = 10015
|
|
CodeUsernameExists = 10016
|
|
CodeTenantCodeExists = 10017
|
|
CodeSessionExpired = 10018
|
|
CodeTokenExpired = 10019
|
|
CodeTokenRevoked = 10020
|
|
CodeMemberLimitReached = 10021
|
|
CodeVerifyCodeInvalid = 10022
|
|
CodeOldPasswordWrong = 10023
|
|
|
|
// Payment errors (10024+)
|
|
CodePaymentChannelNotFound = 10024
|
|
CodePaymentChannelDisabled = 10025
|
|
CodePaymentCreateFailed = 10026
|
|
CodePaymentCallbackFailed = 10027
|
|
CodePaymentInvalidConfig = 10028
|
|
CodePaymentBelowMinTopup = 10029
|
|
CodeOrderAlreadyPaid = 10030
|
|
|
|
// Project errors (10031+)
|
|
CodeProjectNotFound = 10031
|
|
CodeProjectNotActive = 10032
|
|
CodeProjectKeyForbidden = 10033
|
|
CodeConsentRequired = 10034
|
|
|
|
// Data governance errors (10035+)
|
|
CodeExportAlreadyPending = 10035
|
|
CodeExportNotFound = 10036
|
|
CodeExportExpired = 10037
|
|
CodeDeletionRequestExists = 10038
|
|
CodeDeletionRequestNotFound = 10039
|
|
CodeTaskNotRetryable = 10040
|
|
CodeOnboardingCompleted = 10041
|
|
|
|
// Status page errors (10042+)
|
|
CodeComponentNotFound = 10042
|
|
CodeIncidentNotFound = 10043
|
|
CodeMaintenanceNotFound = 10044
|
|
CodeSubscriptionExists = 10045
|
|
CodeInvalidConfirmToken = 10046
|
|
|
|
// Security errors (10047+)
|
|
CodeTotpRequired = 10047
|
|
CodeTotpInvalid = 10048
|
|
CodeTotpAlreadyEnabled = 10049
|
|
CodeTotpNotEnabled = 10050
|
|
CodeBackupCodeInvalid = 10051
|
|
CodeTurnstileFailed = 10052
|
|
CodeIpRestricted = 10053
|
|
CodeHighRisk2FARequired = 10054
|
|
CodeNoAvailableApiKey = 10055
|
|
CodeCaptchaRequired = 10057
|
|
CodeCaptchaFailed = 10058
|
|
|
|
// OAuth errors (10059+)
|
|
CodeOAuthDisabled = 10059
|
|
CodeOAuthInvalidCode = 10060
|
|
CodeOAuthTokenFailed = 10061
|
|
CodeOAuthAlreadyLinked = 10062
|
|
|
|
// Feedback & Changelog errors (10063+)
|
|
CodeFeedbackNotFound = 10063
|
|
CodeChangelogNotFound = 10064
|
|
CodeKeyDisabled = 10065
|
|
|
|
// Response cache errors (10068+)
|
|
CodeCacheDisabled = 10068
|
|
CodeCacheFull = 10069
|
|
|
|
// Help Center errors (10073+)
|
|
CodeHelpCategoryNotFound = 10073
|
|
CodeHelpCategorySlugExists = 10074
|
|
CodeHelpArticleNotFound = 10075
|
|
CodeHelpArticleSlugExists = 10076
|
|
CodeHelpRateLimitExceeded = 10099
|
|
|
|
// Registration errors
|
|
CodeRegistrationDisabled = 10077
|
|
CodeEmailVerificationDisabled = 10082
|
|
CodeIpRateLimitExceeded = 10090 // IP级别限流
|
|
CodeGlobalRateLimitExceeded = 10091 // 全局限流
|
|
CodeForbiddenWord = 10096 // 名称包含禁用词
|
|
CodeEmailExists = 10097 // 邮箱已被使用
|
|
CodeInvalidTenantName = 10098 // 组织名称长度不合法
|
|
|
|
// Setup errors
|
|
CodeSetupCompleted = 10078
|
|
CodeSetupInvalidUsername = 10079
|
|
CodeSetupPasswordMismatch = 10080
|
|
CodeSetupNotInitialized = 10081
|
|
|
|
// Model errors
|
|
CodeModelNameExists = 10083
|
|
|
|
// Model group errors
|
|
CodeModelGroupNotFound = 10084
|
|
CodeModelGroupCodeExists = 10085
|
|
CodeModelGroupHasTenants = 10086
|
|
|
|
// Model import errors
|
|
CodeModelImportInvalidFile = 10087
|
|
CodeModelImportBadVersion = 10088
|
|
|
|
// Username validation
|
|
CodeInvalidUsername = 10089
|
|
|
|
// Agreement errors
|
|
CodeAgreementNotFound = 10092
|
|
CodeAgreementNotDraft = 10093
|
|
CodeAgreementVersionExists = 10094
|
|
CodeAgreementCodeInvalid = 10095
|
|
|
|
// Demo mode
|
|
CodeDemoModeRestricted = 10403
|
|
|
|
// Plugin errors (10404+)
|
|
CodePluginNotFound = 10404
|
|
CodePluginAlreadyInstalled = 10405
|
|
CodePluginNotInstalled = 10406
|
|
CodePluginNotEnabled = 10407
|
|
CodePluginAlreadyEnabled = 10408
|
|
CodePluginDependencyMissing = 10409
|
|
CodePluginInstallFailed = 10410
|
|
|
|
// Update errors (10411+)
|
|
CodeUpdateAlreadyRunning = 10411
|
|
CodeUpdateNotAvailable = 10412
|
|
CodeUpdateDownloadFailed = 10413
|
|
CodeUpdateVerifyFailed = 10414
|
|
CodeUpdateReplaceFailed = 10415
|
|
CodeUpdateRollbackFailed = 10416
|
|
CodeUpdateNotSupported = 10417
|
|
|
|
// Team feature gating
|
|
CodeTeamNotEnabled = 10418
|
|
)
|
|
|
|
// Common error messages
|
|
const (
|
|
MsgSuccess = "ok"
|
|
MsgBadRequest = "请求参数错误"
|
|
MsgUnauthorized = "未授权"
|
|
MsgForbidden = "无权限访问"
|
|
MsgNotFound = "资源不存在"
|
|
MsgTooManyRequests = "请求过于频繁"
|
|
MsgInternalServerError = "服务器内部错误"
|
|
|
|
MsgInsufficientBalance = "余额不足"
|
|
MsgQuotaExceeded = "额度已用完"
|
|
MsgChannelUnavailable = "没有可用的渠道"
|
|
MsgModelDisabled = "模型已禁用"
|
|
MsgServiceUnavailable = "服务暂时不可用,请稍后重试"
|
|
MsgModelUnavailable = "当前模型暂时不可用"
|
|
MsgRequestTimeout = "请求超时,请重试"
|
|
MsgRequestFailed = "请求处理失败"
|
|
MsgTenantSuspended = "租户已被暂停"
|
|
MsgKeyExpired = "API Key 已过期"
|
|
MsgKeyScopeDenied = "API Key 无权访问此接口"
|
|
MsgInvitationExpired = "邀请链接已过期"
|
|
MsgInvitationUsed = "邀请链接已被使用"
|
|
MsgAccountLocked = "账号已被锁定"
|
|
MsgInvalidCredentials = "用户名或密码错误"
|
|
MsgEmailVerifyExpired = "验证码已过期"
|
|
MsgEmailVerifyUsed = "验证码已使用"
|
|
MsgRateLimitExceeded = "请求频率超限"
|
|
MsgPasswordTooWeak = "密码不符合策略"
|
|
MsgUsernameExists = "用户名已存在"
|
|
MsgEmailExists = "该邮箱已被使用"
|
|
MsgTenantCodeExists = "租户代码已存在"
|
|
MsgTeamNotEnabled = "团队功能未启用,请先设置组织代码"
|
|
MsgSessionExpired = "会话已过期"
|
|
MsgTokenExpired = "Token 已过期"
|
|
MsgTokenRevoked = "Token 已被撤销"
|
|
MsgMemberLimitReached = "成员数已达上限"
|
|
MsgVerifyCodeInvalid = "验证码错误"
|
|
MsgOldPasswordWrong = "原密码错误"
|
|
// Payment errors
|
|
MsgPaymentChannelNotFound = "支付渠道不存在"
|
|
MsgPaymentChannelDisabled = "支付渠道已禁用"
|
|
MsgPaymentCreateFailed = "创建支付失败"
|
|
MsgPaymentCallbackFailed = "支付回调处理失败"
|
|
MsgPaymentInvalidConfig = "支付渠道配置无效"
|
|
MsgPaymentBelowMinTopup = "充值金额低于最低限额"
|
|
MsgOrderAlreadyPaid = "订单已支付"
|
|
// Project errors
|
|
MsgProjectNotFound = "项目不存在"
|
|
MsgProjectNotActive = "项目状态不可用"
|
|
MsgProjectKeyForbidden = "仅管理员可管理项目密钥"
|
|
MsgConsentRequired = "需要同意法律条款后才能继续"
|
|
// Data governance errors
|
|
MsgExportAlreadyPending = "已有导出任务进行中"
|
|
MsgExportNotFound = "导出任务不存在"
|
|
MsgExportExpired = "导出文件已过期"
|
|
MsgDeletionRequestExists = "已有删除请求进行中"
|
|
MsgDeletionRequestNotFound = "删除请求不存在"
|
|
MsgTaskNotRetryable = "任务不可重试"
|
|
MsgOnboardingCompleted = "引导流程已完成"
|
|
// Status page errors
|
|
MsgComponentNotFound = "组件不存在"
|
|
MsgIncidentNotFound = "事件不存在"
|
|
MsgMaintenanceNotFound = "维护计划不存在"
|
|
MsgSubscriptionExists = "该邮箱已订阅"
|
|
MsgInvalidConfirmToken = "确认令牌无效"
|
|
// Security errors
|
|
MsgTotpRequired = "需要双因素认证"
|
|
MsgTotpInvalid = "验证码错误"
|
|
MsgTotpAlreadyEnabled = "双因素认证已启用"
|
|
MsgTotpNotEnabled = "未启用双因素认证"
|
|
MsgBackupCodeInvalid = "恢复码无效"
|
|
MsgTurnstileFailed = "人机验证失败"
|
|
MsgIpRestricted = "IP 地址不在白名单中"
|
|
MsgHighRisk2FARequired = "高风险操作需要二次验证"
|
|
MsgCaptchaRequired = "请完成滑块验证"
|
|
MsgCaptchaFailed = "滑块验证失败"
|
|
// OAuth errors
|
|
MsgOAuthDisabled = "该 OAuth 供应商未启用"
|
|
MsgOAuthInvalidCode = "OAuth 授权码无效"
|
|
MsgOAuthTokenFailed = "获取 OAuth 令牌失败"
|
|
MsgOAuthAlreadyLinked = "该 OAuth 账号已绑定其他用户"
|
|
// Feedback & Changelog errors
|
|
MsgFeedbackNotFound = "反馈不存在"
|
|
MsgChangelogNotFound = "更新日志不存在"
|
|
// Response cache errors
|
|
MsgCacheDisabled = "响应缓存未启用"
|
|
MsgCacheFull = "缓存空间已满"
|
|
// Help Center errors
|
|
MsgHelpCategoryNotFound = "帮助分类不存在"
|
|
MsgHelpCategorySlugExists = "分类标识已存在"
|
|
MsgHelpArticleNotFound = "帮助文章不存在"
|
|
MsgHelpArticleSlugExists = "文章标识已存在"
|
|
MsgHelpRateLimitExceeded = "访问过于频繁,请稍后再试"
|
|
// Registration errors
|
|
MsgRegistrationDisabled = "注册功能已关闭"
|
|
MsgEmailVerificationDisabled = "注册邮箱验证未启用"
|
|
MsgIpRateLimitExceeded = "该IP注册频率过高,请稍后再试"
|
|
MsgGlobalRateLimitExceeded = "系统注册繁忙,请稍后再试"
|
|
|
|
// Setup errors
|
|
MsgSetupCompleted = "系统已完成初始化"
|
|
MsgSetupInvalidUsername = "用户名格式无效,仅支持字母和数字,长度3-20"
|
|
MsgSetupPasswordMismatch = "两次输入的密码不一致"
|
|
MsgSetupNotInitialized = "系统未初始化,请先完成设置"
|
|
|
|
// Model errors
|
|
MsgModelNameExists = "模型名称已存在"
|
|
|
|
// Model group errors
|
|
MsgModelGroupNotFound = "模型分组不存在"
|
|
MsgModelGroupCodeExists = "模型分组标识已存在"
|
|
MsgModelGroupHasTenants = "该分组下存在关联租户,无法删除"
|
|
// Model import errors
|
|
MsgModelImportInvalidFile = "导入文件格式无效"
|
|
MsgModelImportBadVersion = "导入文件版本不兼容"
|
|
|
|
// Username validation
|
|
MsgInvalidUsername = "用户名仅支持英文字母和数字,不能为纯数字"
|
|
|
|
// Forbidden word
|
|
MsgForbiddenWord = "名称包含禁用词,请修改后重试"
|
|
|
|
// Tenant name validation:汉字最多8个、字母最多16个(汉字按2计、其余按1计,总宽度≤16)
|
|
MsgInvalidTenantName = "组织名称长度超出限制(汉字最多8个,字母最多16个)"
|
|
|
|
// Agreement errors
|
|
MsgAgreementNotFound = "协议版本不存在"
|
|
MsgAgreementNotDraft = "仅草稿状态的协议可修改或删除"
|
|
MsgAgreementVersionExists = "该协议类型的此版本号已存在"
|
|
MsgAgreementCodeInvalid = "协议标识码无效"
|
|
)
|
|
|
|
// Demo mode messages
|
|
const (
|
|
MsgDemoModeRestricted = "演示环境,数据不可修改"
|
|
// Plugin errors
|
|
MsgPluginNotFound = "插件不存在"
|
|
MsgPluginAlreadyInstalled = "插件已安装"
|
|
MsgPluginNotInstalled = "插件未安装"
|
|
MsgPluginNotEnabled = "插件未启用"
|
|
MsgPluginAlreadyEnabled = "插件已启用"
|
|
MsgPluginDependencyMissing = "插件依赖未满足"
|
|
MsgPluginInstallFailed = "插件安装失败"
|
|
// Update errors
|
|
MsgUpdateAlreadyRunning = "系统更新正在进行中"
|
|
MsgUpdateNotAvailable = "当前已是最新版本"
|
|
MsgUpdateDownloadFailed = "下载更新文件失败"
|
|
MsgUpdateVerifyFailed = "更新文件校验失败"
|
|
MsgUpdateReplaceFailed = "替换二进制文件失败"
|
|
MsgUpdateRollbackFailed = "回滚失败"
|
|
MsgUpdateNotSupported = "当前部署模式不支持自动更新"
|
|
)
|
|
|
|
// Agreement code constants
|
|
const (
|
|
AgreementCodeTerms = "terms" // 用户协议/服务条款
|
|
AgreementCodePrivacy = "privacy" // 隐私政策
|
|
)
|
|
|
|
// 系统配置 key:本位币(系统初始化向导选定后不可更改,bil_ 记账层与全站显示货币)
|
|
const OptionKeyBillingCurrency = "billing_currency"
|
|
|
|
// 本位币取值
|
|
const (
|
|
BillingCurrencyUSD = "USD"
|
|
BillingCurrencyCNY = "CNY"
|
|
)
|
|
|
|
// ValidAgreementCodes 有效协议标识码集合(创建时校验)
|
|
var ValidAgreementCodes = map[string]string{
|
|
AgreementCodeTerms: "用户协议",
|
|
AgreementCodePrivacy: "隐私政策",
|
|
}
|