Files

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: "隐私政策",
}