Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 77 additions & 3 deletions backend/.golangci.toml
Original file line number Diff line number Diff line change
@@ -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"]
# goimports 已经包含 gofmt 能力
enable = ["goimports"]

5 changes: 4 additions & 1 deletion backend/consts/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions backend/handler/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
4 changes: 2 additions & 2 deletions backend/handler/mq/rag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions backend/handler/share/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 --> 需要开启客服消息权限
Expand Down
5 changes: 4 additions & 1 deletion backend/handler/share/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions backend/handler/share/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions backend/handler/share/comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,17 @@ 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
// 判断user is moderate comment ---> 默认false
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)
Expand Down
4 changes: 3 additions & 1 deletion backend/handler/share/stat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 4 additions & 1 deletion backend/handler/share/wechat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion backend/handler/v1/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
9 changes: 8 additions & 1 deletion backend/middleware/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
9 changes: 8 additions & 1 deletion backend/middleware/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package middleware

import (
"context"
"fmt"
"net/http"
"time"

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion backend/pkg/anydoc/anydoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
},
},
Expand Down
22 changes: 17 additions & 5 deletions backend/pkg/bot/dingtalk/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -318,17 +318,25 @@ 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)
}
// 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)
Expand All @@ -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))
Expand Down
10 changes: 8 additions & 2 deletions backend/pkg/bot/discord/discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
}
Expand Down
19 changes: 10 additions & 9 deletions backend/pkg/bot/feishu/stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading