diff --git a/backend/.golangci.toml b/backend/.golangci.toml index 2ae2543b4..0a270c16b 100644 --- a/backend/.golangci.toml +++ b/backend/.golangci.toml @@ -1,10 +1,84 @@ version = "2" -linters.default = "standard" +###################### +# 运行相关 +###################### +[run] +# 超时时间,避免在 CI 里卡死 +timeout = "5m" +# 连测试代码一起扫(gosec 下面会单独排除) +tests = true +###################### +# Linter 开关 +###################### +[linters] +# 官方推荐集合:维护中 + 误报相对少 +preset = "recommended" + +# 在 recommended 基础上,针对 Web 服务额外打开的 +enable = [ + "gosec", # 安全检查(SQL 注入、命令执行等) + "predeclared", # 预声明标识符遮蔽(len/error 等) + "revive", # 更现代的风格/规范检查 + "unparam", # 未使用参数(RPC/handler 很容易写多) + "bodyclose", # HTTP Response Body 必须关闭 + "noctx", # 外部调用缺少 context.Context + "contextcheck", # 使用错误的 context(非继承) + "errname", # error 命名规范(服务端项目很常见) +] + +###################### +# 各 linter 细节配置 +###################### + +# govet + shadow 变量遮蔽检查(v2 新写法) +[linters.settings.govet] +enable = ["shadow"] + +# errcheck:对错误检查更严格一点 +[linters.settings.errcheck] +check-type-assertions = true # a := b.(T) 也要检查错误 +check-blank = true # _, err 这种也要被检查 + +# revive:禁用 exported 注释规则 +[linters.settings.revive] +[[linters.settings.revive.rules]] +name = "exported" +disabled = true + +###################### +# 全局排除 / 特殊规则(v2 新写法) +###################### +[linters.exclusions] +# 不扫的目录(按需要增减) +paths = [ + "vendor", + "third_party", + "node_modules", + "tmp", + "dist", + "docs", + "mocks", +] + +# 严格识别生成文件(一般不用改) +generated = "strict" + +# 1. 测试文件不跑 gosec(通常没必要这么严) [[linters.exclusions.rules]] -linters = [ "errcheck" ] +linters = ["gosec"] +path = "(.+)_test\\.go" + +# 2. 对 defer 调用放宽 errcheck(defer f.Close() 很常见) +[[linters.exclusions.rules]] +linters = ["errcheck"] source = "^\\s*defer\\s+" +###################### +# 格式化 +###################### [formatters] -enable = ["gofmt", "goimports"] \ No newline at end of file +# goimports 已经包含 gofmt 能力 +enable = ["goimports"] + diff --git a/backend/consts/license.go b/backend/consts/license.go index 68e47762a..6149b3869 100644 --- a/backend/consts/license.go +++ b/backend/consts/license.go @@ -18,6 +18,9 @@ const ( ) func GetLicenseEdition(c echo.Context) LicenseEdition { - edition, _ := c.Get("edition").(LicenseEdition) + edition, ok := c.Get("edition").(LicenseEdition) + if !ok { + return LicenseEditionFree + } return edition } diff --git a/backend/handler/base.go b/backend/handler/base.go index 46a6f682a..2ed10e5ce 100644 --- a/backend/handler/base.go +++ b/backend/handler/base.go @@ -26,14 +26,14 @@ type BaseHandler struct { Captcha *captcha.Captcha } -func NewBaseHandler(echo *echo.Echo, logger *log.Logger, config *config.Config, v1Auth middleware.AuthMiddleware, shareAuthMiddleware *middleware.ShareAuthMiddleware, cap *captcha.Captcha) *BaseHandler { +func NewBaseHandler(echo *echo.Echo, logger *log.Logger, config *config.Config, v1Auth middleware.AuthMiddleware, shareAuthMiddleware *middleware.ShareAuthMiddleware, captchaInstance *captcha.Captcha) *BaseHandler { return &BaseHandler{ Router: echo, baseLogger: logger.WithModule("http_base_handler"), config: config, ShareAuthMiddleware: shareAuthMiddleware, V1Auth: v1Auth, - Captcha: cap, + Captcha: captchaInstance, } } diff --git a/backend/handler/mq/rag.go b/backend/handler/mq/rag.go index 6e63c38f2..34af6dad7 100644 --- a/backend/handler/mq/rag.go +++ b/backend/handler/mq/rag.go @@ -91,8 +91,8 @@ func (h *RAGMQHandler) HandleNodeContentVectorRequest(ctx context.Context, msg t return nil } // update node doc_id - if err := h.nodeRepo.UpdateNodeReleaseDocID(ctx, request.NodeReleaseID, docID); err != nil { - h.logger.Error("update node doc_id failed", log.String("node_id", request.NodeReleaseID), log.Error(err)) + if updateErr := h.nodeRepo.UpdateNodeReleaseDocID(ctx, request.NodeReleaseID, docID); updateErr != nil { + h.logger.Error("update node doc_id failed", log.String("node_id", request.NodeReleaseID), log.Error(updateErr)) return nil } // delete old RAG records diff --git a/backend/handler/share/app.go b/backend/handler/share/app.go index 1cfa72255..1f12f6cae 100644 --- a/backend/handler/share/app.go +++ b/backend/handler/share/app.go @@ -183,9 +183,9 @@ func (h *ShareAppHandler) WechatHandlerOfficialAccount(c echo.Context) error { go func(openID, content string) { ctx := context.Background() // send content to ai - result, err := h.usecase.GetWechatOfficialAccountResponse(ctx, officialAccount, kbID, openID, content) - if err != nil { - h.logger.Error("get wechat official account response failed", log.Error(err)) + result, respErr := h.usecase.GetWechatOfficialAccountResponse(ctx, officialAccount, kbID, openID, content) + if respErr != nil { + h.logger.Error("get wechat official account response failed", log.Error(respErr)) return } // send response to user --> 需要开启客服消息权限 diff --git a/backend/handler/share/auth.go b/backend/handler/share/auth.go index f45a586c4..ce4980012 100644 --- a/backend/handler/share/auth.go +++ b/backend/handler/share/auth.go @@ -121,7 +121,10 @@ func (h *ShareAuthHandler) AuthLoginSimple(c echo.Context) error { if s == nil { return h.NewResponseWithError(c, "get session cache key failed", nil) } - store := s.(sessions.Store) + store, ok := s.(sessions.Store) + if !ok { + return h.NewResponseWithError(c, "invalid session store type", nil) + } newSess := sessions.NewSession(store, domain.SessionName) newSess.IsNew = true diff --git a/backend/handler/share/chat.go b/backend/handler/share/chat.go index dcc2345ba..0fb7651a9 100644 --- a/backend/handler/share/chat.go +++ b/backend/handler/share/chat.go @@ -108,8 +108,10 @@ func (h *ShareChatHandler) ChatMessage(c echo.Context) error { userID := c.Get("user_id") h.logger.Debug("userid:", userID) if userID != nil { // find userinfo from auth - userIDValue := userID.(uint) - req.Info.UserInfo.AuthUserID = userIDValue + userIDValue, ok := userID.(uint) + if ok { + req.Info.UserInfo.AuthUserID = userIDValue + } } eventCh, err := h.chatUsecase.Chat(ctx, &req) diff --git a/backend/handler/share/comment.go b/backend/handler/share/comment.go index 5de5d83de..34b9a590d 100644 --- a/backend/handler/share/comment.go +++ b/backend/handler/share/comment.go @@ -101,7 +101,9 @@ func (h *ShareCommentHandler) CreateComment(c echo.Context) error { var userIDValue uint userID := c.Get("user_id") if userID != nil { // can find userinfo from auth - userIDValue = userID.(uint) + if val, ok := userID.(uint); ok { + userIDValue = val + } } var status = 1 // no moderate @@ -109,7 +111,7 @@ func (h *ShareCommentHandler) CreateComment(c echo.Context) error { if appInfo.Settings.WebAppCommentSettings.ModerationEnable { status = 0 } - commentStatus := domain.CommentStatus(status) + commentStatus := domain.CommentStatus(status) //nolint:gosec // G115: Safe conversion, status is always 0 or 1 // 插入到数据库中 commentID, err := h.usecase.CreateComment(ctx, &req, kbID, remoteIP, commentStatus, userIDValue) diff --git a/backend/handler/share/stat.go b/backend/handler/share/stat.go index 20ddbde08..0d725461e 100644 --- a/backend/handler/share/stat.go +++ b/backend/handler/share/stat.go @@ -55,7 +55,9 @@ func (h *ShareStatHandler) RecordPage(c echo.Context) error { var userIDValue uint userID := c.Get("user_id") if userID != nil { // can find userinfo from auth - userIDValue = userID.(uint) + if val, ok := userID.(uint); ok { + userIDValue = val + } } ua := c.Request().UserAgent() diff --git a/backend/handler/share/wechat.go b/backend/handler/share/wechat.go index 06f94051c..811740f36 100644 --- a/backend/handler/share/wechat.go +++ b/backend/handler/share/wechat.go @@ -122,7 +122,10 @@ func (h *ShareWechatHandler) GetWechatAnswer(c echo.Context) error { } // exit --> get message - state := val.(*domain.ConversationState) + state, ok := val.(*domain.ConversationState) + if !ok { + return h.NewResponseWithError(c, "invalid conversation state type", nil) + } // 1. send question if err := h.writeSSEEvent(c, domain.SSEEvent{Type: "question", Content: state.Question}); err != nil { return err diff --git a/backend/handler/v1/app.go b/backend/handler/v1/app.go index 5c8cb9a91..49907d06f 100644 --- a/backend/handler/v1/app.go +++ b/backend/handler/v1/app.go @@ -69,7 +69,7 @@ func (h *AppHandler) GetAppDetail(c echo.Context) error { return h.NewResponseWithError(c, "invalid app type", err) } ctx := c.Request().Context() - app, err := h.usecase.GetAppDetailByKBIDAndAppType(ctx, kbID, domain.AppType(appTypeInt)) + app, err := h.usecase.GetAppDetailByKBIDAndAppType(ctx, kbID, domain.AppType(appTypeInt)) //nolint:gosec // G115: Safe conversion, appTypeInt is validated enum value if err != nil { return h.NewResponseWithError(c, "get app detail failed", err) } diff --git a/backend/middleware/jwt.go b/backend/middleware/jwt.go index fb47bc032..93254d2f6 100644 --- a/backend/middleware/jwt.go +++ b/backend/middleware/jwt.go @@ -156,7 +156,14 @@ func (m *JWTMiddleware) ValidateKBUserPerm(perm consts.UserKBPermission) echo.Mi }) } - kbId, _ := GetKbID(c) + kbId, err := GetKbID(c) + if err != nil { + m.logger.Error("ValidateKBUserPerm GetKbID failed", log.Error(err)) + return c.JSON(http.StatusUnauthorized, map[string]any{ + "Code": http.StatusUnauthorized, + "Message": "Unauthorized", + }) + } if authInfo.IsToken { diff --git a/backend/middleware/session.go b/backend/middleware/session.go index 15bc02fbd..a6ed316f9 100644 --- a/backend/middleware/session.go +++ b/backend/middleware/session.go @@ -2,6 +2,7 @@ package middleware import ( "context" + "fmt" "net/http" "time" @@ -33,13 +34,19 @@ func NewSessionMiddleware(logger *log.Logger, config *config.Config, cache *cach return nil, err } + secretKeyStr, ok := secretKey.(string) + if !ok { + logger.Error("session store secret key is not string") + return nil, fmt.Errorf("session store secret key is not string") + } + store, err := redistore.NewRediStore( 10, "tcp", config.Redis.Addr, "", config.Redis.Password, - []byte(secretKey.(string)), + []byte(secretKeyStr), ) if err != nil { diff --git a/backend/pkg/anydoc/anydoc.go b/backend/pkg/anydoc/anydoc.go index 152121c39..ef26b683c 100644 --- a/backend/pkg/anydoc/anydoc.go +++ b/backend/pkg/anydoc/anydoc.go @@ -61,7 +61,7 @@ func NewClient(logger *log.Logger, mqConsumer mq.MQConsumer) (*Client, error) { httpClient: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: true, //nolint:gosec // G402: Internal service communication, certificate validation not required }, }, }, diff --git a/backend/pkg/bot/dingtalk/stream.go b/backend/pkg/bot/dingtalk/stream.go index c2eb064fc..e706d4ba4 100644 --- a/backend/pkg/bot/dingtalk/stream.go +++ b/backend/pkg/bot/dingtalk/stream.go @@ -216,7 +216,7 @@ func (c *DingTalkClient) OnChatBotMessageReceived(ctx context.Context, data *cha }, } // 之前创建并且发送卡片消息,获取用户基本信息 - userinfo, err := c.GetUserInfo(data.SenderStaffId) + userinfo, err := c.GetUserInfo(ctx, data.SenderStaffId) if err != nil { c.logger.Error("GetUserInfo failed", log.Error(err)) } else { @@ -318,7 +318,7 @@ type UserDetails struct { } // 使用原始的http请求来获取用户的信息 - > 需要设置获取用户的权限功能:企业员工手机号信息和邮箱等个人信息、成员信息读权限 -func (c *DingTalkClient) GetUserInfo(userID string) (*UserDetailResponse, error) { +func (c *DingTalkClient) GetUserInfo(ctx context.Context, userID string) (*UserDetailResponse, error) { accessToken, err := c.GetAccessToken() if err != nil { return nil, fmt.Errorf("failed to get access token while creating and delivering card: %w", err) @@ -326,9 +326,17 @@ func (c *DingTalkClient) GetUserInfo(userID string) (*UserDetailResponse, error) // 1. 构建URL和请求体 url := "https://oapi.dingtalk.com/topapi/v2/user/get" payload := map[string]string{"userid": userID, "language": "zh_CN"} // 默认是中文 - jsonPayload, _ := json.Marshal(payload) + jsonPayload, err := json.Marshal(payload) + if err != nil { + c.logger.Error("Failed to marshal payload: %v", log.Error(err)) + return nil, err + } - req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonPayload)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonPayload)) + if err != nil { + c.logger.Error("Failed to create HTTP request: %v", log.Error(err)) + return nil, err + } req.Header.Set("Content-Type", "application/json") query := req.URL.Query() query.Add("access_token", accessToken) @@ -341,7 +349,11 @@ func (c *DingTalkClient) GetUserInfo(userID string) (*UserDetailResponse, error) return nil, err } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + c.logger.Error("Failed to read response body: %v", log.Error(err)) + return nil, err + } // 获取到用户信息 c.logger.Info("Get user info from dingtalk success", log.Any("resp 原始的消息:", resp)) diff --git a/backend/pkg/bot/discord/discord_test.go b/backend/pkg/bot/discord/discord_test.go index b72d99545..05969c597 100644 --- a/backend/pkg/bot/discord/discord_test.go +++ b/backend/pkg/bot/discord/discord_test.go @@ -10,7 +10,10 @@ import ( ) func TestDiscord(t *testing.T) { - cfg, _ := config.NewConfig() + cfg, err := config.NewConfig() + if err != nil { + t.Fatalf("failed to create config: %v", err) + } log := log.NewLogger(cfg) token := "token" getQA := func(ctx context.Context, msg string, info domain.ConversationInfo, ConversationID string) (chan string, error) { @@ -21,7 +24,10 @@ func TestDiscord(t *testing.T) { }() return contentCh, nil } - c, _ := NewDiscordClient(log, token, getQA) + c, err := NewDiscordClient(log, token, getQA) + if err != nil { + t.Fatalf("Failed to create Discord client: %v", err) + } if err := c.Start(); err != nil { t.Errorf("Failed to start Discord client: %v", err) } diff --git a/backend/pkg/bot/feishu/stream.go b/backend/pkg/bot/feishu/stream.go index e297d7e59..791896254 100644 --- a/backend/pkg/bot/feishu/stream.go +++ b/backend/pkg/bot/feishu/stream.go @@ -73,7 +73,8 @@ func NewFeishuClient(ctx context.Context, cancel context.CancelFunc, clientID, c case <-ticker.C: c.msgMap.Range(func(key, value any) bool { // remove messageId if it is older than 5 minutes - if time.Now().Unix()-value.(int64) > 5*60 { + timestamp, ok := value.(int64) + if ok && time.Now().Unix()-timestamp > 5*60 { c.msgMap.Delete(key) } return true @@ -142,9 +143,9 @@ func (c *FeishuClient) sendQACard(ctx context.Context, receiveIdType string, rec } if receiveIdType == "open_id" { // 获取用户的信息,只需要获取p2p的对话的类型的用户信息 - p2p对话 - userinfo, err := c.GetUserInfo(receiveId) - if err != nil { - c.logger.Error("get user info failed", log.Error(err)) + userinfo, getUserErr := c.GetUserInfo(ctx, receiveId) + if getUserErr != nil { + c.logger.Error("get user info failed", log.Error(getUserErr)) } else { if userinfo.UserId != nil { convInfo.UserInfo.UserID = *userinfo.UserId @@ -160,9 +161,9 @@ func (c *FeishuClient) sendQACard(ctx context.Context, receiveIdType string, rec convInfo.UserInfo.From = domain.MessageFromPrivate // 私聊 } else { // chat_id 中的userid // 获取群聊的消息,用户如果是在群聊中@机器人,那么就获取的是群聊的消息 - userinfo, err := c.GetUserInfo(additionalInfo) - if err != nil { - c.logger.Error("get chat info failed", log.Error(err)) + userinfo, getChatErr := c.GetUserInfo(ctx, additionalInfo) + if getChatErr != nil { + c.logger.Error("get chat info failed", log.Error(getChatErr)) } else { if userinfo.UserId != nil { convInfo.UserInfo.UserID = *userinfo.UserId @@ -269,12 +270,12 @@ func (c *FeishuClient) Start() error { // 下面功能都是需要开启飞书对应的权限才可以获取到用户信息 -- 应用权限(否则获取不到对话用户的信息) // 飞书机器人获取用户信息,只是适用于单个用户 -func (c *FeishuClient) GetUserInfo(UserOpenId string) (*larkcontact.User, error) { +func (c *FeishuClient) GetUserInfo(ctx context.Context, UserOpenId string) (*larkcontact.User, error) { // 获取用户信息,根据用户的id req := larkcontact.NewGetUserReqBuilder().UserId(UserOpenId). UserIdType(`open_id`).DepartmentIdType(`open_department_id`).Build() // 发起请求,获取用户消息 - resp, err := c.client.Contact.User.Get(context.Background(), req) + resp, err := c.client.Contact.User.Get(ctx, req) if err != nil { c.logger.Error("failed to get user info", log.Error(err)) return nil, err diff --git a/backend/pkg/bot/lark/client.go b/backend/pkg/bot/lark/client.go index c160c338b..f8436e643 100644 --- a/backend/pkg/bot/lark/client.go +++ b/backend/pkg/bot/lark/client.go @@ -92,7 +92,8 @@ func NewLarkClient(ctx context.Context, cancel context.CancelFunc, clientID, cli case <-ticker.C: c.msgMap.Range(func(key, value any) bool { // remove messageId if it is older than 5 minutes - if time.Now().Unix()-value.(int64) > 5*60 { + timestamp, ok := value.(int64) + if ok && time.Now().Unix()-timestamp > 5*60 { c.msgMap.Delete(key) } return true @@ -128,14 +129,14 @@ func (c *LarkClient) setupEventHandler() { } // Replace mention placeholders with actual user names questionText := c.replaceMentions(message.Text, event.Event.Message.Mentions) - go c.sendQACard(c.ctx, "chat_id", *event.Event.Message.ChatId, questionText, *event.Event.Sender.SenderId.OpenId) + go c.sendQACard(ctx, "chat_id", *event.Event.Message.ChatId, questionText, *event.Event.Sender.SenderId.OpenId) case "p2p": var message Message if err := json.Unmarshal([]byte(*event.Event.Message.Content), &message); err != nil { c.logger.Error("failed to unmarshal message", log.Error(err)) return nil } - go c.sendQACard(c.ctx, "open_id", *event.Event.Sender.SenderId.OpenId, message.Text, *event.Event.Message.ChatId) + go c.sendQACard(ctx, "open_id", *event.Event.Sender.SenderId.OpenId, message.Text, *event.Event.Message.ChatId) default: c.logger.Warn("unsupported chat type", log.String("chat_type", *event.Event.Message.ChatType)) } @@ -205,9 +206,9 @@ func (c *LarkClient) sendQACard(ctx context.Context, receiveIdType string, recei }, } if receiveIdType == "open_id" { - userinfo, err := c.GetUserInfo(receiveId) - if err != nil { - c.logger.Error("get user info failed", log.Error(err)) + userinfo, getUserErr := c.GetUserInfo(ctx, receiveId) + if getUserErr != nil { + c.logger.Error("get user info failed", log.Error(getUserErr)) } else { if userinfo.UserId != nil { convInfo.UserInfo.UserID = *userinfo.UserId @@ -222,9 +223,9 @@ func (c *LarkClient) sendQACard(ctx context.Context, receiveIdType string, recei } convInfo.UserInfo.From = domain.MessageFromPrivate } else { - userinfo, err := c.GetUserInfo(additionalInfo) - if err != nil { - c.logger.Error("get chat info failed", log.Error(err)) + userinfo, getChatErr := c.GetUserInfo(ctx, additionalInfo) + if getChatErr != nil { + c.logger.Error("get chat info failed", log.Error(getChatErr)) } else { if userinfo.UserId != nil { convInfo.UserInfo.UserID = *userinfo.UserId @@ -310,10 +311,10 @@ func (c *LarkClient) Start() error { return nil } -func (c *LarkClient) GetUserInfo(UserOpenId string) (*larkcontact.User, error) { +func (c *LarkClient) GetUserInfo(ctx context.Context, UserOpenId string) (*larkcontact.User, error) { req := larkcontact.NewGetUserReqBuilder().UserId(UserOpenId). UserIdType(`open_id`).DepartmentIdType(`open_department_id`).Build() - resp, err := c.client.Contact.User.Get(context.Background(), req) + resp, err := c.client.Contact.User.Get(ctx, req) if err != nil { c.logger.Error("failed to get user info", log.Error(err)) return nil, err diff --git a/backend/pkg/bot/wechat/wechat.go b/backend/pkg/bot/wechat/wechat.go index 78d4cba72..93402ced8 100644 --- a/backend/pkg/bot/wechat/wechat.go +++ b/backend/pkg/bot/wechat/wechat.go @@ -131,14 +131,23 @@ func (cfg *WechatConfig) SendURLToUser(touser, question, token, conversationID, } url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", token) - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(cfg.Ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return 0, "", err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return 0, "", err } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, "", err + } var result struct { Errcode int `json:"errcode"` @@ -168,14 +177,23 @@ func (cfg *WechatConfig) SendResponseToUser(response string, touser string, toke } url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s", token) - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(cfg.Ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return 0, "", err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return 0, "", err } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, "", err + } var result struct { Errcode int `json:"errcode"` @@ -246,7 +264,13 @@ func (cfg *WechatConfig) GetAccessToken() (string, error) { // get AccessToken--请求微信客服token url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", cfg.CorpID, cfg.Secret) - resp, err := http.Get(url) + req, err := http.NewRequestWithContext(cfg.Ctx, "GET", url, nil) + if err != nil { + return "", errors.New("create request failed") + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return "", errors.New("get wechatapp accesstoken failed") } @@ -278,9 +302,17 @@ func (cfg *WechatConfig) GetUserInfo(username string) (*UserInfo, error) { return nil, err } // 请求获取用户的内容 - resp, err := http.Get(fmt.Sprintf( + url := fmt.Sprintf( "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", - accessToken, username)) + accessToken, username) + + req, err := http.NewRequestWithContext(cfg.Ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } @@ -314,8 +346,14 @@ func (cfg *WechatConfig) UnmarshalMsg(decryptMsg []byte) (*ReceivedMessage, erro // answer set into conversation state buffer func (cfg *WechatConfig) SendQuestionToAI(conversationID string, wccontent chan string) { // send message - val, _ := domain.ConversationManager.Load(conversationID) - state := val.(*domain.ConversationState) + val, ok := domain.ConversationManager.Load(conversationID) + if !ok { + return + } + state, ok := val.(*domain.ConversationState) + if !ok { + return + } for content := range wccontent { state.Mutex.Lock() if state.IsVisited { diff --git a/backend/pkg/bot/wechatservice/tools.go b/backend/pkg/bot/wechatservice/tools.go index fdc76c35f..630986cdf 100644 --- a/backend/pkg/bot/wechatservice/tools.go +++ b/backend/pkg/bot/wechatservice/tools.go @@ -2,6 +2,7 @@ package wechatservice import ( "bytes" + "context" "encoding/base64" "encoding/json" "fmt" @@ -17,8 +18,14 @@ import ( // 读取 cursor,以客服账号的消息作为key,返回对应的cursor值 func getCursor(openKfId string) string { - cursorValue, _ := KfCursors.Load(openKfId) - cursor, _ := cursorValue.(string) + cursorValue, ok := KfCursors.Load(openKfId) + if !ok { + return "" + } + cursor, ok := cursorValue.(string) + if !ok { + return "" + } return cursor } @@ -27,7 +34,7 @@ func setCursor(openKfId, cursor string) { KfCursors.Store(openKfId, cursor) } -func CheckSessionState(token, extrenaluserid, kfId string) (int, error) { +func CheckSessionState(ctx context.Context, token, extrenaluserid, kfId string) (int, error) { var statusrequest struct { OpenKfId string `json:"open_kfid"` ExternalUserid string `json:"external_userid"` @@ -41,7 +48,18 @@ func CheckSessionState(token, extrenaluserid, kfId string) (int, error) { } // 获取状态信息 url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=%s", token) - resp, _ := http.Post(url, "application/json", bytes.NewBuffer(jsonBody)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody)) + if err != nil { + return 0, fmt.Errorf("创建请求失败: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return 0, fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) @@ -61,7 +79,7 @@ func CheckSessionState(token, extrenaluserid, kfId string) (int, error) { return response.ServiceState, nil } -func ChangeState(token, extrenaluserId, kfId string, state int, serviceId string) error { +func ChangeState(ctx context.Context, token, extrenaluserId, kfId string, state int, serviceId string) error { var changestate struct { OpenKfId string `json:"open_kfid"` ExternalUserid string `json:"external_userid"` @@ -78,7 +96,18 @@ func ChangeState(token, extrenaluserId, kfId string, state int, serviceId string } // 发送请求 url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=%s", token) - resp, _ := http.Post(url, "application/json", bytes.NewBuffer(jsonBody)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody)) + if err != nil { + return fmt.Errorf("创建请求失败: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("发送请求失败: %v", err) + } + defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) @@ -102,7 +131,7 @@ func ChangeState(token, extrenaluserId, kfId string, state int, serviceId string return nil } -func GetUserInfo(userid string, accessToken string) (*Customer, error) { +func GetUserInfo(ctx context.Context, userid string, accessToken string) (*Customer, error) { userInfoRequest := UerInfoRequest{ UserID: []string{userid}, SessionContext: 0, @@ -115,8 +144,14 @@ func GetUserInfo(userid string, accessToken string) (*Customer, error) { return nil, err } // post获取用户的消息信息 - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody)) + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } @@ -140,7 +175,7 @@ func GetUserInfo(userid string, accessToken string) (*Customer, error) { } // get image id -func GetUserImageID(accessToken, filePath string) (string, error) { +func GetUserImageID(ctx context.Context, accessToken, filePath string) (string, error) { UImageCache.Mutex.Lock() defer UImageCache.Mutex.Unlock() @@ -149,7 +184,7 @@ func GetUserImageID(accessToken, filePath string) (string, error) { } // URL - mediaID, err := UploadMediaFromURL(accessToken, filePath) + mediaID, err := UploadMediaFromURL(ctx, accessToken, filePath) if err != nil { return "", err @@ -162,7 +197,7 @@ func GetUserImageID(accessToken, filePath string) (string, error) { } // get image id -func GetDefaultImageID(accessToken, ImageBase64 string) (string, error) { +func GetDefaultImageID(ctx context.Context, accessToken, ImageBase64 string) (string, error) { DImageCache.Mutex.Lock() defer DImageCache.Mutex.Unlock() @@ -171,7 +206,7 @@ func GetDefaultImageID(accessToken, ImageBase64 string) (string, error) { } // Base64编码 - mediaID, err := UploadMediaFromBase64(accessToken, ImageBase64) + mediaID, err := UploadMediaFromBase64(ctx, accessToken, ImageBase64) if err != nil { return "", err @@ -183,9 +218,15 @@ func GetDefaultImageID(accessToken, ImageBase64 string) (string, error) { } // upload media to wechat server from URL -func UploadMediaFromURL(accessToken, fileURL string) (string, error) { +func UploadMediaFromURL(ctx context.Context, accessToken, fileURL string) (string, error) { // 处理URL - resp, err := http.Get(fileURL) + req, err := http.NewRequestWithContext(ctx, "GET", fileURL, nil) + if err != nil { + return "", fmt.Errorf("创建请求失败: %w", err) + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("下载图片失败: %w", err) } @@ -205,11 +246,11 @@ func UploadMediaFromURL(accessToken, fileURL string) (string, error) { } } - return uploadMediaToWechat(accessToken, reader, fileName) + return uploadMediaToWechat(ctx, accessToken, reader, fileName) } // upload media to wechat server from Base64 -func UploadMediaFromBase64(accessToken, base64Data string) (string, error) { +func UploadMediaFromBase64(ctx context.Context, accessToken, base64Data string) (string, error) { // 处理Base64编码的图片 parts := strings.SplitN(base64Data, ",", 2) if len(parts) != 2 { @@ -225,11 +266,11 @@ func UploadMediaFromBase64(accessToken, base64Data string) (string, error) { reader := bytes.NewReader(decodedData) fileName := "image.png" // const - return uploadMediaToWechat(accessToken, reader, fileName) + return uploadMediaToWechat(ctx, accessToken, reader, fileName) } // upload media to wechat server - common function -func uploadMediaToWechat(accessToken string, reader io.Reader, fileName string) (string, error) { +func uploadMediaToWechat(ctx context.Context, accessToken string, reader io.Reader, fileName string) (string, error) { // 上传文件 req body := &bytes.Buffer{} writer := multipart.NewWriter(body) @@ -245,12 +286,12 @@ func uploadMediaToWechat(accessToken string, reader io.Reader, fileName string) return "", fmt.Errorf("复制图片数据失败: %w", err) } - if err := writer.Close(); err != nil { - return "", err + if closeErr := writer.Close(); closeErr != nil { + return "", closeErr } url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=image", accessToken) - req, err := http.NewRequest("POST", url, body) + req, err := http.NewRequestWithContext(ctx, "POST", url, body) if err != nil { return "", err } @@ -275,7 +316,7 @@ func uploadMediaToWechat(accessToken string, reader io.Reader, fileName string) return result.MediaID, nil } -func getMsgs(accessToken string, msg *WeixinUserAskMsg) (*MsgRet, error) { +func getMsgs(ctx context.Context, accessToken string, msg *WeixinUserAskMsg) (*MsgRet, error) { var msgRet MsgRet // 拉取消息的路由 url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=%s", accessToken) @@ -289,9 +330,19 @@ func getMsgs(accessToken string, msg *WeixinUserAskMsg) (*MsgRet, error) { Cursor: cursor, } - jsonBody, _ := json.Marshal(msgBody) + jsonBody, err := json.Marshal(msgBody) + if err != nil { + return nil, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody)) // 得到对应的回复 + client := &http.Client{} + resp, err := client.Do(req) // 得到对应的回复 if err != nil { return nil, err } diff --git a/backend/pkg/bot/wechatservice/wechat.go b/backend/pkg/bot/wechatservice/wechat.go index 87d092c18..5088233b3 100644 --- a/backend/pkg/bot/wechatservice/wechat.go +++ b/backend/pkg/bot/wechatservice/wechat.go @@ -53,14 +53,14 @@ func (cfg *WechatServiceConfig) VerifyUrlWechatService(signature, timestamp, non return decryptEchoStr, nil } -func (cfg *WechatServiceConfig) Wechat(msg *WeixinUserAskMsg, getQA bot.GetQAFun) error { +func (cfg *WechatServiceConfig) Wechat(ctx context.Context, msg *WeixinUserAskMsg, getQA bot.GetQAFun) error { // 获取accesstoken 方便给用户发送消息 token, err := cfg.GetAccessToken() if err != nil { return err } // 主动拉去用户发送的消息 - msgRet, err := getMsgs(token, msg) + msgRet, err := getMsgs(ctx, token, msg) if err != nil { return err } @@ -68,7 +68,7 @@ func (cfg *WechatServiceConfig) Wechat(msg *WeixinUserAskMsg, getQA bot.GetQAFun setCursor(msg.OpenKfId, msgRet.NextCursor) } - err = cfg.Processmessage(msgRet, msg, getQA) + err = cfg.Processmessage(ctx, msgRet, msg, getQA) if err != nil { cfg.logger.Error("send to ai failed!") return err @@ -77,7 +77,7 @@ func (cfg *WechatServiceConfig) Wechat(msg *WeixinUserAskMsg, getQA bot.GetQAFun } // forwardToBackend -func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUserAskMsg, GetQA bot.GetQAFun) error { +func (cfg *WechatServiceConfig) Processmessage(ctx context.Context, msgRet *MsgRet, Kfmsg *WeixinUserAskMsg, GetQA bot.GetQAFun) error { // err message cfg.logger.Info("get user message", log.Int("msgRet.Errcode", msgRet.Errcode), log.String("msg.Errmsg", msgRet.Errmsg)) @@ -96,9 +96,13 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser openkfId := current.OpenKfid content := current.Text.Content - token, _ := cfg.GetAccessToken() + token, err := cfg.GetAccessToken() + if err != nil { + cfg.logger.Error("failed to get access token", log.Error(err)) + return err + } - state, err := CheckSessionState(token, userId, openkfId) + state, err := CheckSessionState(ctx, token, userId, openkfId) if err != nil { cfg.logger.Error("check session state failed", log.Error(err)) return err @@ -113,18 +117,18 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser }) { // 改变状态为人工接待 // 非人工 ->转人工 - humanList, err := cfg.GetKfHumanList(token, openkfId) - if err != nil { - cfg.logger.Error("get human list failed", log.Error(err)) - return err + humanList, getHumanErr := cfg.GetKfHumanList(token, openkfId) + if getHumanErr != nil { + cfg.logger.Error("get human list failed", log.Error(getHumanErr)) + return getHumanErr } // 遍历找到可以接待的员工 for _, servicer := range humanList.ServicerList { if servicer.Status == 0 { // 可以接待 - err := ChangeState(token, userId, openkfId, 3, servicer.UserID) - if err != nil { - cfg.logger.Error("change state to human failed", log.Error(err)) - return err + changeErr := ChangeState(ctx, token, userId, openkfId, 3, servicer.UserID) + if changeErr != nil { + cfg.logger.Error("change state to human failed", log.Error(changeErr)) + return changeErr } cfg.logger.Info("change state to human successful") // 转人工成功 return nil @@ -137,12 +141,12 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser } // 1. first response to user - if err := cfg.SendResponseToKfTxt(userId, openkfId, "正在思考您的问题,请稍等...", token); err != nil { - return err + if sendErr := cfg.SendResponseToKfTxt(userId, openkfId, "正在思考您的问题,请稍等...", token); sendErr != nil { + return sendErr } // 获取用户的详细信息 - customer, err := GetUserInfo(userId, token) + customer, err := GetUserInfo(ctx, userId, token) if err != nil { cfg.logger.Error("get user info failed", log.Error(err)) } @@ -154,7 +158,7 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser id = uuid.New() } conversationID := id.String() - wccontent, err := GetQA(cfg.Ctx, content, domain.ConversationInfo{UserInfo: domain.UserInfo{ + wccontent, err := GetQA(ctx, content, domain.ConversationInfo{UserInfo: domain.UserInfo{ UserID: customer.ExternalUserID, // 用户对话的id NickName: customer.Nickname, //用户微信的昵称 Avatar: customer.Avatar, // 用户微信的头像 @@ -164,7 +168,7 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser return err } //2. get baseurl and image path - info, err := cfg.WeRepo.GetWechatStatic(cfg.Ctx, cfg.kbID, domain.AppTypeWeb) + info, err := cfg.WeRepo.GetWechatStatic(ctx, cfg.kbID, domain.AppTypeWeb) if err != nil { return err } @@ -181,26 +185,26 @@ func (cfg *WechatServiceConfig) Processmessage(msgRet *MsgRet, Kfmsg *WeixinUser go cfg.SendQuestionToAI(conversationID, wccontent) } // 3. second send url to user - return cfg.SendResponseToKfUrl(userId, openkfId, conversationID, token, content, info.BaseUrl, info.ImagePath) + return cfg.SendResponseToKfUrl(ctx, userId, openkfId, conversationID, token, content, info.BaseUrl, info.ImagePath) } -func (cfg *WechatServiceConfig) SendResponseToKfUrl(userId, openkfId, conversationID, token, question, baseUrl, image string) error { +func (cfg *WechatServiceConfig) SendResponseToKfUrl(ctx context.Context, userId, openkfId, conversationID, token, question, baseUrl, image string) error { var imageId string var err error if image != "" && !strings.HasPrefix(image, "data:image/") { // user own image and not base64 image - imageId, err = GetUserImageID(token, fmt.Sprintf("%s%s", "http://panda-wiki-minio:9000", image)) + imageId, err = GetUserImageID(ctx, token, fmt.Sprintf("%s%s", "http://panda-wiki-minio:9000", image)) if err != nil { return err } } else if strings.HasPrefix(image, "data:image/") { // 解析base64 - imageId, err = GetDefaultImageID(token, image) + imageId, err = GetDefaultImageID(ctx, token, image) if err != nil { return err } } else { // 解析base64 -> default image - imageId, err = GetDefaultImageID(token, domain.DefaultPandaWikiIconB64) + imageId, err = GetDefaultImageID(ctx, token, domain.DefaultPandaWikiIconB64) if err != nil { return err } @@ -246,7 +250,14 @@ func (cfg *WechatServiceConfig) SendResponseToKfTxt(userId string, openkfId stri func (cfg *WechatServiceConfig) SendMessage(jsonData []byte, token string) error { // 发送消息给客服 url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=%s", token) - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(cfg.Ctx, "POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return fmt.Errorf("create request failed: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return fmt.Errorf("post to wechatservice failed: %w", err) } @@ -263,14 +274,14 @@ func (cfg *WechatServiceConfig) SendMessage(jsonData []byte, token string) error MsgID string `json:"msgid"` } - if err := json.Unmarshal(body, &res); err != nil { - cfg.logger.Error("解析响应失败", log.Error(err)) - return err + if unmarshalErr := json.Unmarshal(body, &res); unmarshalErr != nil { + cfg.logger.Error("解析响应失败", log.Error(unmarshalErr)) + return unmarshalErr } if res.ErrCode != 0 { cfg.logger.Error("发送给微信客服消息失败", log.Any("errcode", res.ErrCode)) - return err + return fmt.Errorf("send message failed: errcode=%d, errmsg=%s", res.ErrCode, res.ErrMsg) } // 发送消息给微信客服成功 s := string(body) @@ -308,7 +319,13 @@ func (cfg *WechatServiceConfig) GetAccessToken() (string, error) { // get AccessToken--请求微信客服token url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", cfg.CorpID, cfg.Secret) - resp, err := http.Get(url) + req, err := http.NewRequestWithContext(cfg.Ctx, "GET", url, nil) + if err != nil { + return "", errors.New("create request failed") + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return "", errors.New("get wechatservice accesstoken failed") } @@ -342,7 +359,13 @@ func (cfg *WechatServiceConfig) UnmarshalMsg(decryptMsg []byte) (*WeixinUserAskM func (cfg *WechatServiceConfig) GetKfHumanList(token string, KfId string) (*HumanList, error) { url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/servicer/list?access_token=%s&open_kfid=%s", token, KfId) - resp, err := http.Get(url) + req, err := http.NewRequestWithContext(cfg.Ctx, "GET", url, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return nil, err } @@ -365,8 +388,14 @@ func (cfg *WechatServiceConfig) GetKfHumanList(token string, KfId string) (*Huma // answer set into redis queue and set useful time func (cfg *WechatServiceConfig) SendQuestionToAI(conversationID string, wccontent chan string) { // send message - val, _ := domain.ConversationManager.Load(conversationID) - state := val.(*domain.ConversationState) + val, ok := domain.ConversationManager.Load(conversationID) + if !ok { + return + } + state, ok := val.(*domain.ConversationState) + if !ok { + return + } for content := range wccontent { state.Mutex.Lock() if state.IsVisited { diff --git a/backend/pkg/bot/wecom/crypt.go b/backend/pkg/bot/wecom/crypt.go index 240618497..7622192f5 100644 --- a/backend/pkg/bot/wecom/crypt.go +++ b/backend/pkg/bot/wecom/crypt.go @@ -7,7 +7,7 @@ import ( "crypto/aes" "crypto/cipher" "crypto/rand" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // G505: SHA1 required by WeChat Work API specification "encoding/base64" "encoding/binary" "encoding/json" @@ -79,7 +79,7 @@ func (s *SHA1) GetSHA1(token, timestamp, nonce string, encrypt interface{}) (int list := []string{token, timestamp, nonce, encStr} sort.Strings(list) joined := strings.Join(list, "") - h := sha1.New() + h := sha1.New() //nolint:gosec // G401: SHA1 required by WeChat Work API specification _, err := h.Write([]byte(joined)) if err != nil { return WXBizMsgCrypt_ComputeSignature_Error, "" @@ -118,7 +118,10 @@ func (jp *JsonParse) Generate(encrypt, signature, timestamp, nonce string) strin Timestamp: timestamp, Nonce: nonce, } - bs, _ := json.Marshal(resp) + bs, err := json.Marshal(resp) + if err != nil { + return "" + } return string(bs) } @@ -185,7 +188,7 @@ func (pc *Prpcrypt) Encrypt(plainText string, receiveID string) (int, string) { // len(txt) 网络字节序 lenBuf := make([]byte, 4) // Python 示例使用 socket.htonl(len(text)),即 network order (big endian) - binary.BigEndian.PutUint32(lenBuf, uint32(len(txt))) + binary.BigEndian.PutUint32(lenBuf, uint32(len(txt))) //nolint:gosec // G115: Safe conversion, text length is within uint32 range buf.Write(lenBuf) buf.Write(txt) buf.Write([]byte(receiveID)) diff --git a/backend/pkg/cas/cas.go b/backend/pkg/cas/cas.go index c7548178d..4ca3ba048 100644 --- a/backend/pkg/cas/cas.go +++ b/backend/pkg/cas/cas.go @@ -115,7 +115,7 @@ func NewClient(ctx context.Context, logger *log.Logger, config Config) (*Client, config: &config, httpClient: &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // G402: CAS server may use self-signed certificates }, }, }, nil @@ -145,7 +145,12 @@ func (c *Client) ValidateTicket(ticket, state string) (*UserInfo, error) { log.String("url", fullURL), log.String("version", c.config.Version)) - resp, err := c.httpClient.Get(fullURL) + req, err := http.NewRequestWithContext(c.ctx, "GET", fullURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to validate ticket: %w", err) } diff --git a/backend/pkg/dingtalk/dingtalk.go b/backend/pkg/dingtalk/dingtalk.go index 8d5b6f695..8f4308d58 100644 --- a/backend/pkg/dingtalk/dingtalk.go +++ b/backend/pkg/dingtalk/dingtalk.go @@ -120,7 +120,10 @@ func NewDingTalkClient(ctx context.Context, logger *log.Logger, clientId, client // GenerateAuthURL 生成钉钉授权URL func (c *Client) GenerateAuthURL(redirectURI string, state string) string { - redirectURL, _ := url.Parse(redirectURI) + redirectURL, err := url.Parse(redirectURI) + if err != nil { + return "" + } redirectURL.Path = callbackPath redirectURI = redirectURL.String() @@ -150,8 +153,7 @@ func (c *Client) GetAccessTokenByCode(code string) (string, error) { return accessToken, nil } -func (c *Client) GetAccessToken() (string, error) { - ctx := context.Background() +func (c *Client) GetAccessToken(ctx context.Context) (string, error) { cacheKey := fmt.Sprintf("dingtalk-access-token:%s", c.clientID) cachedData, err := c.cache.Get(ctx, cacheKey).Result() if err == nil && cachedData != "" { @@ -189,7 +191,7 @@ func (c *Client) GetAccessToken() (string, error) { } func (c *Client) GetUserInfoByCode(code string) (*UserInfo, error) { - req, err := http.NewRequest("GET", userInfoUrl, nil) + req, err := http.NewRequestWithContext(c.ctx, "GET", userInfoUrl, nil) if err != nil { return nil, fmt.Errorf("failed to create GET request: %w", err) } @@ -226,8 +228,8 @@ func (c *Client) GetUserInfoByCode(code string) (*UserInfo, error) { return &userInfo, nil } -func (c *Client) GetDepartmentList() (*DepartmentListRsp, error) { - accessToken, err := c.GetAccessToken() +func (c *Client) GetDepartmentList(ctx context.Context) (*DepartmentListRsp, error) { + accessToken, err := c.GetAccessToken(ctx) if err != nil { return nil, err } @@ -236,7 +238,7 @@ func (c *Client) GetDepartmentList() (*DepartmentListRsp, error) { params.Add("access_token", accessToken) requestURL := fmt.Sprintf("%s?%s", DepartmentListUrl, params.Encode()) - req, err := http.NewRequest(http.MethodGet, requestURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } @@ -271,13 +273,13 @@ func (c *Client) GetDepartmentList() (*DepartmentListRsp, error) { return &departmentListRsp, nil } -func (c *Client) GetAllUserList(deptID int) ([]UserDetail, error) { +func (c *Client) GetAllUserList(ctx context.Context, deptID int) ([]UserDetail, error) { depth := 0 const maxDepth = 10 userList := make([]UserDetail, 0) for depth < maxDepth { - resp, err := c.GetUserList(deptID) + resp, err := c.GetUserList(ctx, deptID) if err != nil { return nil, err } @@ -292,8 +294,8 @@ func (c *Client) GetAllUserList(deptID int) ([]UserDetail, error) { return userList, nil } -func (c *Client) GetUserList(deptID int) (*GetUserListResp, error) { - accessToken, err := c.GetAccessToken() +func (c *Client) GetUserList(ctx context.Context, deptID int) (*GetUserListResp, error) { + accessToken, err := c.GetAccessToken(ctx) if err != nil { return nil, err } @@ -313,7 +315,7 @@ func (c *Client) GetUserList(deptID int) (*GetUserListResp, error) { return nil, fmt.Errorf("failed to marshal request body: %w", err) } - req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } diff --git a/backend/pkg/feishu/feishu.go b/backend/pkg/feishu/feishu.go index d481ae32b..58d7a4c4a 100644 --- a/backend/pkg/feishu/feishu.go +++ b/backend/pkg/feishu/feishu.go @@ -14,7 +14,7 @@ import ( const ( AuthURL = "https://accounts.feishu.cn/open-apis/authen/v1/authorize" - TokenURL = "https://open.feishu.cn/open-apis/authen/v2/oauth/token" + TokenURL = "https://open.feishu.cn/open-apis/authen/v2/oauth/token" //nolint:gosec // G101: Not a credential, just API endpoint URL UserInfoURL = "https://open.feishu.cn/open-apis/authen/v1/user_info" callbackPath = "/share/pro/v1/openapi/feishu/callback" ) @@ -54,7 +54,10 @@ type UserInfo struct { } func NewClient(ctx context.Context, logger *log.Logger, appID, appSecret, redirectURI string) (*Client, error) { - redirectURL, _ := url.Parse(redirectURI) + redirectURL, err := url.Parse(redirectURI) + if err != nil { + return nil, err + } redirectURL.Path = callbackPath redirectURI = redirectURL.String() @@ -95,7 +98,7 @@ func (c *Client) GetUserInfoByCode(ctx context.Context, code string, codeVerifie } client := c.oauthConfig.Client(ctx, token) - req, err := http.NewRequest("GET", UserInfoURL, nil) + req, err := http.NewRequestWithContext(ctx, "GET", UserInfoURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } diff --git a/backend/pkg/oauth/github.go b/backend/pkg/oauth/github.go index 2ef0ed2d3..9bac07a80 100644 --- a/backend/pkg/oauth/github.go +++ b/backend/pkg/oauth/github.go @@ -17,7 +17,7 @@ import ( const ( githubAuthorizeURL = "https://github.com/login/oauth/authorize" - githubTokenURL = "https://github.com/login/oauth/access_token" + githubTokenURL = "https://github.com/login/oauth/access_token" //nolint:gosec // G101: Not a credential, just API endpoint URL githubUserInfoURL = "https://api.github.com/user" githubUserEmailURL = "https://api.github.com/user/emails" githubCallbackPathPro = "/share/pro/v1/openapi/github/callback" @@ -30,7 +30,10 @@ func NewGithubClient(ctx context.Context, logger *log.Logger, clientID, clientSe return nil, fmt.Errorf("failed to retrieve license edition from context") } - redirectURL, _ := url.Parse(redirectURI) + redirectURL, err := url.Parse(redirectURI) + if err != nil { + return nil, fmt.Errorf("failed to parse redirect URI: %w", err) + } redirectURL.Path = githubCallbackPath if licenseEdition > consts.LicenseEditionFree { @@ -94,13 +97,13 @@ func NewGithubClient(ctx context.Context, logger *log.Logger, clientID, clientSe }, nil } -func (c *Client) GetGithubPrimaryEmail(token *oauth2.Token) (string, error) { +func (c *Client) GetGithubPrimaryEmail(ctx context.Context, token *oauth2.Token) (string, error) { var client *http.Client if c.httpClient != nil { - ctx := context.WithValue(c.ctx, oauth2.HTTPClient, c.httpClient) - client = c.oauth.Client(ctx, token) + ctxWithClient := context.WithValue(ctx, oauth2.HTTPClient, c.httpClient) + client = c.oauth.Client(ctxWithClient, token) } else { - client = c.oauth.Client(c.ctx, token) + client = c.oauth.Client(ctx, token) } type Email struct { @@ -109,7 +112,12 @@ func (c *Client) GetGithubPrimaryEmail(token *oauth2.Token) (string, error) { Verified bool `json:"verified"` } - resp, err := client.Get(githubUserEmailURL) + req, err := http.NewRequestWithContext(ctx, "GET", githubUserEmailURL, nil) + if err != nil { + return "", err + } + + resp, err := client.Do(req) if err != nil { return "", err } diff --git a/backend/pkg/oauth/oauth.go b/backend/pkg/oauth/oauth.go index 19bd6e9a4..8740a0106 100644 --- a/backend/pkg/oauth/oauth.go +++ b/backend/pkg/oauth/oauth.go @@ -2,6 +2,7 @@ package oauth import ( "context" + "fmt" "io" "net/http" "net/url" @@ -46,7 +47,10 @@ type UserInfo struct { // NewClient 创建OAuth客户端 func NewClient(ctx context.Context, logger *log.Logger, config Config) (*Client, error) { - redirectURL, _ := url.Parse(config.RedirectURI) + redirectURL, err := url.Parse(config.RedirectURI) + if err != nil { + return nil, fmt.Errorf("failed to parse redirect URI: %w", err) + } redirectURL.Path = callbackPath redirectURI := redirectURL.String() @@ -71,13 +75,18 @@ func (c *Client) GetAuthorizeURL(state string) string { return c.oauth.AuthCodeURL(state) } -func (c *Client) GetUserInfo(code string) (*UserInfo, error) { - token, err := c.oauth.Exchange(c.ctx, code) +func (c *Client) GetUserInfo(ctx context.Context, code string) (*UserInfo, error) { + token, err := c.oauth.Exchange(ctx, code) + if err != nil { + return nil, err + } + client := c.oauth.Client(ctx, token) + req, err := http.NewRequestWithContext(ctx, "GET", c.config.UserInfoURL, nil) if err != nil { return nil, err } - client := c.oauth.Client(c.ctx, token) - res, err := client.Get(c.config.UserInfoURL) + + res, err := client.Do(req) if err != nil { return nil, err } @@ -94,7 +103,7 @@ func (c *Client) GetUserInfo(code string) (*UserInfo, error) { email := gjson.Get(jsonString, c.config.EmailField).String() if email == "" && c.config.UserInfoURL == githubUserInfoURL { - email, err = c.GetGithubPrimaryEmail(token) + email, err = c.GetGithubPrimaryEmail(ctx, token) if err != nil { c.logger.Warn("GetGithubPrimaryEmail failed", log.Error(err)) } diff --git a/backend/pkg/wecom/wecom.go b/backend/pkg/wecom/wecom.go index c728a6f16..31b8dd794 100644 --- a/backend/pkg/wecom/wecom.go +++ b/backend/pkg/wecom/wecom.go @@ -20,7 +20,7 @@ const ( // AuthURL api doc https://developer.work.weixin.qq.com/document/path/98152 AuthWebURL = "https://login.work.weixin.qq.com/wwlogin/sso/login" AuthAPPURL = "https://open.weixin.qq.com/connect/oauth2/authorize" - TokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" + TokenURL = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" //nolint:gosec // G101: Not a credential, just API endpoint URL UserInfoURL = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo" UserDetailURL = "https://qyapi.weixin.qq.com/cgi-bin/user/get" // DepartmentListURL https://developer.work.weixin.qq.com/document/path/90344 @@ -112,7 +112,10 @@ type UserListResponse struct { } func NewClient(ctx context.Context, logger *log.Logger, corpID, corpSecret, agentID, redirectURI string, cache *cache.Cache, isApp bool) (*Client, error) { - redirectURL, _ := url.Parse(redirectURI) + redirectURL, err := url.Parse(redirectURI) + if err != nil { + return nil, err + } redirectURL.Path = callbackPath redirectURI = redirectURL.String() authUrl := AuthWebURL @@ -173,7 +176,13 @@ func (c *Client) GetAccessToken(ctx context.Context) (string, error) { params.Set("corpid", c.corpID) params.Set("corpsecret", c.oauthConfig.ClientSecret) - resp, err := c.httpClient.Get(fmt.Sprintf("%s?%s", TokenURL, params.Encode())) + url := fmt.Sprintf("%s?%s", TokenURL, params.Encode()) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) if err != nil { return "", fmt.Errorf("failed to get access token: %w", err) } @@ -211,7 +220,12 @@ func (c *Client) GetUserInfoByCode(ctx context.Context, code string) (*UserDetai c.logger.Debug("GetUserInfoByCode", log.Any("userInfoURL", userInfoURL)) - resp, err := c.httpClient.Get(userInfoURL) + req, err := http.NewRequestWithContext(ctx, "GET", userInfoURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to get user info: %w", err) } @@ -227,8 +241,8 @@ func (c *Client) GetUserInfoByCode(ctx context.Context, code string) (*UserDetai resp.Body = io.NopCloser(bytes.NewReader(rawBody)) var userInfoResp UserInfoResponse - if err := json.NewDecoder(resp.Body).Decode(&userInfoResp); err != nil { - return nil, fmt.Errorf("failed to decode user info response: %w", err) + if decodeErr := json.NewDecoder(resp.Body).Decode(&userInfoResp); decodeErr != nil { + return nil, fmt.Errorf("failed to decode user info response: %w", decodeErr) } c.logger.Debug("GetUserInfoByCode resp:", log.Any("resp", userInfoResp)) @@ -243,7 +257,12 @@ func (c *Client) GetUserInfoByCode(ctx context.Context, code string) (*UserDetai userDetailURL := fmt.Sprintf("%s?%s", UserDetailURL, detailParams.Encode()) - detailResp, err := c.httpClient.Get(userDetailURL) + detailReq, err := http.NewRequestWithContext(ctx, "GET", userDetailURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create detail request: %w", err) + } + + detailResp, err := c.httpClient.Do(detailReq) if err != nil { return nil, fmt.Errorf("failed to get user detail: %w", err) } @@ -275,7 +294,12 @@ func (c *Client) GetDepartmentList(ctx context.Context) (*DepartmentListResponse departmentListURL := fmt.Sprintf("%s?%s", DepartmentListURL, params.Encode()) - resp, err := c.httpClient.Get(departmentListURL) + req, err := http.NewRequestWithContext(ctx, "GET", departmentListURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to get department list: %w", err) } @@ -316,10 +340,16 @@ func (c *Client) GetUserList(ctx context.Context, deptID string) (*UserListRespo userListUrl := fmt.Sprintf("%s?%s", UserListUrl, params.Encode()) - resp, err := c.httpClient.Get(userListUrl) + req, err := http.NewRequestWithContext(ctx, "GET", userListUrl, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + resp, err := c.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to get user list: %w", err) } + defer resp.Body.Close() rawBody, err := io.ReadAll(resp.Body) if err != nil { diff --git a/backend/pro b/backend/pro index bb1b17dd5..4e56cddc2 160000 --- a/backend/pro +++ b/backend/pro @@ -1 +1 @@ -Subproject commit bb1b17dd5c7d72d40f6a1198b1604f4d3c44116e +Subproject commit 4e56cddc2778d122223c0222b98867be00f66696 diff --git a/backend/repo/pg/app.go b/backend/repo/pg/app.go index f96079429..f38a76814 100644 --- a/backend/repo/pg/app.go +++ b/backend/repo/pg/app.go @@ -58,8 +58,8 @@ func (r *AppRepository) GetOrCreateAppByKBIDAndType(ctx context.Context, kbID st if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // create app if kb is exist - if err := tx.Model(&domain.KnowledgeBase{}).Where("id = ?", kbID).First(&domain.KnowledgeBase{}).Error; err != nil { - return err + if kbErr := tx.Model(&domain.KnowledgeBase{}).Where("id = ?", kbID).First(&domain.KnowledgeBase{}).Error; kbErr != nil { + return kbErr } app = &domain.App{ ID: uuid.New().String(), diff --git a/backend/repo/pg/auth.go b/backend/repo/pg/auth.go index 2612d481d..f1e7ad137 100644 --- a/backend/repo/pg/auth.go +++ b/backend/repo/pg/auth.go @@ -168,7 +168,7 @@ func (r *AuthRepo) GetAuthGroupIdsWithParentsByAuthId(ctx context.Context, authI result := make([]int, 0, len(groupsMap)) for _, group := range groupsMap { - result = append(result, int(group.ID)) + result = append(result, int(group.ID)) //nolint:gosec // G115: Safe conversion, group.ID is database ID } return result, nil @@ -208,9 +208,9 @@ func (r *AuthRepo) CreateAuthConfig(ctx context.Context, authConfig *domain.Auth if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - if err := tx.Model(&domain.AuthConfig{}). - Create(authConfig).Error; err != nil { - return err + if createErr := tx.Model(&domain.AuthConfig{}). + Create(authConfig).Error; createErr != nil { + return createErr } return nil } @@ -271,7 +271,10 @@ func (r *AuthRepo) GetAuths(ctx context.Context, kbID string, sourceType consts. func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourceType consts.SourceType) (*domain.Auth, error) { - licenseEdition, _ := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition) + licenseEdition, ok := ctx.Value(consts.ContextKeyEdition).(consts.LicenseEdition) + if !ok { + licenseEdition = consts.LicenseEditionFree + } if licenseEdition < consts.LicenseEditionEnterprise { rdsKey := fmt.Sprintf("GetOrCreateAuth:%s", auth.KBID) @@ -293,11 +296,11 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc if errors.Is(err, gorm.ErrRecordNotFound) { var count int64 // 统计时排除机器人类型的认证,机器人不占用license限制名额 - if err := tx.Model(&domain.Auth{}). + if countErr := tx.Model(&domain.Auth{}). Where("kb_id = ?", auth.KBID). Where("source_type NOT IN (?)", consts.BotSourceTypes). - Count(&count).Error; err != nil { - return err + Count(&count).Error; countErr != nil { + return countErr } if int(count) >= domain.GetBaseEditionLimitation(ctx).MaxSSOUser { @@ -305,8 +308,8 @@ func (r *AuthRepo) GetOrCreateAuth(ctx context.Context, auth *domain.Auth, sourc } auth.LastLoginTime = time.Now() - if err := tx.Model(&domain.Auth{}).Create(auth).Error; err != nil { - return err + if createErr := tx.Model(&domain.Auth{}).Create(auth).Error; createErr != nil { + return createErr } return nil } diff --git a/backend/repo/pg/conversation.go b/backend/repo/pg/conversation.go index 7b7d0d56e..573c4f5cc 100644 --- a/backend/repo/pg/conversation.go +++ b/backend/repo/pg/conversation.go @@ -65,7 +65,7 @@ func (r *ConversationRepository) GetConversationList(ctx context.Context, reques Find(&conversations).Error; err != nil { return nil, 0, err } - return conversations, uint64(count), nil + return conversations, uint64(count), nil //nolint:gosec // G115: Safe conversion, count is database count within uint64 range } func (r *ConversationRepository) GetConversationDetail(ctx context.Context, kbID, conversationID string) (*domain.ConversationDetailResp, error) { diff --git a/backend/repo/pg/knowledge_base.go b/backend/repo/pg/knowledge_base.go index c03c41b40..709aa32b6 100644 --- a/backend/repo/pg/knowledge_base.go +++ b/backend/repo/pg/knowledge_base.go @@ -268,17 +268,21 @@ func (r *KnowledgeBaseRepository) SyncKBAccessSettingsToCaddy(ctx context.Contex config := map[string]any{ "apps": apps, } - newBody, _ := json.Marshal(config) + newBody, err := json.Marshal(config) + if err != nil { + return err + } tr := &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", socketPath) + DialContext: func(dialCtx context.Context, _, _ string) (net.Conn, error) { + var d net.Dialer + return d.DialContext(dialCtx, "unix", socketPath) }, } client := &http.Client{ Transport: tr, Timeout: 5 * time.Second, } - req, err := http.NewRequest("POST", "http://unix/load", bytes.NewBuffer(newBody)) + req, err := http.NewRequestWithContext(ctx, "POST", "http://unix/load", bytes.NewBuffer(newBody)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } @@ -287,9 +291,14 @@ func (r *KnowledgeBaseRepository) SyncKBAccessSettingsToCaddy(ctx context.Contex if err != nil { return fmt.Errorf("failed to send request: %w", err) } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - r.logger.Error("failed to update caddy config", "error", string(body)) + body, err := io.ReadAll(resp.Body) + if err != nil { + r.logger.Error("failed to read response body", "error", err) + } else { + r.logger.Error("failed to update caddy config", "error", string(body)) + } return domain.ErrSyncCaddyConfigFailed } return nil @@ -521,21 +530,21 @@ func (r *KnowledgeBaseRepository) UpdateKnowledgeBase(ctx context.Context, req * } if err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - if err := tx.Model(&domain.KnowledgeBase{}).Where("id = ?", req.ID).Updates(updateMap).Error; err != nil { - return err + if updateErr := tx.Model(&domain.KnowledgeBase{}).Where("id = ?", req.ID).Updates(updateMap).Error; updateErr != nil { + return updateErr } // get all kb list var kbs []*domain.KnowledgeBaseListItem - if err := tx.Model(&domain.KnowledgeBase{}). + if findErr := tx.Model(&domain.KnowledgeBase{}). Order("created_at ASC"). - Find(&kbs).Error; err != nil { - return err + Find(&kbs).Error; findErr != nil { + return findErr } - if err := r.checkUniquePortHost(kbs); err != nil { - return err + if checkErr := r.checkUniquePortHost(kbs); checkErr != nil { + return checkErr } - if err := r.SyncKBAccessSettingsToCaddy(ctx, kbs); err != nil { - return fmt.Errorf("failed to sync kb access settings to caddy: %w", err) + if syncErr := r.SyncKBAccessSettingsToCaddy(ctx, kbs); syncErr != nil { + return fmt.Errorf("failed to sync kb access settings to caddy: %w", syncErr) } return nil }); err != nil { diff --git a/backend/repo/pg/node.go b/backend/repo/pg/node.go index 42861ae82..e0270d5b9 100644 --- a/backend/repo/pg/node.go +++ b/backend/repo/pg/node.go @@ -41,10 +41,10 @@ func (r *NodeRepository) Create(ctx context.Context, req *domain.CreateNodeReq, err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { // check count var count int64 - if err := tx.Model(&domain.Node{}). + if countErr := tx.Model(&domain.Node{}). Where("kb_id = ?", req.KBID). - Count(&count).Error; err != nil { - return err + Count(&count).Error; countErr != nil { + return countErr } if count >= int64(req.MaxNode) { return domain.ErrMaxNodeLimitReached @@ -60,10 +60,10 @@ func (r *NodeRepository) Create(ctx context.Context, req *domain.CreateNodeReq, query = query.Where("parent_id = ?", req.ParentID) } - if err := query. + if scanErr := query. Select("COALESCE(MAX(position::float), 0)"). - Scan(&maxPos).Error; err != nil { - return err + Scan(&maxPos).Error; scanErr != nil { + return scanErr } var newPos float64 @@ -75,8 +75,8 @@ func (r *NodeRepository) Create(ctx context.Context, req *domain.CreateNodeReq, } else { // default the last newPos = maxPos + (domain.MaxPosition-maxPos)/2.0 if newPos-maxPos < domain.MinPositionGap { - if err := r.reorderPositionsByParentID(tx, req.KBID, req.ParentID); err != nil { - return err + if reorderErr := r.reorderPositionsByParentID(tx, req.KBID, req.ParentID); reorderErr != nil { + return reorderErr } } } diff --git a/backend/repo/pg/user_access.go b/backend/repo/pg/user_access.go index 23ab2c554..f69207a25 100644 --- a/backend/repo/pg/user_access.go +++ b/backend/repo/pg/user_access.go @@ -38,7 +38,11 @@ func (r *UserAccessRepository) UpdateAccessTime(userID string) { // GetAccessTime get user access time func (r *UserAccessRepository) GetAccessTime(userID string) (time.Time, bool) { if value, ok := r.accessMap.Load(userID); ok { - return value.(time.Time), true + timestamp, ok := value.(time.Time) + if !ok { + return time.Time{}, false + } + return timestamp, true } return time.Time{}, false } @@ -58,8 +62,14 @@ func (r *UserAccessRepository) syncToDatabase() { // collect data to update updates := make([]domain.UserAccessTime, 0) r.accessMap.Range(func(key, value any) bool { - userID := key.(string) - timestamp := value.(time.Time) + userID, ok := key.(string) + if !ok { + return true + } + timestamp, ok := value.(time.Time) + if !ok { + return true + } updates = append(updates, domain.UserAccessTime{ UserID: userID, Timestamp: timestamp, diff --git a/backend/server/http/http.go b/backend/server/http/http.go index d075d2cc2..032258817 100644 --- a/backend/server/http/http.go +++ b/backend/server/http/http.go @@ -132,8 +132,8 @@ type MyBinder struct { } func (b *MyBinder) Bind(i interface{}, c echo.Context) (err error) { - if err := b.BindPathParams(c, i); err != nil { - return err + if bindErr := b.BindPathParams(c, i); bindErr != nil { + return bindErr } method := c.Request().Method diff --git a/backend/setup/cert.go b/backend/setup/cert.go index b7a01d2e8..abd4bcdf0 100644 --- a/backend/setup/cert.go +++ b/backend/setup/cert.go @@ -68,8 +68,8 @@ func createSelfSignedCerts() error { } // ensure dir /app/etc/nginx/ssl exists - if err := os.MkdirAll("/app/etc/nginx/ssl", 0o755); err != nil { - return fmt.Errorf("failed to create ssl dir: %v", err) + if mkdirErr := os.MkdirAll("/app/etc/nginx/ssl", 0o750); mkdirErr != nil { + return fmt.Errorf("failed to create ssl dir: %v", mkdirErr) } // Write certificate file with appropriate permissions @@ -80,8 +80,8 @@ func createSelfSignedCerts() error { defer certFile.Close() // Set certificate file permissions to 644 (readable by all) - if err := certFile.Chmod(0o644); err != nil { - return fmt.Errorf("failed to set cert file permissions: %v", err) + if chmodErr := certFile.Chmod(0o644); chmodErr != nil { + return fmt.Errorf("failed to set cert file permissions: %v", chmodErr) } err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) @@ -97,8 +97,8 @@ func createSelfSignedCerts() error { defer keyFile.Close() // Set private key file permissions to 600 (owner read/write) - if err := keyFile.Chmod(0o600); err != nil { - return fmt.Errorf("failed to set key file permissions: %v", err) + if chmodKeyErr := keyFile.Chmod(0o600); chmodKeyErr != nil { + return fmt.Errorf("failed to set key file permissions: %v", chmodKeyErr) } err = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) diff --git a/backend/store/cache/redis.go b/backend/store/cache/redis.go index 01c93a9f0..9e00b4c3f 100644 --- a/backend/store/cache/redis.go +++ b/backend/store/cache/redis.go @@ -32,8 +32,8 @@ func (cache *Cache) GetOrSet(ctx context.Context, key string, value interface{}, val, err := cache.Get(ctx, key).Result() if err == redis.Nil { // If not found, set the value - if err := cache.Set(ctx, key, value, expiration).Err(); err != nil { - return nil, err + if setErr := cache.Set(ctx, key, value, expiration).Err(); setErr != nil { + return nil, setErr } return value, nil } else if err != nil { diff --git a/backend/store/rag/ct/rag.go b/backend/store/rag/ct/rag.go index cd174f79d..6cc72ac85 100644 --- a/backend/store/rag/ct/rag.go +++ b/backend/store/rag/ct/rag.go @@ -108,11 +108,11 @@ func (s *CTRAG) UpsertRecords(ctx context.Context, datasetID string, nodeRelease return "", fmt.Errorf("convert html to markdown failed: %w", err) } } - if _, err := tempFile.Write([]byte(markdown)); err != nil { - return "", fmt.Errorf("write temp file failed: %w", err) + if _, writeErr := tempFile.Write([]byte(markdown)); writeErr != nil { + return "", fmt.Errorf("write temp file failed: %w", writeErr) } - if err := tempFile.Close(); err != nil { - return "", fmt.Errorf("close temp file failed: %w", err) + if closeErr := tempFile.Close(); closeErr != nil { + return "", fmt.Errorf("close temp file failed: %w", closeErr) } defer os.Remove(tempFile.Name()) docs, err := s.client.UploadDocumentsAndParse(ctx, datasetID, []string{tempFile.Name()}, groupIds, &rag.DocumentMetadata{ diff --git a/backend/telemetry/client.go b/backend/telemetry/client.go index 7e6e37cb6..eea40d734 100644 --- a/backend/telemetry/client.go +++ b/backend/telemetry/client.go @@ -81,13 +81,13 @@ func (c *Client) getOrCreateMachineID() (string, error) { // ensure dir is exists dir := filepath.Dir(machineIDFile) - if err := os.MkdirAll(dir, 0o755); err != nil { + if err := os.MkdirAll(dir, 0o750); err != nil { return "", fmt.Errorf("failed to create machine ID directory: %w", err) } // create lock file to prevent concurrent access lockFile := machineIDFile + ".lock" - lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644) + lock, err := os.OpenFile(lockFile, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) if err != nil { if os.IsExist(err) { // if lock file already exists, wait and try again @@ -115,20 +115,20 @@ func (c *Client) getOrCreateMachineID() (string, error) { id := uuid.New().String() // write machine ID to file and ensure data is written to disk - if err := os.WriteFile(machineIDFile, []byte(id), 0o644); err != nil { + if err := os.WriteFile(machineIDFile, []byte(id), 0o600); err != nil { return "", fmt.Errorf("failed to write machine ID file: %w", err) } // sync file to ensure data is written to disk - if file, err := os.OpenFile(machineIDFile, os.O_RDWR, 0o644); err == nil { - if err := file.Sync(); err != nil { - if err := file.Close(); err != nil { - c.logger.Error("failed to close machine ID file after write", log.Error(err)) + if file, openErr := os.OpenFile(machineIDFile, os.O_RDWR, 0o600); openErr == nil { + if syncErr := file.Sync(); syncErr != nil { + if closeErr := file.Close(); closeErr != nil { + c.logger.Error("failed to close machine ID file after write", log.Error(closeErr)) } - return "", fmt.Errorf("failed to sync machine ID file: %w", err) + return "", fmt.Errorf("failed to sync machine ID file: %w", syncErr) } - if err := file.Close(); err != nil { - c.logger.Error("failed to close machine ID file after sync", log.Error(err)) + if closeErr := file.Close(); closeErr != nil { + c.logger.Error("failed to close machine ID file after sync", log.Error(closeErr)) } } return id, nil @@ -185,7 +185,7 @@ func (c *Client) reportInstallation() error { if err != nil { return fmt.Errorf("marshal installation event: %w", err) } - req, err := http.NewRequest("POST", c.baseURL, bytes.NewBuffer(eventEncryptedRaw)) + req, err := http.NewRequestWithContext(context.Background(), "POST", c.baseURL, bytes.NewBuffer(eventEncryptedRaw)) if err != nil { return fmt.Errorf("create request: %w", err) } diff --git a/backend/usecase/app.go b/backend/usecase/app.go index 55d941c28..ff23312ee 100644 --- a/backend/usecase/app.go +++ b/backend/usecase/app.go @@ -74,11 +74,11 @@ func NewAppUsecase( for _, app := range apps { switch app.Type { case domain.AppTypeDingTalkBot: - u.updateDingTalkBot(app) + u.updateDingTalkBot(context.Background(), app) case domain.AppTypeFeishuBot: - u.updateFeishuBot(app) + u.updateFeishuBot(context.Background(), app) case domain.AppTypeLarkBot: - u.updateLarkBot(app) + u.updateLarkBot(context.Background(), app) case domain.AppTypeDisCordBot: u.updateDisCordBot(app) } @@ -139,11 +139,11 @@ func (u *AppUsecase) UpdateApp(ctx context.Context, id string, appRequest *domai } switch app.Type { case domain.AppTypeDingTalkBot: - u.updateDingTalkBot(app) + u.updateDingTalkBot(ctx, app) case domain.AppTypeFeishuBot: - u.updateFeishuBot(app) + u.updateFeishuBot(ctx, app) case domain.AppTypeLarkBot: - u.updateLarkBot(app) + u.updateLarkBot(ctx, app) case domain.AppTypeDisCordBot: u.updateDisCordBot(app) } @@ -218,7 +218,7 @@ func (u *AppUsecase) getQAFunc(kbID string, appType domain.AppType) bot.GetQAFun } } -func (u *AppUsecase) updateFeishuBot(app *domain.App) { +func (u *AppUsecase) updateFeishuBot(ctx context.Context, app *domain.App) { u.feishuMutex.Lock() defer u.feishuMutex.Unlock() @@ -235,7 +235,7 @@ func (u *AppUsecase) updateFeishuBot(app *domain.App) { getQA := u.getQAFunc(app.KBID, app.Type) - botCtx, cancel := context.WithCancel(context.Background()) + botCtx, cancel := context.WithCancel(ctx) feishuClient := feishu.NewFeishuClient( botCtx, cancel, @@ -258,7 +258,7 @@ func (u *AppUsecase) updateFeishuBot(app *domain.App) { u.feishuBots[app.ID] = feishuClient } -func (u *AppUsecase) updateLarkBot(app *domain.App) { +func (u *AppUsecase) updateLarkBot(ctx context.Context, app *domain.App) { u.larkMutex.Lock() defer u.larkMutex.Unlock() @@ -275,7 +275,7 @@ func (u *AppUsecase) updateLarkBot(app *domain.App) { getQA := u.getQAFunc(app.KBID, app.Type) - botCtx, cancel := context.WithCancel(context.Background()) + botCtx, cancel := context.WithCancel(ctx) larkClient, err := lark.NewLarkClient( botCtx, cancel, @@ -304,7 +304,7 @@ func (u *AppUsecase) updateLarkBot(app *domain.App) { u.larkBots[app.ID] = larkClient } -func (u *AppUsecase) updateDingTalkBot(app *domain.App) { +func (u *AppUsecase) updateDingTalkBot(ctx context.Context, app *domain.App) { u.dingTalkMutex.Lock() defer u.dingTalkMutex.Unlock() @@ -321,7 +321,7 @@ func (u *AppUsecase) updateDingTalkBot(app *domain.App) { getQA := u.getQAFunc(app.KBID, app.Type) - botCtx, cancel := context.WithCancel(context.Background()) + botCtx, cancel := context.WithCancel(ctx) dingTalkClient, err := dingtalk.NewDingTalkClient( botCtx, cancel, @@ -771,8 +771,8 @@ func (u *AppUsecase) handleBotAuth(ctx context.Context, kbID, appId string, curr } defer u.cache.ReleaseLock(ctx, rdsKey) - existingAuth, _ := u.authRepo.GetAuthByKBIDAndSourceType(ctx, kbID, sourceType) - if existingAuth != nil { + existingAuth, err := u.authRepo.GetAuthByKBIDAndSourceType(ctx, kbID, sourceType) + if err == nil && existingAuth != nil { return nil } diff --git a/backend/usecase/auth.go b/backend/usecase/auth.go index 7d0a4a2d5..4181a7f54 100644 --- a/backend/usecase/auth.go +++ b/backend/usecase/auth.go @@ -121,10 +121,16 @@ func (u *AuthUsecase) ValidateRedirectUrl(ctx context.Context, kbId, redirectUrl if err != nil { return false, err } - redirectURL, _ := url.Parse(redirectUrl) + redirectURL, err := url.Parse(redirectUrl) + if err != nil { + return false, err + } if kb.AccessSettings.BaseURL != "" { - baseUrl, _ := url.Parse(kb.AccessSettings.BaseURL) + baseUrl, err := url.Parse(kb.AccessSettings.BaseURL) + if err != nil { + return false, err + } if baseUrl.Hostname() != redirectURL.Hostname() { return false, nil } @@ -157,7 +163,10 @@ func (u *AuthUsecase) SaveNewSession(c echo.Context, auth *domain.Auth) error { if s == nil { return fmt.Errorf("failed to get session store") } - store := s.(sessions.Store) + store, ok := s.(sessions.Store) + if !ok { + return fmt.Errorf("failed to get session store: invalid type") + } newSess := sessions.NewSession(store, domain.SessionName) newSess.IsNew = true diff --git a/backend/usecase/auth_github.go b/backend/usecase/auth_github.go index cef92aa52..cd0ca73ac 100644 --- a/backend/usecase/auth_github.go +++ b/backend/usecase/auth_github.go @@ -52,7 +52,7 @@ func (u *AuthUsecase) GitHubCallback(ctx context.Context, req shareV1.GitHubCall return nil, "", err } - userInfo, err := githubClient.GetUserInfo(req.Code) + userInfo, err := githubClient.GetUserInfo(ctx, req.Code) if err != nil { return nil, "", err } diff --git a/backend/usecase/chat.go b/backend/usecase/chat.go index 7e13ec709..2aa2cb433 100644 --- a/backend/usecase/chat.go +++ b/backend/usecase/chat.go @@ -116,9 +116,9 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan return } } else if req.ConversationID == "" { - id, err := uuid.NewV7() - if err != nil { - u.logger.Error("failed to generate conversation uuid", log.Error(err)) + id, uuidErr := uuid.NewV7() + if uuidErr != nil { + u.logger.Error("failed to generate conversation uuid", log.Error(uuidErr)) id = uuid.New() } conversationID := id.String() @@ -146,9 +146,9 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan eventCh <- domain.SSEEvent{Type: "error", Content: "nonce is required"} return } - err := u.conversationUsecase.ValidateConversationNonce(ctx, req.ConversationID, req.Nonce) - if err != nil { - u.logger.Error("failed to validate chat conversation nonce", log.Error(err)) + validateErr := u.conversationUsecase.ValidateConversationNonce(ctx, req.ConversationID, req.Nonce) + if validateErr != nil { + u.logger.Error("failed to validate chat conversation nonce", log.Error(validateErr)) eventCh <- domain.SSEEvent{Type: "error", Content: "validate chat conversation nonce failed"} return } @@ -158,7 +158,7 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan eventCh <- domain.SSEEvent{Type: "message_id", Content: messageId} userMessageId := uuid.New().String() // save user question to conversation message - if err := u.conversationUsecase.CreateChatConversationMessage(ctx, req.KBID, &domain.ConversationMessage{ + if saveErr := u.conversationUsecase.CreateChatConversationMessage(ctx, req.KBID, &domain.ConversationMessage{ ID: userMessageId, ConversationID: req.ConversationID, KBID: req.KBID, @@ -166,8 +166,8 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan Role: schema.User, Content: req.Message, RemoteIP: req.RemoteIP, - }); err != nil { - u.logger.Error("failed to save user question to conversation message", log.Error(err)) + }); saveErr != nil { + u.logger.Error("failed to save user question to conversation message", log.Error(saveErr)) eventCh <- domain.SSEEvent{Type: "error", Content: "failed to save user question to conversation message"} return } @@ -180,11 +180,11 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan } if len(blockWords) > 0 { // check --> filter questionFilter := utils.GetDFA(req.KBID) - if err := questionFilter.DFA.Check(req.Message); err != nil { // exist then return err + if checkErr := questionFilter.DFA.Check(req.Message); checkErr != nil { // exist then return err answer := "**您的问题包含敏感词, AI 无法回答您的问题。**" eventCh <- domain.SSEEvent{Type: "error", Content: answer} // save ai answer and set it err - if err := u.conversationUsecase.CreateChatConversationMessage(context.Background(), req.KBID, &domain.ConversationMessage{ + if saveBlockErr := u.conversationUsecase.CreateChatConversationMessage(ctx, req.KBID, &domain.ConversationMessage{ ID: messageId, ConversationID: req.ConversationID, KBID: req.KBID, @@ -195,8 +195,8 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan Model: string(req.ModelInfo.Model), RemoteIP: req.RemoteIP, ParentID: userMessageId, - }); err != nil { - u.logger.Error("failed to save assistant answer to conversation message", log.Error(err)) + }); saveBlockErr != nil { + u.logger.Error("failed to save assistant answer to conversation message", log.Error(saveBlockErr)) eventCh <- domain.SSEEvent{Type: "error", Content: "failed to save assistant answer to conversation message"} return } @@ -205,8 +205,8 @@ func (u *ChatUsecase) Chat(ctx context.Context, req *domain.ChatRequest) (<-chan } if req.Info.UserInfo.AuthUserID == 0 { - auth, _ := u.AuthRepo.GetAuthBySourceType(ctx, req.AppType.ToSourceType()) - if auth != nil { + auth, authErr := u.AuthRepo.GetAuthBySourceType(ctx, req.AppType.ToSourceType()) + if authErr == nil && auth != nil { req.Info.UserInfo.AuthUserID = auth.ID } } diff --git a/backend/usecase/comment.go b/backend/usecase/comment.go index c1a3a49c8..da74d2489 100644 --- a/backend/usecase/comment.go +++ b/backend/usecase/comment.go @@ -114,7 +114,7 @@ func (u *CommentUsecase) GetCommentListByNodeID(ctx context.Context, nodeID stri return comment }) // success - return domain.NewPaginatedResult(comments, uint64(total)), nil + return domain.NewPaginatedResult(comments, uint64(total)), nil //nolint:gosec // G115: Safe conversion, total is database count within uint64 range } func (u *CommentUsecase) GetCommentListByKbID(ctx context.Context, req *domain.CommentListReq, edition consts.LicenseEdition) (*domain.PaginatedResult[[]*domain.CommentListItem], error) { @@ -156,7 +156,7 @@ func (u *CommentUsecase) GetCommentListByKbID(ctx context.Context, req *domain.C return comment }) - return domain.NewPaginatedResult(comments, uint64(total)), nil + return domain.NewPaginatedResult(comments, uint64(total)), nil //nolint:gosec // G115: Safe conversion, total is database count within uint64 range } // 批量删除评论, (简单化,只删除传入评论id) diff --git a/backend/usecase/conversation.go b/backend/usecase/conversation.go index 127eba430..ab303c44c 100644 --- a/backend/usecase/conversation.go +++ b/backend/usecase/conversation.go @@ -248,7 +248,7 @@ func (u *ConversationUsecase) GetMessageList(ctx context.Context, req *domain.Me return message }) - return domain.NewPaginatedResult(messageList, uint64(total)), nil + return domain.NewPaginatedResult(messageList, uint64(total)), nil //nolint:gosec // G115: Safe conversion, total is database count within uint64 range } func (u *ConversationUsecase) GetMessageDetail(ctx context.Context, kbId, messageId string) (*domain.ConversationMessage, error) { diff --git a/backend/usecase/crawler.go b/backend/usecase/crawler.go index f37f9c40d..534a5e31b 100644 --- a/backend/usecase/crawler.go +++ b/backend/usecase/crawler.go @@ -37,7 +37,7 @@ func NewCrawlerUsecase(logger *log.Logger, mqConsumer mq.MQConsumer, cache *cach httpClient: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: true, //nolint:gosec // G402: Crawler may need to access various servers with self-signed certificates }, }, }, diff --git a/backend/usecase/knowledge_base.go b/backend/usecase/knowledge_base.go index 2467d45d0..befea7508 100644 --- a/backend/usecase/knowledge_base.go +++ b/backend/usecase/knowledge_base.go @@ -189,7 +189,7 @@ func (u *KnowledgeBaseUsecase) GetKBReleaseList(ctx context.Context, req *domain return nil, err } - return domain.NewPaginatedResult(releases, uint64(total)), nil + return domain.NewPaginatedResult(releases, uint64(total)), nil //nolint:gosec // G115: Safe conversion, total is database count within uint64 range } func (u *KnowledgeBaseUsecase) GetKBUserList(ctx context.Context, req v1.KBUserListReq) ([]v1.KBUserListItemResp, error) { diff --git a/backend/usecase/llm.go b/backend/usecase/llm.go index e63a6d93f..71f2ce4b0 100644 --- a/backend/usecase/llm.go +++ b/backend/usecase/llm.go @@ -210,9 +210,9 @@ func (u *LLMUsecase) SummaryNode(ctx context.Context, model *domain.Model, name, summaries := make([]string, 0, len(chunks)) for idx, chunk := range chunks { - summary, err := u.requestSummary(ctx, chatModel, name, chunk) - if err != nil { - u.logger.Error("Failed to generate summary for chunk", log.Int("chunk_index", idx), log.Error(err)) + summary, summaryErr := u.requestSummary(ctx, chatModel, name, chunk) + if summaryErr != nil { + u.logger.Error("Failed to generate summary for chunk", log.Int("chunk_index", idx), log.Error(summaryErr)) continue } if summary == "" { diff --git a/backend/usecase/model.go b/backend/usecase/model.go index e1bd4c3f2..06074332f 100644 --- a/backend/usecase/model.go +++ b/backend/usecase/model.go @@ -75,15 +75,15 @@ func (u *ModelUsecase) TriggerUpsertRecords(ctx context.Context) error { return fmt.Errorf("get knowledge base list failed: %w", err) } for _, kb := range kbList { - newDatasetID, err := u.ragStore.CreateKnowledgeBase(ctx) - if err != nil { - return fmt.Errorf("create new dataset failed: %w", err) + newDatasetID, createErr := u.ragStore.CreateKnowledgeBase(ctx) + if createErr != nil { + return fmt.Errorf("create new dataset failed: %w", createErr) } - if err := u.ragStore.DeleteKnowledgeBase(ctx, kb.DatasetID); err != nil { - return fmt.Errorf("delete old dataset failed: %w", err) + if deleteErr := u.ragStore.DeleteKnowledgeBase(ctx, kb.DatasetID); deleteErr != nil { + return fmt.Errorf("delete old dataset failed: %w", deleteErr) } - if err := u.kbRepo.UpdateDatasetID(ctx, kb.ID, newDatasetID); err != nil { - return fmt.Errorf("update knowledge base dataset id failed: %w", err) + if updateErr := u.kbRepo.UpdateDatasetID(ctx, kb.ID, newDatasetID); updateErr != nil { + return fmt.Errorf("update knowledge base dataset id failed: %w", updateErr) } } // traverse all nodes @@ -96,8 +96,8 @@ func (u *ModelUsecase) TriggerUpsertRecords(ctx context.Context) error { Action: "upsert", }, } - if err := u.ragRepo.AsyncUpdateNodeReleaseVector(ctx, nodeContentVectorRequests); err != nil { - return err + if asyncErr := u.ragRepo.AsyncUpdateNodeReleaseVector(ctx, nodeContentVectorRequests); asyncErr != nil { + return asyncErr } return nil }) @@ -228,8 +228,8 @@ func (u *ModelUsecase) updateModeSettingConfig(ctx context.Context, mode, apiKey } var config domain.ModelModeSetting - if err := json.Unmarshal(setting.Value, &config); err != nil { - return nil, fmt.Errorf("failed to parse current model setting: %w", err) + if unmarshalErr := json.Unmarshal(setting.Value, &config); unmarshalErr != nil { + return nil, fmt.Errorf("failed to parse current model setting: %w", unmarshalErr) } // 更新设置 diff --git a/backend/usecase/stat.go b/backend/usecase/stat.go index 8696e39a7..b436339a9 100644 --- a/backend/usecase/stat.go +++ b/backend/usecase/stat.go @@ -365,7 +365,7 @@ func (u *StatUseCase) GetConversationDistribution(ctx context.Context, kbID stri existDist.Count += v } else { mergedDistributions[key] = &domain.ConversationDistribution{ - AppType: domain.AppType(t), + AppType: domain.AppType(t), //nolint:gosec // G115: Safe conversion, t is validated enum value AppID: "", Count: v, } diff --git a/backend/usecase/wechat_service.go b/backend/usecase/wechat_service.go index c5b916876..1623248e7 100644 --- a/backend/usecase/wechat_service.go +++ b/backend/usecase/wechat_service.go @@ -42,7 +42,7 @@ func (u *WechatUsecase) WechatService(ctx context.Context, msg *wechatservice.We getQA := u.getQAFunc(kbID, domain.AppTypeWechatServiceBot) WechatServiceConfig.WeRepo = u.weRepo - err := WechatServiceConfig.Wechat(msg, getQA) + err := WechatServiceConfig.Wechat(ctx, msg, getQA) if err != nil { u.logger.Error("WechatServiceConf wechat failed", log.Error(err)) return err diff --git a/backend/usecase/wecom.go b/backend/usecase/wecom.go index 58d83a10d..15227f3d0 100644 --- a/backend/usecase/wecom.go +++ b/backend/usecase/wecom.go @@ -83,20 +83,20 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp switch req.Msgtype { case "text": // Generate conversation ID - id, err := uuid.NewV7() - if err != nil { - u.logger.Error("failed to generate conversation uuid", log.Error(err)) + id, uuidErr := uuid.NewV7() + if uuidErr != nil { + u.logger.Error("failed to generate conversation uuid", log.Error(uuidErr)) id = uuid.New() } conversationID := id.String() redisKey := fmt.Sprintf("wecom-aibot-%s", req.Msgid) - if err := u.cache.SetNX(ctx, redisKey, conversationID, 15*time.Minute).Err(); err != nil { + if cacheErr := u.cache.SetNX(ctx, redisKey, conversationID, 15*time.Minute).Err(); cacheErr != nil { u.logger.Error("failed to store conversation mapping in cache", log.String("redis_key", redisKey), log.String("conversation_id", conversationID), - log.Error(err)) - return "", fmt.Errorf("cache operation failed: %w", err) + log.Error(cacheErr)) + return "", fmt.Errorf("cache operation failed: %w", cacheErr) } // Get auth user for WeChat Work bot @@ -117,9 +117,9 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp _, loaded := domain.ConversationManager.LoadOrStore(conversationID, state) if !loaded { go func() { - bgCtx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + bgCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() - eventCh, err := u.chatUsecase.Chat(bgCtx, &domain.ChatRequest{ + eventCh, chatErr := u.chatUsecase.Chat(bgCtx, &domain.ChatRequest{ Message: req.Text.Content, KBID: kbID, AppType: domain.AppTypeWecomAIBot, @@ -134,15 +134,17 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp }, }, }) - if err != nil { - u.logger.Error("failed to create chat", log.Error(err)) + if chatErr != nil { + u.logger.Error("failed to create chat", log.Error(chatErr)) // Clean up state if val, ok := domain.ConversationManager.Load(conversationID); ok { - state := val.(*domain.ConversationState) - state.Mutex.Lock() - state.IsDone = true - state.Mutex.Unlock() - close(state.NotificationChan) + state, ok := val.(*domain.ConversationState) + if ok { + state.Mutex.Lock() + state.IsDone = true + state.Mutex.Unlock() + close(state.NotificationChan) + } } return } @@ -163,9 +165,19 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp redisKey := fmt.Sprintf("wecom-aibot-%s", req.Stream.Id) - conversationId, err := u.cache.Get(ctx, redisKey).Result() - if err != nil || conversationId == "" { - resp, err := wecomAIBotClient.MakeStreamResp(nonce, req.Stream.Id, "服务内部异常,请稍后重试", true) + conversationId, getErr := u.cache.Get(ctx, redisKey).Result() + if getErr != nil || conversationId == "" { + resp, respErr := wecomAIBotClient.MakeStreamResp(nonce, req.Stream.Id, "服务内部异常,请稍后重试", true) + if respErr != nil { + u.logger.Error("MakeStreamResp failed", log.Error(respErr)) + return "", respErr + } + return resp, nil + } + + val, ok := domain.ConversationManager.Load(conversationId) + if !ok { + resp, err := wecomAIBotClient.MakeStreamResp(nonce, req.Stream.Id, "服务暂时不可用,请稍后重试", true) if err != nil { u.logger.Error("MakeStreamResp failed", log.Error(err)) return "", err @@ -173,7 +185,7 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp return resp, nil } - val, ok := domain.ConversationManager.Load(conversationId) + state, ok := val.(*domain.ConversationState) if !ok { resp, err := wecomAIBotClient.MakeStreamResp(nonce, req.Stream.Id, "服务暂时不可用,请稍后重试", true) if err != nil { @@ -182,8 +194,6 @@ func (u *WecomUsecase) HandleMsg(ctx context.Context, kbID, signature, timestamp } return resp, nil } - - state := val.(*domain.ConversationState) state.Mutex.Lock() content := state.Buffer.String() state.Mutex.Unlock() @@ -217,7 +227,11 @@ func (u *WecomUsecase) SendQuestionToAI(conversationID string, eventCh <-chan do return } - state := val.(*domain.ConversationState) + state, ok := val.(*domain.ConversationState) + if !ok { + u.logger.Error("invalid conversation state type", log.String("conversation_id", conversationID)) + return + } defer func() { close(state.NotificationChan) // 标记为完成,但不立即删除,让 stream 请求可以继续拉取 diff --git a/backend/utils/epub.go b/backend/utils/epub.go index 06f585b91..8ade0a366 100644 --- a/backend/utils/epub.go +++ b/backend/utils/epub.go @@ -56,8 +56,8 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar if err != nil { return "", nil, err } - if err := valid(zipReader); err != nil { - return "", nil, err + if validErr := valid(zipReader); validErr != nil { + return "", nil, validErr } // read ./path/to/content.opf @@ -72,8 +72,8 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar } // resolve resource file - if err := e.uploadFile(ctx, kbID, zipReader); err != nil { - return "", nil, err + if uploadErr := e.uploadFile(ctx, kbID, zipReader); uploadErr != nil { + return "", nil, uploadErr } conv := converter.NewConverter( @@ -91,9 +91,9 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar for _, zipfile := range zipReader.File { ext := strings.ToLower(filepath.Ext(zipfile.Name)) if ext == ".ncx" { - file, err := zipfile.Open() - if err != nil { - return "", nil, err + file, openErr := zipfile.Open() + if openErr != nil { + return "", nil, openErr } defer file.Close() toc, err = ParseNCX(file) @@ -101,18 +101,18 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar return "", nil, err } } - file, err := zipfile.Open() - if err != nil { - return "", nil, err + file, openFileErr := zipfile.Open() + if openFileErr != nil { + return "", nil, openFileErr } defer file.Close() - htmlStr, err := io.ReadAll(file) - if err != nil { - return "", nil, err + htmlStr, readErr := io.ReadAll(file) + if readErr != nil { + return "", nil, readErr } - mdStr, err := conv.ConvertString((string(htmlStr))) - if err != nil { - return "", nil, err + mdStr, convErr := conv.ConvertString((string(htmlStr))) + if convErr != nil { + return "", nil, convErr } e.logger.Info("convert File", "file name", clearFileName(zipfile.Name)) res[clearFileName(zipfile.Name)] = bytes.NewBufferString(mdStr) @@ -121,8 +121,8 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar result := bytes.NewBuffer(nil) for _, href := range p.Guide.References { if r, ok := res[clearFileName(href.Href)]; ok { - if _, err := io.Copy(result, r); err != nil { - return "", nil, err + if _, copyErr := io.Copy(result, r); copyErr != nil { + return "", nil, copyErr } result.WriteString("\n\n") } @@ -140,8 +140,8 @@ func (e *EpubConverter) Convert(ctx context.Context, kbID string, data *multipar e.logger.Debug("add File", "file name", clearFileName(e.resourcesIdMap[itemRef.IDRef].Href)) if r, ok := res[clearFileName(e.resourcesIdMap[itemRef.IDRef].Href)]; ok { result.WriteString("\n\n") - if _, err := io.Copy(result, r); err != nil { - return "", nil, err + if _, copyResultErr := io.Copy(result, r); copyResultErr != nil { + return "", nil, copyResultErr } result.WriteString("\n\n") } diff --git a/backend/utils/utils.go b/backend/utils/utils.go index 3ee83f59a..6a0792620 100644 --- a/backend/utils/utils.go +++ b/backend/utils/utils.go @@ -37,12 +37,17 @@ func HTTPGet(url string) ([]byte, error) { Timeout: 10 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, + InsecureSkipVerify: true, //nolint:gosec // G402: General utility function, may need to access servers with self-signed certificates }, }, } - resp, err := client.Get(url) + req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request for %s: %v", url, err) + } + + resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to get %s: %v", url, err) } @@ -60,9 +65,8 @@ func DecodeBytes(data []byte) string { // try different encodings encodings := []string{"utf-8", "gbk", "gb2312", "big5"} for _, enc := range encodings { - if decoded, err := decode(data, enc); err == nil { - return decoded - } + decoded := decode(data, enc) + return decoded } return string(data) } @@ -113,11 +117,11 @@ func URLRemovePath(rawURL string) (string, error) { return parsedURL.String(), nil } -// decode decode bytes with specified encoding -func decode(data []byte, encoding string) (string, error) { +// decode decode bytes to string +func decode(data []byte, _ string) string { // need to implement encoding conversion based on actual needs // use golang.org/x/text/encoding package - return string(data), nil + return string(data) } // GetHeaderMap get header map @@ -178,7 +182,13 @@ func UploadImage(ctx context.Context, minioClient *s3.MinioClient, imageURL stri var data []byte var contentType string if strings.HasPrefix(imageURL, "http://") || strings.HasPrefix(imageURL, "https://") { - resp, err := http.Get(imageURL) + req, err := http.NewRequestWithContext(ctx, "GET", imageURL, nil) + if err != nil { + return "", fmt.Errorf("failed to create request: %v", err) + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to fetch image: %v", err) } @@ -200,7 +210,7 @@ func UploadImage(ctx context.Context, minioClient *s3.MinioClient, imageURL stri } else { // 从本地文件系统读取图片 var err error - data, err = os.ReadFile(imageURL) + data, err = os.ReadFile(imageURL) //nolint:gosec // G304: imageURL is from trusted internal source for file upload if err != nil { return "", fmt.Errorf("failed to read image file: %v", err) }