feat: enhance error handling with internationalized messages across services and locales

This commit is contained in:
mlogclub
2026-06-02 21:10:17 +08:00
parent 0b4a1b4594
commit 5b6c54de34
14 changed files with 109 additions and 42 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ var Embedding = &embedding{}
func (s *embedding) GetModel(ctx context.Context) (*models.AIConfig, error) {
config, err := GetEnabledAIConfig(enums.AIModelTypeEmbedding)
if err != nil {
return nil, errorsx.BusinessError(2001, "未配置可用的 Embedding 模型")
return nil, errorsx.BusinessErrorI18n(2001, "error.embeddingModel.noneEnabled")
}
return config, nil
}
+1 -1
View File
@@ -31,7 +31,7 @@ func newOpenAIClient(config models.AIConfig) openai.Client {
func GetEnabledAIConfig(modelType enums.AIModelType) (*models.AIConfig, error) {
item := repositories.AIConfigRepository.GetEnabled(sqls.DB(), modelType)
if item == nil {
return nil, errorsx.BusinessError(2005, "未配置可用的 AI 配置")
return nil, errorsx.BusinessErrorI18n(2005, "error.aiConfig.noneEnabled")
}
return item, nil
}
+3 -3
View File
@@ -104,7 +104,7 @@ func Enabled() bool {
func BuildAuthCodeURL(next string) (string, error) {
if !Enabled() {
return "", errorsx.BusinessError(1, "OIDC 登录未启用")
return "", errorsx.BusinessErrorI18n(1, "error.oidc.loginDisabled")
}
state, err := CreateState(next)
if err != nil {
@@ -115,7 +115,7 @@ func BuildAuthCodeURL(next string) (string, error) {
func ExchangeCode(ctx context.Context, code string) (*Profile, error) {
if !Enabled() {
return nil, errorsx.BusinessError(1, "OIDC 登录未启用")
return nil, errorsx.BusinessErrorI18n(1, "error.oidc.loginDisabled")
}
code = strings.TrimSpace(code)
if code == "" {
@@ -149,7 +149,7 @@ func ExchangeCode(ctx context.Context, code string) (*Profile, error) {
func CreateState(next string) (string, error) {
secret := stateSecret()
if secret == "" {
return "", errorsx.BusinessError(2, "OIDC 登录密钥未配置")
return "", errorsx.BusinessErrorI18n(2, "error.oidc.stateSecretMissing")
}
nonce, err := randomToken("os_")
if err != nil {
+2 -1
View File
@@ -1,6 +1,7 @@
package params
import (
"agent-desk/internal/pkg/errorsx"
"errors"
"fmt"
"log/slog"
@@ -182,7 +183,7 @@ func FormValue(ctx *gin.Context, name string) string {
func FormValueRequired(ctx *gin.Context, name string) (string, error) {
str := FormValue(ctx, name)
if len(str) == 0 {
return "", errors.New("参数:" + name + "不能为空")
return "", errorsx.InvalidParamI18n("error.param.required", name)
}
return str, nil
}
+33
View File
@@ -356,3 +356,36 @@ error.mcp.callToolFailed: "Failed to call MCP tool: %v"
error.mcp.connectServerFailed: "Failed to connect to MCP Server: %v"
error.wxwork.unsupportedOutboundMessageType: "Unsupported WeCom outbound message type: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "The current WeCom outbound message type is not supported yet: %s"
error.param.required: "Parameter %s is required."
error.oidc.loginDisabled: "OIDC login is not enabled."
error.oidc.stateSecretMissing: "OIDC login secret is not configured."
error.oidc.profileMissing: "OIDC user information does not exist."
error.oidc.bindingDisabled: "The current OIDC binding has been disabled."
error.oidc.boundUserMissing: "The system user bound to this OIDC account does not exist."
error.wxwork.loginDisabled: "WeCom login is not enabled."
error.wxwork.loginSecretMissing: "WeCom login secret is not configured."
error.wxwork.profileMissing: "WeCom user information does not exist."
error.wxwork.bindingDisabled: "The current WeCom binding has been disabled."
error.wxwork.boundUserMissing: "The system user bound to this WeCom account does not exist."
error.wxwork.userIDMissing: "Failed to get the WeCom user ID."
error.wxwork.userIDTaken: "The WeCom user ID is already used as a system username."
error.wxwork.mobileTaken: "The WeCom mobile number is already used by a system user."
error.wxwork.emailTaken: "The WeCom email address is already used by a system user."
error.wxwork.configIncomplete: "WeCom is not enabled or its configuration is incomplete."
error.customerSession.secretMissing: "Customer session secret is not configured."
error.conversation.createFailed: "Failed to create conversation."
error.conversation.tagNotFound: "Tag not found."
error.aiConfig.noneEnabled: "No available AI configuration is configured."
error.embeddingModel.noneEnabled: "No available Embedding model is configured."
error.agentTeamSchedule.startAtInvalid: "Invalid start time format."
error.agentTeamSchedule.startAtInvalidWithFormat: "Invalid start time format. Use yyyy-MM-dd HH:mm:ss or RFC3339."
error.agentTeamSchedule.endAtInvalid: "Invalid end time format."
error.agentTeamSchedule.endAtInvalidWithFormat: "Invalid end time format. Use yyyy-MM-dd HH:mm:ss or RFC3339."
error.agentTeamSchedule.startDateInvalid: "Invalid start date format."
error.agentTeamSchedule.startDateInvalidWithFormat: "Invalid start date format. Use yyyy-MM-dd."
error.agentTeamSchedule.endDateInvalid: "Invalid end date format."
error.agentTeamSchedule.endDateInvalidWithFormat: "Invalid end date format. Use yyyy-MM-dd."
error.agentTeamSchedule.startTimeInvalid: "Invalid start time format."
error.agentTeamSchedule.startTimeInvalidWithFormat: "Invalid start time format. Use HH:mm or HH:mm:ss."
error.agentTeamSchedule.endTimeInvalid: "Invalid end time format."
error.agentTeamSchedule.endTimeInvalidWithFormat: "Invalid end time format. Use HH:mm or HH:mm:ss."
+33
View File
@@ -356,3 +356,36 @@ error.mcp.callToolFailed: "调用 MCP 工具失败: %v"
error.mcp.connectServerFailed: "连接 MCP Server 失败: %v"
error.wxwork.unsupportedOutboundMessageType: "不支持的企业微信下行消息类型: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "当前暂不支持企业微信下行消息类型: %s"
error.param.required: "参数:%s不能为空"
error.oidc.loginDisabled: "OIDC 登录未启用"
error.oidc.stateSecretMissing: "OIDC 登录密钥未配置"
error.oidc.profileMissing: "OIDC 用户信息不存在"
error.oidc.bindingDisabled: "当前 OIDC 绑定已停用"
error.oidc.boundUserMissing: "OIDC 账号绑定的系统用户不存在"
error.wxwork.loginDisabled: "企业微信登录未启用"
error.wxwork.loginSecretMissing: "企业微信登录密钥未配置"
error.wxwork.profileMissing: "企业微信用户信息不存在"
error.wxwork.bindingDisabled: "当前企业微信绑定已停用"
error.wxwork.boundUserMissing: "企业微信账号绑定的系统用户不存在"
error.wxwork.userIDMissing: "企业微信用户ID获取失败"
error.wxwork.userIDTaken: "企业微信用户ID已被系统用户名占用"
error.wxwork.mobileTaken: "企业微信手机号已被系统用户占用"
error.wxwork.emailTaken: "企业微信邮箱已被系统用户占用"
error.wxwork.configIncomplete: "企业微信未启用或配置不完整"
error.customerSession.secretMissing: "客服会话密钥未配置"
error.conversation.createFailed: "创建会话失败"
error.conversation.tagNotFound: "标签不存在"
error.aiConfig.noneEnabled: "未配置可用的 AI 配置"
error.embeddingModel.noneEnabled: "未配置可用的 Embedding 模型"
error.agentTeamSchedule.startAtInvalid: "开始时间格式错误"
error.agentTeamSchedule.startAtInvalidWithFormat: "开始时间格式错误,请使用 yyyy-MM-dd HH:mm:ss 或 RFC3339"
error.agentTeamSchedule.endAtInvalid: "结束时间格式错误"
error.agentTeamSchedule.endAtInvalidWithFormat: "结束时间格式错误,请使用 yyyy-MM-dd HH:mm:ss 或 RFC3339"
error.agentTeamSchedule.startDateInvalid: "开始日期格式错误"
error.agentTeamSchedule.startDateInvalidWithFormat: "开始日期格式错误,请使用 yyyy-MM-dd"
error.agentTeamSchedule.endDateInvalid: "结束日期格式错误"
error.agentTeamSchedule.endDateInvalidWithFormat: "结束日期格式错误,请使用 yyyy-MM-dd"
error.agentTeamSchedule.startTimeInvalid: "开始时间格式错误"
error.agentTeamSchedule.startTimeInvalidWithFormat: "开始时间格式错误,请使用 HH:mm 或 HH:mm:ss"
error.agentTeamSchedule.endTimeInvalid: "结束时间格式错误"
error.agentTeamSchedule.endTimeInvalidWithFormat: "结束时间格式错误,请使用 HH:mm 或 HH:mm:ss"
@@ -92,11 +92,11 @@ func (s *agentTeamScheduleService) Count(cnd *sqls.Cnd) int64 {
}
func (s *agentTeamScheduleService) FindCalendarSchedules(req request.AgentTeamScheduleCalendarRequest) ([]models.AgentTeamSchedule, error) {
startAtValue, err := parseRequiredDateTime(req.StartAt, "开始时间格式错误")
startAtValue, err := parseRequiredDateTime(req.StartAt, "error.agentTeamSchedule.startAtInvalid", "error.agentTeamSchedule.startAtInvalidWithFormat")
if err != nil {
return nil, err
}
endAtValue, err := parseRequiredDateTime(req.EndAt, "结束时间格式错误")
endAtValue, err := parseRequiredDateTime(req.EndAt, "error.agentTeamSchedule.endAtInvalid", "error.agentTeamSchedule.endAtInvalidWithFormat")
if err != nil {
return nil, err
}
@@ -256,11 +256,11 @@ func (s *agentTeamScheduleService) buildScheduleModel(id, teamID int64, startAt,
if !slices.Contains(enums.StatusValues, team.Status) {
return nil, errorsx.InvalidParamI18n("error.e0174")
}
startAtValue, err := parseRequiredDateTime(startAt, "开始时间格式错误")
startAtValue, err := parseRequiredDateTime(startAt, "error.agentTeamSchedule.startAtInvalid", "error.agentTeamSchedule.startAtInvalidWithFormat")
if err != nil {
return nil, err
}
endAtValue, err := parseRequiredDateTime(endAt, "结束时间格式错误")
endAtValue, err := parseRequiredDateTime(endAt, "error.agentTeamSchedule.endAtInvalid", "error.agentTeamSchedule.endAtInvalidWithFormat")
if err != nil {
return nil, err
}
@@ -296,11 +296,11 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
if err != nil {
return nil, err
}
startDate, err := parseRequiredDate(req.StartDate, "开始日期格式错误")
startDate, err := parseRequiredDate(req.StartDate, "error.agentTeamSchedule.startDateInvalid", "error.agentTeamSchedule.startDateInvalidWithFormat")
if err != nil {
return nil, err
}
endDate, err := parseRequiredDate(req.EndDate, "结束日期格式错误")
endDate, err := parseRequiredDate(req.EndDate, "error.agentTeamSchedule.endDateInvalid", "error.agentTeamSchedule.endDateInvalidWithFormat")
if err != nil {
return nil, err
}
@@ -310,11 +310,11 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
if startDate.Before(startOfLocalDay(time.Now())) {
return nil, errorsx.InvalidParamI18n("error.e0085")
}
startClock, err := parseRequiredClock(req.StartTime, "开始时间格式错误")
startClock, err := parseRequiredClock(req.StartTime, "error.agentTeamSchedule.startTimeInvalid", "error.agentTeamSchedule.startTimeInvalidWithFormat")
if err != nil {
return nil, err
}
endClock, err := parseRequiredClock(req.EndTime, "结束时间格式错误")
endClock, err := parseRequiredClock(req.EndTime, "error.agentTeamSchedule.endTimeInvalid", "error.agentTeamSchedule.endTimeInvalidWithFormat")
if err != nil {
return nil, err
}
@@ -370,22 +370,22 @@ func (s *agentTeamScheduleService) buildBatchScheduleCandidates(req request.Agen
return candidates, nil
}
func parseRequiredDate(value, message string) (time.Time, error) {
func parseRequiredDate(value, emptyKey, formatKey string) (time.Time, error) {
value = strings.TrimSpace(value)
if value == "" {
return time.Time{}, errorsx.InvalidParam(message)
return time.Time{}, errorsx.InvalidParamI18n(emptyKey)
}
ret, err := time.ParseInLocation(time.DateOnly, value, time.Local)
if err != nil {
return time.Time{}, errorsx.InvalidParam(message + ",请使用 yyyy-MM-dd")
return time.Time{}, errorsx.InvalidParamI18n(formatKey)
}
return startOfLocalDay(ret), nil
}
func parseRequiredClock(value, message string) (time.Time, error) {
func parseRequiredClock(value, emptyKey, formatKey string) (time.Time, error) {
value = strings.TrimSpace(value)
if value == "" {
return time.Time{}, errorsx.InvalidParam(message)
return time.Time{}, errorsx.InvalidParamI18n(emptyKey)
}
layouts := []string{"15:04", "15:04:05"}
for _, layout := range layouts {
@@ -393,7 +393,7 @@ func parseRequiredClock(value, message string) (time.Time, error) {
return ret, nil
}
}
return time.Time{}, errorsx.InvalidParam(message + ",请使用 HH:mm 或 HH:mm:ss")
return time.Time{}, errorsx.InvalidParamI18n(formatKey)
}
func combineDateAndClock(date, clock time.Time) time.Time {
@@ -508,14 +508,14 @@ func uniquePositiveInt64s(values []int64) []int64 {
return ret
}
func parseRequiredDateTime(value, message string) (time.Time, error) {
func parseRequiredDateTime(value, emptyKey, formatKey string) (time.Time, error) {
value = strings.TrimSpace(value)
if value == "" {
return time.Time{}, errorsx.InvalidParam(message)
return time.Time{}, errorsx.InvalidParamI18n(emptyKey)
}
ret, err := parseDateTimeValue(value)
if err != nil {
return time.Time{}, errorsx.InvalidParam(message + ",请使用 yyyy-MM-dd HH:mm:ss 或 RFC3339")
return time.Time{}, errorsx.InvalidParamI18n(formatKey)
}
return ret, nil
}
+1 -1
View File
@@ -169,7 +169,7 @@ func (s *channelService) ParseWxWorkKFChannelConfig(raw string) (*dto.WxWorkKFCh
func (s *channelService) ListWxWorkKFAccounts() ([]response.WxWorkKFAccountResponse, error) {
if !wxwork.Enabled() || wxwork.GetWorkCli() == nil {
return nil, errorsx.BusinessError(1, "企业微信未启用或配置不完整")
return nil, errorsx.BusinessErrorI18n(1, "error.wxwork.configIncomplete")
}
cli, err := wxwork.GetWorkCli().GetKF()
if err != nil {
+1 -1
View File
@@ -159,7 +159,7 @@ func (s *conversationService) Create(externalUser openidentity.ExternalUser, cha
return nil, err
}
if conversation == nil {
return nil, errorsx.BusinessError(1, "创建会话失败")
return nil, errorsx.BusinessErrorI18n(1, "error.conversation.createFailed")
}
if !created {
return conversation, nil
@@ -5,9 +5,9 @@ import (
"agent-desk/internal/pkg/dto"
"agent-desk/internal/pkg/dto/request"
"agent-desk/internal/pkg/enums"
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/utils"
"agent-desk/internal/repositories"
"errors"
"agent-desk/internal/pkg/httpx/params"
@@ -78,7 +78,7 @@ func (s *conversationTagService) IsExists(conversationID int64, tagID int64) boo
func (s *conversationTagService) AddTag(req request.AddConversationTagRequest, operator *dto.AuthPrincipal) error {
tag := TagService.Get(req.TagID)
if tag == nil || tag.Status != enums.StatusOk {
return errors.New("标签不存在")
return errorsx.InvalidParamI18n("error.conversation.tagNotFound")
}
if s.IsExists(req.ConversationID, req.TagID) {
return nil
@@ -90,7 +90,7 @@ func (s *customerSessionService) Sign(channel *models.Channel, customer *models.
cfg := config.Current().CustomerSession
secret := strings.TrimSpace(cfg.Secret)
if secret == "" {
return "", time.Time{}, errorsx.BusinessError(1, "客服会话密钥未配置")
return "", time.Time{}, errorsx.BusinessErrorI18n(1, "error.customerSession.secretMissing")
}
if channel == nil || customer == nil {
return "", time.Time{}, errorsx.InvalidParamI18n("error.e0158")
@@ -168,7 +168,7 @@ func (s *customerSessionService) verifyToken(rawToken string) (*customerSessionC
cfg := config.Current().CustomerSession
secret := strings.TrimSpace(cfg.Secret)
if secret == "" {
return nil, errorsx.BusinessError(1, "客服会话密钥未配置")
return nil, errorsx.BusinessErrorI18n(1, "error.customerSession.secretMissing")
}
claims := &customerSessionClaims{}
token, err := jwt.ParseWithClaims(rawToken, claims, func(token *jwt.Token) (any, error) {
+3 -3
View File
@@ -61,7 +61,7 @@ func (s *oidcLoginService) ExchangeOIDCLoginTicket(ticket string) (*response.Log
func (s *oidcLoginService) loginWithOIDCProfile(profile *oidcLoginProfile, authCfg config.AuthConfig, clientIP, userAgent string) (*response.LoginResponse, error) {
if profile == nil || strings.TrimSpace(profile.Subject) == "" {
return nil, errorsx.BusinessError(2, "OIDC 用户信息不存在")
return nil, errorsx.BusinessErrorI18n(2, "error.oidc.profileMissing")
}
var ret *response.LoginResponse
@@ -78,11 +78,11 @@ func (s *oidcLoginService) loginWithOIDCProfile(profile *oidcLoginProfile, authC
}
} else {
if identity.Status != enums.StatusOk {
return errorsx.BusinessError(3, "当前 OIDC 绑定已停用")
return errorsx.BusinessErrorI18n(3, "error.oidc.bindingDisabled")
}
user = repositories.UserRepository.Get(ctx.Tx, identity.UserID)
if user == nil {
return errorsx.BusinessError(4, "OIDC 账号绑定的系统用户不存在")
return errorsx.BusinessErrorI18n(4, "error.oidc.boundUserMissing")
}
}
+9 -9
View File
@@ -24,7 +24,7 @@ type wxWorkLoginService struct {
func (s *wxWorkLoginService) BuildWxWorkLoginURL(next string) (string, error) {
if !wxwork.Enabled() {
return "", errorsx.BusinessError(1, "企业微信登录未启用")
return "", errorsx.BusinessErrorI18n(1, "error.wxwork.loginDisabled")
}
state, err := wxwork.CreateState(next)
if err != nil {
@@ -35,7 +35,7 @@ func (s *wxWorkLoginService) BuildWxWorkLoginURL(next string) (string, error) {
func (s *wxWorkLoginService) BuildWxWorkQRCodeLoginURL(next string) (string, error) {
if !wxwork.Enabled() {
return "", errorsx.BusinessError(1, "企业微信登录未启用")
return "", errorsx.BusinessErrorI18n(1, "error.wxwork.loginDisabled")
}
state, err := wxwork.CreateState(next)
if err != nil {
@@ -70,7 +70,7 @@ func (s *wxWorkLoginService) ExchangeWxWorkLoginTicket(ticket string) (*response
func (s *wxWorkLoginService) loginWithWxWorkProfile(profile *wxwork.LoginUser, authCfg config.AuthConfig, clientIP, userAgent string) (*response.LoginResponse, error) {
if profile == nil || strings.TrimSpace(profile.UserID) == "" {
return nil, errorsx.BusinessError(2, "企业微信用户信息不存在")
return nil, errorsx.BusinessErrorI18n(2, "error.wxwork.profileMissing")
}
var ret *response.LoginResponse
@@ -87,11 +87,11 @@ func (s *wxWorkLoginService) loginWithWxWorkProfile(profile *wxwork.LoginUser, a
}
} else {
if identity.Status != enums.StatusOk {
return errorsx.BusinessError(3, "当前企业微信绑定已停用")
return errorsx.BusinessErrorI18n(3, "error.wxwork.bindingDisabled")
}
user = repositories.UserRepository.Get(ctx.Tx, identity.UserID)
if user == nil {
return errorsx.BusinessError(4, "企业微信账号绑定的系统用户不存在")
return errorsx.BusinessErrorI18n(4, "error.wxwork.boundUserMissing")
}
}
@@ -218,19 +218,19 @@ func (s *wxWorkLoginService) resolveWxWorkAvatar(current string, profile *wxwork
func (s *wxWorkLoginService) checkWxWorkProfile(tx *gorm.DB, username, mobile string, email string) error {
if strs.IsBlank(username) {
return errorsx.BusinessError(5, "企业微信用户ID获取失败")
return errorsx.BusinessErrorI18n(5, "error.wxwork.userIDMissing")
}
if existing := repositories.UserRepository.GetByUsername(tx, username); existing != nil {
return errorsx.BusinessError(5, "企业微信用户ID已被系统用户名占用")
return errorsx.BusinessErrorI18n(5, "error.wxwork.userIDTaken")
}
if strs.IsNotBlank(mobile) {
if repositories.UserRepository.GetByMobile(tx, mobile) != nil {
return errorsx.BusinessError(6, "企业微信手机号已被系统用户占用")
return errorsx.BusinessErrorI18n(6, "error.wxwork.mobileTaken")
}
}
if strs.IsNotBlank(email) {
if repositories.UserRepository.GetByEmail(tx, email) != nil {
return errorsx.BusinessError(7, "企业微信邮箱已被系统用户占用")
return errorsx.BusinessErrorI18n(7, "error.wxwork.emailTaken")
}
}
return nil
+1 -1
View File
@@ -82,7 +82,7 @@ func BuildQRCodeLoginURL(state string) (string, error) {
func CreateState(next string) (string, error) {
secret := strings.TrimSpace(StateSecret())
if secret == "" {
return "", errorsx.BusinessError(1, "企业微信登录密钥未配置")
return "", errorsx.BusinessErrorI18n(1, "error.wxwork.loginSecretMissing")
}
payload := statePayload{
Next: sanitizeNextPath(next),