From a8b41ddb48e6fb875c42b77a7f28caed710e9c15 Mon Sep 17 00:00:00 2001 From: Neko Ayaka Date: Fri, 5 May 2023 17:18:30 +0800 Subject: [PATCH] chore: added golangci-lint (#42) --- .github/workflows/ci.yml | 91 +++++++------ .golangci.yml | 57 ++++++++ cmd/insights-bot/main.go | 16 +-- .../bots/telegram/handlers/recap/recap.go | 34 ++--- .../handlers/recap/recap_callback_query.go | 28 ++-- .../telegram/handlers/recap/toggle_recap.go | 128 +++++++++--------- .../handlers/summarize/smr_channel_post.go | 1 + .../handlers/summarize/smr_command.go | 5 +- .../telegram/middlewares/record_messsages.go | 4 +- internal/bots/telegram/telegram.go | 8 +- internal/configs/configs.go | 8 +- internal/datastore/datastore.go | 4 +- internal/datastore/ent.go | 1 + .../chat_histories.go | 29 ++-- .../chat_histories_test.go | 2 +- internal/models/models.go | 4 +- internal/models/smr/smr.go | 6 +- internal/models/smr/smr_test.go | 1 + .../autorecap.go} | 10 +- internal/services/pprof/pprof.go | 78 +++++++++++ internal/services/services.go | 6 +- pkg/bots/tgbot/context.go | 1 + pkg/bots/tgbot/dispatcher.go | 5 +- pkg/bots/tgbot/errors.go | 1 + pkg/bots/tgbot/handler.go | 1 + pkg/bots/tgbot/help_command.go | 2 + pkg/bots/tgbot/response.go | 1 + pkg/bots/tgbot/utils.go | 6 +- pkg/bots/tgbot/utils_test.go | 1 + pkg/logger/format.go | 22 ++- pkg/logger/logger.go | 37 +++-- pkg/openai/openai.go | 18 +-- pkg/openai/openai_test.go | 6 +- pkg/utils/concurrent.go | 8 +- pkg/utils/debug.go | 32 +++-- pkg/utils/debug_test.go | 1 + pkg/utils/file.go | 1 + pkg/utils/path.go | 4 +- pkg/utils/random.go | 44 +++--- pkg/utils/random_test.go | 6 +- pkg/utils/units.go | 8 +- 41 files changed, 464 insertions(+), 262 deletions(-) create mode 100644 .golangci.yml rename internal/models/{chat_histories => chathistories}/chat_histories.go (94%) rename internal/models/{chat_histories => chathistories}/chat_histories_test.go (99%) rename internal/services/{chat_history_recap/chat_history_recap.go => autorecap/autorecap.go} (94%) create mode 100644 internal/services/pprof/pprof.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fc7074..6c601bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,53 +11,49 @@ on: - "**/*.md" jobs: - scan: - runs-on: ubuntu-latest - steps: - # 代码签出 - - uses: actions/checkout@v3 - - # 设定 Go 环境 - - uses: actions/setup-go@v3 - with: - go-version: "^1.20.0" - cache: true + # scan: + # runs-on: ubuntu-latest + # steps: + # # 代码签出 + # - uses: actions/checkout@v3 - # Get values for cache paths to be used in later steps - - name: Setup Go Cache PATH - id: go-cache-paths - run: | - echo "go-build=$(go env GOCACHE)" >> $GITHUB_OUTPUT - echo "go-mod=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT + # # 设定 Go 环境 + # - uses: actions/setup-go@v3 + # with: + # go-version: "^1.20.0" + # cache: true - # Cache go build cache, used to speedup go test - - name: Go Build Cache - uses: actions/cache@v2 - with: - path: ${{ steps.go-cache-paths.outputs.go-build }} - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} + # # Get values for cache paths to be used in later steps + # - name: Setup Go Cache PATH + # id: go-cache-paths + # run: | + # echo "go-build=$(go env GOCACHE)" >> $GITHUB_OUTPUT + # echo "go-mod=$(go env GOMODCACHE)" >> $GITHUB_OUTPUT - # Cache go mod cache, used to speedup builds - - name: Go Mod Cache - uses: actions/cache@v2 - with: - path: ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} + # # Cache go build cache, used to speedup go test + # - name: Go Build Cache + # uses: actions/cache@v2 + # with: + # path: ${{ steps.go-cache-paths.outputs.go-build }} + # key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - name: Setup govulncheck - run: go install golang.org/x/vuln/cmd/govulncheck@latest + # # Cache go mod cache, used to speedup builds + # - name: Go Mod Cache + # uses: actions/cache@v2 + # with: + # path: ${{ steps.go-cache-paths.outputs.go-mod }} + # key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - # 代码检查潜在错误 - - name: Vet (Scan for potential mistakes) - run: | - go vet ./internal/... - go vet ./pkg/... - go vet ./cmd/... + # - name: Setup govulncheck + # run: go install golang.org/x/vuln/cmd/govulncheck@latest - govulncheck ./internal/... - govulncheck ./pkg/... - govulncheck ./cmd/... + # # 代码检查潜在错误 + # - name: Vet (Scan for potential mistakes) + # run: | + # go vet ./... + # govulncheck ./... buildtest: + name: Build Test runs-on: ubuntu-latest steps: # 代码签出 @@ -93,7 +89,9 @@ jobs: # 测试构建 - name: Test Build run: go build -a -o "release/insights-bot" "github.com/nekomeowww/insights-bot/cmd/insights-bot" + ent_check: + name: Check entgo.io codegen runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -102,6 +100,19 @@ jobs: go-version: "^1.20" - uses: ent/contrib/ci@master + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v4 + with: + go-version: "^1.20.0" + cache: true + + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3.4.0 + # unittest: # # 运行目标 # runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..fa533b3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,57 @@ +linters: + enable: + # - contextcheck # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + - errcheck + - gosimple + # - govet # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + - ineffassign + - staticcheck + - typecheck + - unused + + # - bodyclose # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + - containedctx + - depguard + - dupl + - durationcheck + - errname + - exhaustive + - exportloopref + - forcetypeassert + - godot + - gofmt + - goheader + - goprintffuncname + - gosec + - musttag + - nestif + # - nilerr # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + # - noctx # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + - nolintlint + - nosprintfhostport + - prealloc + - predeclared + - reassign + - revive + - tenv + - testableexamples + - unconvert + # - unparam # due to https://github.com/golangci/golangci-lint/issues/3086#issuecomment-1475232706 + - usestdlibvars + - whitespace + - wsl + +linters-settings: + wsl: + allow-assign-and-call: false + revive: + rules: + - name: blank-imports + disabled: true + +issues: + exclude: + - "if statements should only be cuddled with assignments" # from wsl + - "if statements should only be cuddled with assignments used in the if statement itself" # from wsl + - "assignments should only be cuddled with other assignments" # from wsl. false positive case: var a bool\nb := true + diff --git a/cmd/insights-bot/main.go b/cmd/insights-bot/main.go index ba86243..6071a8a 100644 --- a/cmd/insights-bot/main.go +++ b/cmd/insights-bot/main.go @@ -3,8 +3,6 @@ package main import ( "context" "log" - "net/http" - _ "net/http/pprof" "time" "go.uber.org/fx" @@ -16,7 +14,8 @@ import ( "github.com/nekomeowww/insights-bot/internal/lib" "github.com/nekomeowww/insights-bot/internal/models" "github.com/nekomeowww/insights-bot/internal/services" - "github.com/nekomeowww/insights-bot/internal/services/chat_history_recap" + "github.com/nekomeowww/insights-bot/internal/services/autorecap" + "github.com/nekomeowww/insights-bot/internal/services/pprof" "github.com/nekomeowww/insights-bot/internal/thirdparty" ) @@ -31,19 +30,16 @@ func main() { fx.Options(telegram.NewModules()), fx.Options(slack.NewModules()), fx.Invoke(telegram.Run()), - fx.Invoke(chat_history_recap.Run()), + fx.Invoke(autorecap.Run()), fx.Invoke(slack.Run()), - fx.Invoke(func() { - err := http.ListenAndServe(":6060", nil) - if err != nil { - log.Println(err) - } - }), + fx.Invoke(pprof.Run()), )) app.Run() + stopCtx, stopCtxCancel := context.WithTimeout(context.Background(), time.Second*15) defer stopCtxCancel() + if err := app.Stop(stopCtx); err != nil { log.Fatal(err) } diff --git a/internal/bots/telegram/handlers/recap/recap.go b/internal/bots/telegram/handlers/recap/recap.go index 2546670..c43e7c2 100644 --- a/internal/bots/telegram/handlers/recap/recap.go +++ b/internal/bots/telegram/handlers/recap/recap.go @@ -23,21 +23,21 @@ func NewModules() fx.Option { } var ( - _ tgbot.CommandHandler = (*RecapCommandHandler)(nil) + _ tgbot.CommandHandler = (*CommandHandler)(nil) ) type NewHandlersParams struct { fx.In - RecapCommand *RecapCommandHandler - RecapCallbackQuery *RecapCallbackQueryHandler + RecapCommand *CommandHandler + RecapCallbackQuery *CallbackQueryHandler EnableRecapCommand *EnableRecapCommandHandler DisableRecapCommand *DisableRecapCommandHandler } type Handlers struct { - recapCommand *RecapCommandHandler - recapCallbackQuery *RecapCallbackQueryHandler + recapCommand *CommandHandler + recapCallbackQuery *CallbackQueryHandler enableRecapCommand *EnableRecapCommandHandler disableRecapCommand *DisableRecapCommandHandler } @@ -61,44 +61,44 @@ func (h *Handlers) Install(dispatcher *tgbot.Dispatcher) { } var ( - RecapSelectHourAvailables = []int64{ + RecapSelectHourAvailable = []int64{ 1, 2, 4, 6, 12, } - RecapSelectHourAvailableText = lo.SliceToMap(RecapSelectHourAvailables, func(item int64) (int64, string) { + RecapSelectHourAvailableText = lo.SliceToMap(RecapSelectHourAvailable, func(item int64) (int64, string) { return item, fmt.Sprintf("%d 小时", item) }) - RecapSelectHourAvailableValues = lo.SliceToMap(RecapSelectHourAvailables, func(item int64) (int64, string) { + RecapSelectHourAvailableValues = lo.SliceToMap(RecapSelectHourAvailable, func(item int64) (int64, string) { return item, fmt.Sprintf("%d", item) }) ) -type NewRecapCommandHandlerParams struct { +type NewCommandHandlerParams struct { fx.In TgChats *tgchats.Model } -type RecapCommandHandler struct { +type CommandHandler struct { tgchats *tgchats.Model } -func NewRecapCommandHandler() func(NewRecapCommandHandlerParams) *RecapCommandHandler { - return func(param NewRecapCommandHandlerParams) *RecapCommandHandler { - return &RecapCommandHandler{ +func NewRecapCommandHandler() func(NewCommandHandlerParams) *CommandHandler { + return func(param NewCommandHandlerParams) *CommandHandler { + return &CommandHandler{ tgchats: param.TgChats, } } } -func (h RecapCommandHandler) Command() string { +func (h CommandHandler) Command() string { return "recap" } -func (h RecapCommandHandler) CommandHelp() string { +func (h CommandHandler) CommandHelp() string { return "总结过去的聊天记录并生成回顾快报" } -func (h *RecapCommandHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { +func (h *CommandHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { enabled, err := h.tgchats.HasChatHistoriesRecapEnabled(c.Update.Message.Chat.ID, telegram.ChatType(c.Update.Message.Chat.Type)) if err != nil { return nil, tgbot.NewExceptionError(err).WithMessage("生成失败,请稍后再试。").WithReply(c.Update.Message) @@ -109,7 +109,7 @@ func (h *RecapCommandHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { replyMarkupKeyboard := tgbotapi.NewInlineKeyboardMarkup( tgbotapi.NewInlineKeyboardRow( - lo.Map(RecapSelectHourAvailables, func(item int64, _ int) tgbotapi.InlineKeyboardButton { + lo.Map(RecapSelectHourAvailable, func(item int64, _ int) tgbotapi.InlineKeyboardButton { return tgbotapi.NewInlineKeyboardButtonData( RecapSelectHourAvailableText[item], tgbot.NewCallbackQueryData("recap", "select_hour", url.Values{"hour": []string{RecapSelectHourAvailableValues[item]}}), diff --git a/internal/bots/telegram/handlers/recap/recap_callback_query.go b/internal/bots/telegram/handlers/recap/recap_callback_query.go index 5a55b8f..eed3414 100644 --- a/internal/bots/telegram/handlers/recap/recap_callback_query.go +++ b/internal/bots/telegram/handlers/recap/recap_callback_query.go @@ -8,54 +8,55 @@ import ( "github.com/samber/lo" "go.uber.org/fx" - "github.com/nekomeowww/insights-bot/internal/models/chat_histories" + "github.com/nekomeowww/insights-bot/internal/models/chathistories" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" "github.com/nekomeowww/insights-bot/pkg/logger" ) var ( - _ tgbot.CallbackQueryHandler = (*RecapCallbackQueryHandler)(nil) + _ tgbot.CallbackQueryHandler = (*CallbackQueryHandler)(nil) ) type NewRecapCallbackQueryHandlerParams struct { fx.In Logger *logger.Logger - ChatHistories *chat_histories.Model + ChatHistories *chathistories.Model } -type RecapCallbackQueryHandler struct { +type CallbackQueryHandler struct { logger *logger.Logger - chatHistories *chat_histories.Model + chatHistories *chathistories.Model } -func NewRecapCallbackQueryHandler() func(NewRecapCallbackQueryHandlerParams) *RecapCallbackQueryHandler { - return func(param NewRecapCallbackQueryHandlerParams) *RecapCallbackQueryHandler { - return &RecapCallbackQueryHandler{ +func NewRecapCallbackQueryHandler() func(NewRecapCallbackQueryHandlerParams) *CallbackQueryHandler { + return func(param NewRecapCallbackQueryHandlerParams) *CallbackQueryHandler { + return &CallbackQueryHandler{ logger: param.Logger, chatHistories: param.ChatHistories, } } } -type RecapSelectHourCallbackQueryData struct { +type SelectHourCallbackQueryData struct { Hour int64 `schema:"hour" json:"hour"` } -func (h RecapCallbackQueryHandler) CallbackQueryRoute() string { +func (h CallbackQueryHandler) CallbackQueryRoute() string { return "recap/select_hour" } -func (h *RecapCallbackQueryHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { +func (h *CallbackQueryHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { chatID := c.Update.CallbackQuery.Message.Chat.ID messageID := c.Update.CallbackQuery.Message.MessageID - var data RecapSelectHourCallbackQueryData + var data SelectHourCallbackQueryData + err := c.CallbackQueryDataBindQuery(&data) if err != nil { return nil, tgbot.NewExceptionError(err) } - if !lo.Contains(RecapSelectHourAvailables, data.Hour) { + if !lo.Contains(RecapSelectHourAvailable, data.Hour) { return nil, tgbot.NewExceptionError(fmt.Errorf("invalid hour: %d", data.Hour)).WithReply(c.Update.CallbackQuery.Message.ReplyToMessage) } @@ -92,5 +93,6 @@ func (h *RecapCallbackQueryHandler) Handle(c *tgbot.Context) (tgbot.Response, er } h.logger.Infof("sending chat histories recap for chat %d: %s", chatID, summarization) + return c.NewMessageReplyTo(fmt.Sprintf("%s\n\n#recap\n🤖️ Generated by chatGPT", summarization), c.Update.CallbackQuery.Message.ReplyToMessage.MessageID).WithParseModeHTML(), nil } diff --git a/internal/bots/telegram/handlers/recap/toggle_recap.go b/internal/bots/telegram/handlers/recap/toggle_recap.go index 7f6c8f1..2d545b1 100644 --- a/internal/bots/telegram/handlers/recap/toggle_recap.go +++ b/internal/bots/telegram/handlers/recap/toggle_recap.go @@ -10,6 +10,64 @@ import ( "go.uber.org/fx" ) +func checkTogglingRecapPermission(chatID, userID int64, update tgbotapi.Update, bot *tgbot.Bot) error { + member, err := bot.GetChatMember(tgbotapi.GetChatMemberConfig{ + ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ + ChatID: chatID, + UserID: userID, + }, + }) + if err != nil { + return tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(update.Message) + } + if !lo.Contains([]telegram.MemberStatus{ + telegram.MemberStatusCreator, + telegram.MemberStatusAdministrator, + }, telegram.MemberStatus(member.Status)) { + return tgbot.NewMessageError("你没有权限关闭聊天记录回顾功能哦!").WithReply(update.Message) + } + + return nil +} + +func checkBotMember(chatID int64, update tgbotapi.Update, bot *tgbot.Bot) error { + botMember, err := bot.GetChatMember(tgbotapi.GetChatMemberConfig{ + ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ + ChatID: chatID, + UserID: bot.Self.ID, + }, + }) + if err != nil { + return tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(update.Message) + } + if !lo.Contains([]telegram.MemberStatus{ + telegram.MemberStatusAdministrator, + }, telegram.MemberStatus(botMember.Status)) { + return tgbot.NewMessageError("现在机器人不是群组管理员,已经不会记录任何聊天记录了。如果需要打开聊天记录回顾功能,请先将机器人设为群组管理员。").WithReply(update.Message) + } + + return nil +} + +func checkToggle(update tgbotapi.Update, bot *tgbot.Bot) error { + chatType := telegram.ChatType(update.Message.Chat.Type) + if !lo.Contains([]telegram.ChatType{telegram.ChatTypeGroup, telegram.ChatTypeSuperGroup}, chatType) { + return tgbot.NewMessageError("聊天记录回顾功能只有群组和超级群组可以配置开启哦!").WithReply(update.Message) + } + + err := checkTogglingRecapPermission(update.Message.Chat.ID, update.Message.From.ID, update, bot) + if err != nil { + return err + } + + err = checkBotMember(update.Message.Chat.ID, update, bot) + if err != nil { + return err + } + + return nil +} + var ( _ tgbot.CommandHandler = (*EnableRecapCommandHandler)(nil) _ tgbot.CommandHandler = (*DisableRecapCommandHandler)(nil) @@ -46,31 +104,10 @@ func (h EnableRecapCommandHandler) CommandHelp() string { func (h *EnableRecapCommandHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { chatType := telegram.ChatType(c.Update.Message.Chat.Type) - if !lo.Contains([]telegram.ChatType{telegram.ChatTypeGroup, telegram.ChatTypeSuperGroup}, chatType) { - return nil, tgbot.NewMessageError("聊天记录回顾功能只有群组和超级群组可以配置开启哦!").WithReply(c.Update.Message) - } - - hasTogglingRecapPermission, err := checkTogglingRecapPermission(c.Bot, c.Update.Message.Chat.ID, c.Update.Message.From.ID) - if err != nil { - return nil, tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(c.Update.Message) - } - if !hasTogglingRecapPermission { - return nil, tgbot.NewMessageError("你没有权限开启聊天记录回顾功能哦!").WithReply(c.Update.Message) - } - botMember, err := c.Bot.GetChatMember(tgbotapi.GetChatMemberConfig{ - ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ - ChatID: c.Update.Message.Chat.ID, - UserID: c.Bot.Self.ID, - }, - }) + err := checkToggle(c.Update, c.Bot) if err != nil { - return nil, tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(c.Update.Message) - } - if !lo.Contains([]telegram.MemberStatus{ - telegram.MemberStatusAdministrator, - }, telegram.MemberStatus(botMember.Status)) { - return nil, tgbot.NewMessageError("请先将机器人设为群组管理员,然后再开启聊天记录回顾功能哦!").WithReply(c.Update.Message) + return nil, err } err = h.tgchats.EnableChatHistoriesRecap(c.Update.Message.Chat.ID, chatType) @@ -112,31 +149,10 @@ func (h DisableRecapCommandHandler) CommandHelp() string { func (h *DisableRecapCommandHandler) Handle(c *tgbot.Context) (tgbot.Response, error) { chatType := telegram.ChatType(c.Update.Message.Chat.Type) - if !lo.Contains([]telegram.ChatType{telegram.ChatTypeGroup, telegram.ChatTypeSuperGroup}, chatType) { - return nil, tgbot.NewMessageError("聊天记录回顾功能只有群组和超级群组可以配置关闭哦!").WithReply(c.Update.Message) - } - hasTogglingRecapPermission, err := checkTogglingRecapPermission(c.Bot, c.Update.Message.Chat.ID, c.Update.Message.From.ID) + err := checkToggle(c.Update, c.Bot) if err != nil { - return nil, tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(c.Update.Message) - } - if !hasTogglingRecapPermission { - return nil, tgbot.NewMessageError("你没有权限关闭聊天记录回顾功能哦!").WithReply(c.Update.Message) - } - - botMember, err := c.Bot.GetChatMember(tgbotapi.GetChatMemberConfig{ - ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ - ChatID: c.Update.Message.Chat.ID, - UserID: c.Bot.Self.ID, - }, - }) - if err != nil { - return nil, tgbot.NewExceptionError(err).WithMessage("聊天记录回顾功能开启失败,请稍后再试!").WithReply(c.Update.Message) - } - if !lo.Contains([]telegram.MemberStatus{ - telegram.MemberStatusAdministrator, - }, telegram.MemberStatus(botMember.Status)) { - return nil, tgbot.NewMessageError("现在机器人不是群组管理员,已经不会记录任何聊天记录了。如果需要打开聊天记录回顾功能,请先将机器人设为群组管理员。").WithReply(c.Update.Message) + return nil, err } err = h.tgchats.DisableChatHistoriesRecap(c.Update.Message.Chat.ID, chatType) @@ -146,23 +162,3 @@ func (h *DisableRecapCommandHandler) Handle(c *tgbot.Context) (tgbot.Response, e return c.NewMessageReplyTo("聊天记录回顾功能已关闭,关闭后将不会自动收集群组中的聊天记录并定时发送聊天回顾快报了。", c.Update.Message.MessageID), nil } - -func checkTogglingRecapPermission(bot *tgbot.Bot, chatID, userID int64) (bool, error) { - member, err := bot.GetChatMember(tgbotapi.GetChatMemberConfig{ - ChatConfigWithUser: tgbotapi.ChatConfigWithUser{ - ChatID: chatID, - UserID: userID, - }, - }) - if err != nil { - return false, err - } - if !lo.Contains([]telegram.MemberStatus{ - telegram.MemberStatusCreator, - telegram.MemberStatusAdministrator, - }, telegram.MemberStatus(member.Status)) { - return false, nil - } - - return true, nil -} diff --git a/internal/bots/telegram/handlers/summarize/smr_channel_post.go b/internal/bots/telegram/handlers/summarize/smr_channel_post.go index ee44e26..08147a2 100644 --- a/internal/bots/telegram/handlers/summarize/smr_channel_post.go +++ b/internal/bots/telegram/handlers/summarize/smr_channel_post.go @@ -21,6 +21,7 @@ func (h *Handlers) HandleChannelPost(c *tgbot.Context) (tgbot.Response, error) { } urlString := strings.TrimSpace(strings.TrimPrefix(c.Update.ChannelPost.Text, "/smr ")) + summarization, err := h.smr.SummarizeInputURL(urlString) if err != nil { return nil, tgbot.NewExceptionError(err) diff --git a/internal/bots/telegram/handlers/summarize/smr_command.go b/internal/bots/telegram/handlers/summarize/smr_command.go index be1fdda..4627a05 100644 --- a/internal/bots/telegram/handlers/summarize/smr_command.go +++ b/internal/bots/telegram/handlers/summarize/smr_command.go @@ -35,6 +35,7 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { message := tgbotapi.NewMessage(c.Update.Message.Chat.ID, "请稍等,量子速读中...") message.ReplyToMessageID = c.Update.Message.MessageID + processingMessage, err := c.Bot.Send(message) if err != nil { return nil, tgbot.NewExceptionError(err) @@ -47,9 +48,9 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { } if errors.Is(err, smr.ErrNetworkError) || errors.Is(err, smr.ErrRequestFailed) { return nil, tgbot.NewMessageError("量子速读的链接读取失败了哦。可以再试试?").WithEdit(&processingMessage) - } else { - return nil, tgbot.NewMessageError("量子速读失败了。可以再试试?").WithEdit(&processingMessage) } + + return nil, tgbot.NewMessageError("量子速读失败了。可以再试试?").WithEdit(&processingMessage) } return c.NewMessageReplyTo(summarization.FormatSummarizationAsHTML(), c.Update.Message.MessageID).WithParseModeHTML(), nil diff --git a/internal/bots/telegram/middlewares/record_messsages.go b/internal/bots/telegram/middlewares/record_messsages.go index f5bbba8..570120b 100644 --- a/internal/bots/telegram/middlewares/record_messsages.go +++ b/internal/bots/telegram/middlewares/record_messsages.go @@ -1,14 +1,14 @@ package middlewares import ( - "github.com/nekomeowww/insights-bot/internal/models/chat_histories" + "github.com/nekomeowww/insights-bot/internal/models/chathistories" "github.com/nekomeowww/insights-bot/internal/models/tgchats" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" "github.com/nekomeowww/insights-bot/pkg/types/telegram" "github.com/samber/lo" ) -func RecordMessage(chatHistories *chat_histories.Model, tgchats *tgchats.Model) func(c *tgbot.Context, next func()) { +func RecordMessage(chatHistories *chathistories.Model, tgchats *tgchats.Model) func(c *tgbot.Context, next func()) { return func(c *tgbot.Context, next func()) { if c.Update.Message == nil { return diff --git a/internal/bots/telegram/telegram.go b/internal/bots/telegram/telegram.go index 013e8db..8e34e13 100644 --- a/internal/bots/telegram/telegram.go +++ b/internal/bots/telegram/telegram.go @@ -9,7 +9,7 @@ import ( "github.com/nekomeowww/insights-bot/internal/bots/telegram/handlers" "github.com/nekomeowww/insights-bot/internal/bots/telegram/middlewares" "github.com/nekomeowww/insights-bot/internal/configs" - "github.com/nekomeowww/insights-bot/internal/models/chat_histories" + "github.com/nekomeowww/insights-bot/internal/models/chathistories" "github.com/nekomeowww/insights-bot/internal/models/tgchats" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" "github.com/nekomeowww/insights-bot/pkg/logger" @@ -34,7 +34,7 @@ type NewBotParam struct { Dispatcher *tgbot.Dispatcher Handlers *handlers.Handlers - ChatHistories *chat_histories.Model + ChatHistories *chathistories.Model TgChats *tgchats.Model } @@ -45,7 +45,7 @@ type Bot struct { Logger *logger.Logger Dispatcher *tgbot.Dispatcher - ChatHistories *chat_histories.Model + ChatHistories *chathistories.Model alreadyClose bool closeChan chan struct{} @@ -80,6 +80,7 @@ func NewBot() func(param NewBotParam) (*Bot, error) { param.Dispatcher.Use(middlewares.RecordMessage(param.ChatHistories, param.TgChats)) param.Handlers.InstallAll() param.Logger.Infof("Authorized as bot @%s", bot.Self.UserName) + return bot, nil } } @@ -104,6 +105,7 @@ func (b *Bot) pullUpdates() { u.Timeout = 60 updates := b.GetUpdatesChan(u) + for { if b.alreadyClose { b.Logger.Info("stopped to receiving updates") diff --git a/internal/configs/configs.go b/internal/configs/configs.go index 08771e5..6e78c59 100644 --- a/internal/configs/configs.go +++ b/internal/configs/configs.go @@ -5,16 +5,16 @@ import ( ) const ( - EnvTelegramBotToken = "TELEGRAM_BOT_TOKEN" + EnvTelegramBotToken = "TELEGRAM_BOT_TOKEN" //nolint:gosec - EnvSlackBotToken = "SLACK_BOT_TOKEN" + EnvSlackBotToken = "SLACK_BOT_TOKEN" //nolint:gosec EnvSlackBotPort = "SLACK_BOT_PORT" - EnvOpenAIAPISecret = "OPENAI_API_SECRET" + EnvOpenAIAPISecret = "OPENAI_API_SECRET" //nolint:gosec EnvOpenAIAPIHost = "OPENAI_API_HOST" EnvPineconeProjectName = "PINECONE_PROJECT_NAME" EnvPineconeEnvironment = "PINECONE_ENVIRONMENT" - EnvPineconeAPIKey = "PINECONE_API_KEY" + EnvPineconeAPIKey = "PINECONE_API_KEY" //nolint:gosec EnvPineconeChatHistoryIndexName = "PINECONE_CHAT_HISTORY_INDEX_NAME" EnvDBConnectionString = "DB_CONNECTION_STR" ) diff --git a/internal/datastore/datastore.go b/internal/datastore/datastore.go index bd71260..027c846 100644 --- a/internal/datastore/datastore.go +++ b/internal/datastore/datastore.go @@ -1,6 +1,8 @@ package datastore -import "go.uber.org/fx" +import ( + "go.uber.org/fx" +) func NewModules() fx.Option { return fx.Options( diff --git a/internal/datastore/ent.go b/internal/datastore/ent.go index 2354006..7556f52 100644 --- a/internal/datastore/ent.go +++ b/internal/datastore/ent.go @@ -24,6 +24,7 @@ type Ent struct { func NewEnt() func(NewEntParams) (*Ent, error) { return func(param NewEntParams) (*Ent, error) { opts := make([]ent.Option, 0) + client, err := ent.Open(dialect.Postgres, param.Configs.DB.ConnectionString, opts...) if err != nil { return nil, err diff --git a/internal/models/chat_histories/chat_histories.go b/internal/models/chathistories/chat_histories.go similarity index 94% rename from internal/models/chat_histories/chat_histories.go rename to internal/models/chathistories/chat_histories.go index 9092748..9700532 100644 --- a/internal/models/chat_histories/chat_histories.go +++ b/internal/models/chathistories/chat_histories.go @@ -1,4 +1,4 @@ -package chat_histories +package chathistories import ( "context" @@ -124,6 +124,7 @@ func (m *Model) SaveOneTelegramChatHistory(message *tgbotapi.Message) error { "message_id": telegramChatHistory.MessageID, "text": strings.ReplaceAll(telegramChatHistory.Text, "\n", " "), }).Debug("saved one telegram chat history") + return nil } @@ -137,6 +138,7 @@ func (m *Model) FindLastSixHourChatHistories(chatID int64) ([]*ent.ChatHistories func (m *Model) FindChatHistoriesByTimeBefore(chatID int64, before time.Duration) ([]*ent.ChatHistories, error) { m.logger.Infof("querying chat histories for %d", chatID) + telegramChatHistories, err := m.ent.ChatHistories. Query(). Where( @@ -192,9 +194,10 @@ var RecapOutputTemplate = lo.Must(template. {{ end }}{{ end }}`)) -func (c *Model) summarizeChatHistoriesSlice(s string) ([]*openai.ChatHistorySummarizationOutputs, error) { - c.logger.Infof("✍️ summarizing last one hour chat histories:\n%s", s) - resp, err := c.openAI.SummarizeWithChatHistories(context.Background(), s) +func (m *Model) summarizeChatHistoriesSlice(s string) ([]*openai.ChatHistorySummarizationOutputs, error) { + m.logger.Infof("✍️ summarizing last one hour chat histories:\n%s", s) + + resp, err := m.openAI.SummarizeWithChatHistories(context.Background(), s) if err != nil { return nil, err } @@ -202,7 +205,7 @@ func (c *Model) summarizeChatHistoriesSlice(s string) ([]*openai.ChatHistorySumm return nil, nil } - c.logger.WithFields(logrus.Fields{ + m.logger.WithFields(logrus.Fields{ "prompt_token_usage": resp.Usage.PromptTokens, "completion_token_usage": resp.Usage.CompletionTokens, "total_token_usage": resp.Usage.TotalTokens, @@ -212,17 +215,19 @@ func (c *Model) summarizeChatHistoriesSlice(s string) ([]*openai.ChatHistorySumm } var outputs []*openai.ChatHistorySummarizationOutputs + err = json.Unmarshal([]byte(resp.Choices[0].Message.Content), &outputs) if err != nil { - c.logger.Errorf("failed to unmarshal chat history summarization output: %s", resp.Choices[0].Message.Content) + m.logger.Errorf("failed to unmarshal chat history summarization output: %s", resp.Choices[0].Message.Content) return nil, err } return outputs, nil } -func (c *Model) SummarizeChatHistories(chatID int64, histories []*ent.ChatHistories) (string, error) { +func (m *Model) SummarizeChatHistories(chatID int64, histories []*ent.ChatHistories) (string, error) { historiesLLMFriendly := make([]string, 0, len(histories)) + for _, message := range histories { if message.RepliedToMessageID == 0 { historiesLLMFriendly = append(historiesLLMFriendly, fmt.Sprintf( @@ -244,14 +249,16 @@ func (c *Model) SummarizeChatHistories(chatID int64, histories []*ent.ChatHistor } chatHistories := strings.Join(historiesLLMFriendly, "\n") - chatHistoriesSlices := c.openAI.SplitContentBasedByTokenLimitations(chatHistories, 2800) + chatHistoriesSlices := m.openAI.SplitContentBasedByTokenLimitations(chatHistories, 2800) chatHistoriesSummarizations := make([]*openai.ChatHistorySummarizationOutputs, 0, len(chatHistoriesSlices)) + for _, s := range chatHistoriesSlices { var outputs []*openai.ChatHistorySummarizationOutputs + _, _, err := lo.AttemptWithDelay(3, time.Second, func(tried int, delay time.Duration) error { - o, err := c.summarizeChatHistoriesSlice(s) + o, err := m.summarizeChatHistoriesSlice(s) if err != nil { - c.logger.Errorf("failed to summarize chat histories slice: %s, tried %d...", s, tried) + m.logger.Errorf("failed to summarize chat histories slice: %s, tried %d...", s, tried) return err } @@ -273,6 +280,7 @@ func (c *Model) SummarizeChatHistories(chatID int64, histories []*ent.ChatHistor d.CriticalMessageIDs = lo.Filter(d.CriticalMessageIDs, func(item int64, _ int) bool { return item != 0 }) + if len(d.CriticalMessageIDs) > 5 { d.CriticalMessageIDs = d.CriticalMessageIDs[:5] } @@ -283,6 +291,7 @@ func (c *Model) SummarizeChatHistories(chatID int64, histories []*ent.ChatHistor } sb := new(strings.Builder) + err := RecapOutputTemplate.Execute(sb, RecapOutputTemplateInputs{ ChatID: formatChatID(chatID), Recaps: chatHistoriesSummarizations, diff --git a/internal/models/chat_histories/chat_histories_test.go b/internal/models/chathistories/chat_histories_test.go similarity index 99% rename from internal/models/chat_histories/chat_histories_test.go rename to internal/models/chathistories/chat_histories_test.go index 506c54d..9b478f9 100644 --- a/internal/models/chat_histories/chat_histories_test.go +++ b/internal/models/chathistories/chat_histories_test.go @@ -1,4 +1,4 @@ -package chat_histories +package chathistories import ( "context" diff --git a/internal/models/models.go b/internal/models/models.go index fd3b075..a05225b 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -3,14 +3,14 @@ package models import ( "go.uber.org/fx" - "github.com/nekomeowww/insights-bot/internal/models/chat_histories" + "github.com/nekomeowww/insights-bot/internal/models/chathistories" "github.com/nekomeowww/insights-bot/internal/models/smr" "github.com/nekomeowww/insights-bot/internal/models/tgchats" ) func NewModules() fx.Option { return fx.Options( - fx.Provide(chat_histories.NewModel()), + fx.Provide(chathistories.NewModel()), fx.Provide(tgchats.NewModel()), fx.Provide(smr.NewModel()), ) diff --git a/internal/models/smr/smr.go b/internal/models/smr/smr.go index 9971d6f..66cef8a 100644 --- a/internal/models/smr/smr.go +++ b/internal/models/smr/smr.go @@ -60,7 +60,7 @@ func (u *URLSummarizationOutput) FormatSummarizationAsHTML() string { return fmt.Sprintf("%s\n%s\n\n🤖️ Generated by chatGPT", u.URL, u.Title, u.Msg) } -// FormatSummarizationAsSlackMarkdown the link syntax in slack markdown flavor is different than standard +// FormatSummarizationAsSlackMarkdown the link syntax in slack markdown flavor is different than standard. func (u *URLSummarizationOutput) FormatSummarizationAsSlackMarkdown() string { return fmt.Sprintf("*<%s|%s>*\n%s\n\n_🤖️ Generated by chatGPT_", u.URL, u.Title, u.Msg) } @@ -72,10 +72,12 @@ func (m *Model) SummarizeInputURL(url string) (*URLSummarizationOutput, error) { } textContent := m.openai.TruncateContentBasedOnTokens(article.TextContent, 3000) + m.logger.WithFields(logrus.Fields{ "title": article.Title, "url": url, }).Infof("✍️ summarizing article...") + resp, err := m.openai.SummarizeWithQuestionsAsSimplifiedChinese( context.Background(), article.Title, @@ -100,6 +102,7 @@ func (m *Model) SummarizeInputURL(url string) (*URLSummarizationOutput, error) { "completion_token_usage": resp.Usage.CompletionTokens, "total_token_usage": resp.Usage.TotalTokens, }).Infof("✅ summarizing article done") + return &URLSummarizationOutput{ URL: url, Title: article.Title, @@ -133,6 +136,7 @@ func (m *Model) extractContentFromURL(urlString string) (*readability.Article, e } buffer := new(bytes.Buffer) + _, err = io.Copy(buffer, resp.Body) if err != nil { return nil, err diff --git a/internal/models/smr/smr_test.go b/internal/models/smr/smr_test.go index 0c4dfd4..f9fedba 100644 --- a/internal/models/smr/smr_test.go +++ b/internal/models/smr/smr_test.go @@ -17,6 +17,7 @@ func TestMain(m *testing.M) { model = NewModel()(NewModelParams{ Logger: lib.NewLogger()(), }) + os.Exit(m.Run()) } diff --git a/internal/services/chat_history_recap/chat_history_recap.go b/internal/services/autorecap/autorecap.go similarity index 94% rename from internal/services/chat_history_recap/chat_history_recap.go rename to internal/services/autorecap/autorecap.go index 8572f10..ecc96b5 100644 --- a/internal/services/chat_history_recap/chat_history_recap.go +++ b/internal/services/autorecap/autorecap.go @@ -1,4 +1,4 @@ -package chat_history_recap +package autorecap import ( "context" @@ -9,7 +9,7 @@ import ( "go.uber.org/fx" "github.com/nekomeowww/insights-bot/internal/bots/telegram" - "github.com/nekomeowww/insights-bot/internal/models/chat_histories" + "github.com/nekomeowww/insights-bot/internal/models/chathistories" "github.com/nekomeowww/insights-bot/internal/models/tgchats" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" "github.com/nekomeowww/insights-bot/pkg/logger" @@ -23,7 +23,7 @@ type NewChatHistoryRecapServiceParam struct { Bot *telegram.Bot Logger *logger.Logger - ChatHistories *chat_histories.Model + ChatHistories *chathistories.Model TgChats *tgchats.Model OpenAI *openai.Client } @@ -33,7 +33,7 @@ type ChatHistoryRecapService struct { bot *telegram.Bot logger *logger.Logger - chatHistories *chat_histories.Model + chatHistories *chathistories.Model tgchats *tgchats.Model openai *openai.Client } @@ -62,6 +62,7 @@ func NewChatHistoryRecapService() func(NewChatHistoryRecapServiceParam) (*ChatHi }) service.logger.Infof("chat history recap service started") + return service, nil } } @@ -111,6 +112,7 @@ func (s *ChatHistoryRecapService) SendChatHistoriesRecap() { s.logger.Infof("sending chat histories recap for chat %d", chatID) message := tgbotapi.NewMessage(chatID, fmt.Sprintf("%s\n\n#recap #recap_auto\n🤖️ Generated by chatGPT", summarization)) message.ParseMode = "HTML" + _, err = s.bot.Send(message) if err != nil { s.logger.Errorf("failed to send chat histories recap: %v", err) diff --git a/internal/services/pprof/pprof.go b/internal/services/pprof/pprof.go new file mode 100644 index 0000000..d3b2daa --- /dev/null +++ b/internal/services/pprof/pprof.go @@ -0,0 +1,78 @@ +package pprof + +import ( + "context" + "fmt" + "net" + "net/http" + "net/http/pprof" + "time" + + "github.com/nekomeowww/insights-bot/pkg/logger" + "go.uber.org/fx" +) + +type NewPprofParams struct { + fx.In + + Lifecycle fx.Lifecycle + Logger *logger.Logger +} + +type Pprof struct { + srv *http.Server + logger *logger.Logger +} + +func NewPprof() func(NewPprofParams) *Pprof { + return func(params NewPprofParams) *Pprof { + srvMux := http.NewServeMux() + + srvMux.HandleFunc("/debug/pprof/", pprof.Index) + srvMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + srvMux.HandleFunc("/debug/pprof/profile", pprof.Profile) + srvMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + srvMux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + srv := &http.Server{ + Addr: ":6060", + Handler: srvMux, + ReadHeaderTimeout: time.Second * 15, + } + + params.Lifecycle.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + closeCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := srv.Shutdown(closeCtx); err != nil && err != http.ErrServerClosed { + return err + } + + return nil + }, + }) + + return &Pprof{ + srv: srv, + logger: params.Logger, + } + } +} + +func Run() func(*Pprof) error { + return func(srv *Pprof) error { + listener, err := net.Listen("tcp", srv.srv.Addr) + if err != nil { + return fmt.Errorf("failed to listen %s: %v", "0.0.0.0:6060", err) + } + + go func() { + if err := srv.srv.Serve(listener); err != nil && err != http.ErrServerClosed { + srv.logger.Fatalf("failed to serve pprof: %v", err) + } + }() + + return nil + } +} diff --git a/internal/services/services.go b/internal/services/services.go index c4cc9b2..e0bab1a 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -1,12 +1,14 @@ package services import ( - "github.com/nekomeowww/insights-bot/internal/services/chat_history_recap" + "github.com/nekomeowww/insights-bot/internal/services/autorecap" + "github.com/nekomeowww/insights-bot/internal/services/pprof" "go.uber.org/fx" ) func NewModules() fx.Option { return fx.Options( - fx.Provide(chat_history_recap.NewChatHistoryRecapService()), + fx.Provide(autorecap.NewChatHistoryRecapService()), + fx.Provide(pprof.NewPprof()), ) } diff --git a/pkg/bots/tgbot/context.go b/pkg/bots/tgbot/context.go index 516d008..ca347c2 100644 --- a/pkg/bots/tgbot/context.go +++ b/pkg/bots/tgbot/context.go @@ -93,6 +93,7 @@ func (c *Context) CallbackQueryDataBindQuery(dst interface{}) error { } decoder := schema.NewDecoder() + err = decoder.Decode(dst, parsedURL.Query()) if err != nil { return err diff --git a/pkg/bots/tgbot/dispatcher.go b/pkg/bots/tgbot/dispatcher.go index 17ee738..523a1b1 100644 --- a/pkg/bots/tgbot/dispatcher.go +++ b/pkg/bots/tgbot/dispatcher.go @@ -45,6 +45,7 @@ func NewDispatcher() func(param NewDispatcherParam) *Dispatcher { } d.OnCommand(d.helpCommand) + return d } } @@ -65,6 +66,7 @@ func (d *Dispatcher) OnMessage(h MessageHandler) { func (d *Dispatcher) dispatchMessage(c *Context) { identityStrings := make([]string, 0) identityStrings = append(identityStrings, FullNameFromFirstAndLastName(c.Update.Message.From.FirstName, c.Update.Message.From.LastName)) + if c.Update.Message.From.UserName != "" { identityStrings = append(identityStrings, "@"+c.Update.Message.From.UserName) } @@ -93,7 +95,6 @@ func (d *Dispatcher) dispatchMessage(c *Context) { } } }) - } else { d.dispatchInGoroutine(func() { for msg, f := range d.messageHandlers { @@ -156,6 +157,7 @@ func (d *Dispatcher) dispatchCallbackQuery(c *Context) { func (d *Dispatcher) dispatchMyChatMember(c *Context) { identityStrings := make([]string, 0) identityStrings = append(identityStrings, FullNameFromFirstAndLastName(c.Update.MyChatMember.From.FirstName, c.Update.MyChatMember.From.LastName)) + if c.Update.MyChatMember.From.UserName != "" { identityStrings = append(identityStrings, "@"+c.Update.MyChatMember.From.UserName) } @@ -172,6 +174,7 @@ func (d *Dispatcher) dispatchMyChatMember(c *Context) { MapMemberStatusToChineseText(oldMemberStatus), MapMemberStatusToChineseText(newMemberStatus), ) + switch c.Update.MyChatMember.Chat.Type { case "channel": if newMemberStatus != "administrator" { diff --git a/pkg/bots/tgbot/errors.go b/pkg/bots/tgbot/errors.go index beffbaf..54d42fa 100644 --- a/pkg/bots/tgbot/errors.go +++ b/pkg/bots/tgbot/errors.go @@ -69,6 +69,7 @@ func NewExceptionError(err error) ExceptionError { pc, file, line, _ := runtime.Caller(e.callFrameSkip) funcDetails := runtime.FuncForPC(pc) + var funcName string if funcDetails != nil { funcName = funcDetails.Name() diff --git a/pkg/bots/tgbot/handler.go b/pkg/bots/tgbot/handler.go index 10bac94..491b195 100644 --- a/pkg/bots/tgbot/handler.go +++ b/pkg/bots/tgbot/handler.go @@ -172,6 +172,7 @@ func NewHandler(h HandleFunc) Handler { } processResponse(ctx, resp) + return nil, nil } diff --git a/pkg/bots/tgbot/help_command.go b/pkg/bots/tgbot/help_command.go index 22b07bc..0325582 100644 --- a/pkg/bots/tgbot/help_command.go +++ b/pkg/bots/tgbot/help_command.go @@ -32,6 +32,7 @@ func (h helpCommandHandler) Handle(c *Context) (Response, error) { helpMessage.WriteString("我当前支持这些命令:\n") subCommandHelpMessages := make([]string, 0) + for _, c := range h.commands { subCommandHelpMessage := strings.Builder{} subCommandHelpMessage.WriteString("/") @@ -43,6 +44,7 @@ func (h helpCommandHandler) Handle(c *Context) (Response, error) { subCommandHelpMessages = append(subCommandHelpMessages, subCommandHelpMessage.String()) } + helpMessage.WriteString(strings.Join(subCommandHelpMessages, "\n")) return c.NewMessageReplyTo(helpMessage.String(), c.Update.Message.MessageID).WithParseModeHTML(), nil diff --git a/pkg/bots/tgbot/response.go b/pkg/bots/tgbot/response.go index a1a8b75..9b72e06 100644 --- a/pkg/bots/tgbot/response.go +++ b/pkg/bots/tgbot/response.go @@ -21,6 +21,7 @@ func NewMessageReplyTo(chatID int64, message string, replyToMessageID int) Messa } m.messageConfig.ReplyToMessageID = replyToMessageID + return m } diff --git a/pkg/bots/tgbot/utils.go b/pkg/bots/tgbot/utils.go index 39ba59b..f25b8b5 100644 --- a/pkg/bots/tgbot/utils.go +++ b/pkg/bots/tgbot/utils.go @@ -39,6 +39,7 @@ func EscapeStringForMarkdownV2(src string) string { // 对需要转义的字符进行转义 var lastMatchedIndex int + for i, match := range escapingIndexes { if i == 0 { result += src[lastMatchedIndex:match[0]] @@ -56,12 +57,10 @@ func EscapeStringForMarkdownV2(src string) string { return result } -// NewCallbackQueryData func NewCallbackQueryData(component string, route string, queries url.Values) string { return fmt.Sprintf("cbq://%s/%s?%s", component, route, queries.Encode()) } -// FullNameFromFirstAndLastName func FullNameFromFirstAndLastName(firstName, lastName string) string { if lastName == "" { return firstName @@ -82,7 +81,6 @@ func FullNameFromFirstAndLastName(firstName, lastName string) string { return firstName + " " + lastName } -// ExtractTextFromMessage func ExtractTextFromMessage(message *tgbotapi.Message) string { if message.Caption != "" { return message.Caption @@ -100,6 +98,7 @@ func EscapeHTMLSymbols(str string) string { str = strings.ReplaceAll(str, "<", "<") str = strings.ReplaceAll(str, ">", ">") str = strings.ReplaceAll(str, "&", "&") + return str } @@ -107,7 +106,6 @@ var ( matchMdTitles = regexp.MustCompile(`(?m)^(#){1,6} (.)*(\n)?`) ) -// ReplaceMarkdownTitlesToTelegramBoldElement func ReplaceMarkdownTitlesToTelegramBoldElement(text string) (string, error) { return matchMdTitles.ReplaceAllStringFunc(text, func(s string) string { // remove hashtag diff --git a/pkg/bots/tgbot/utils_test.go b/pkg/bots/tgbot/utils_test.go index 7cc3ab9..dc1c35e 100644 --- a/pkg/bots/tgbot/utils_test.go +++ b/pkg/bots/tgbot/utils_test.go @@ -9,6 +9,7 @@ import ( func TestReplaceMarkdownTitlesToBoldTexts(t *testing.T) { prefix := "" + for i := 0; i < 6; i++ { t.Run(fmt.Sprintf("TitleLevel%d", i+1), func(t *testing.T) { a := assert.New(t) diff --git a/pkg/logger/format.go b/pkg/logger/format.go index 5be5929..68514d0 100644 --- a/pkg/logger/format.go +++ b/pkg/logger/format.go @@ -11,13 +11,14 @@ import ( "github.com/sirupsen/logrus" ) -// LogFileFormatter defines the format for log file +// LogFileFormatter defines the format for log file. type LogFileFormatter struct { logrus.TextFormatter MinimumCallerDepth int } -// NewLogFileFormatter return the log format for log file +// NewLogFileFormatter return the log format for log file. +// // eg: 2019-01-31T04:48:20 [info] [controllers/aibf/character.go:99] foo key=value func NewLogFileFormatter() *LogFileFormatter { return &LogFileFormatter{ @@ -30,7 +31,8 @@ func NewLogFileFormatter() *LogFileFormatter { } // Format renders a single log entry for log file -// the original file log format is defined here: github.com/sirupsen/logrus/text_formatter.TextFormatter{}.Format() +// +// the original file log format is defined here: github.com/sirupsen/logrus/text_formatter.TextFormatter{}.Format(). func (f *LogFileFormatter) Format(entry *logrus.Entry) ([]byte, error) { data := make(Fields) for k, v := range entry.Data { @@ -38,10 +40,12 @@ func (f *LogFileFormatter) Format(entry *logrus.Entry) ([]byte, error) { } keys := make([]string, 0, len(data)) + for k := range data { if k == "file" { continue } + keys = append(keys, k) } @@ -66,6 +70,7 @@ func (f *LogFileFormatter) Format(entry *logrus.Entry) ([]byte, error) { } var levelColor int + switch entry.Level { case logrus.DebugLevel, logrus.TraceLevel: levelColor = 37 // gray @@ -93,26 +98,29 @@ func (f *LogFileFormatter) Format(entry *logrus.Entry) ([]byte, error) { if "" != entry.Message { b.WriteString(" " + entry.Message) } + for _, key := range keys { value := data[key] appendKeyValue(b, key, value, f.QuoteEmptyFields) } b.WriteByte('\n') + return b.Bytes(), nil } -// appendKeyValue append value with key to data that to be appended to log file +// appendKeyValue append value with key to data that to be appended to log file. func appendKeyValue(b *bytes.Buffer, key string, value interface{}, QuoteEmptyFields bool) { if b.Len() > 0 { b.WriteByte(' ') } + b.WriteString(key) b.WriteByte('=') appendValue(b, value, QuoteEmptyFields) } -// appendValue append value to data used for method appendKeyValue +// appendValue append value to data used for method appendKeyValue. func appendValue(b *bytes.Buffer, value interface{}, QuoteEmptyFields bool) { stringVal, ok := value.(string) if !ok { @@ -126,11 +134,12 @@ func appendValue(b *bytes.Buffer, value interface{}, QuoteEmptyFields bool) { } } -// needsQuoting check where text needs to be quoted +// needsQuoting check where text needs to be quoted. func needsQuoting(text string, QuoteEmptyFields bool) bool { if QuoteEmptyFields && len(text) == 0 { return true } + for _, ch := range text { if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || @@ -139,5 +148,6 @@ func needsQuoting(text string, QuoteEmptyFields bool) bool { return true } } + return false } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index d45834d..4408f4c 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -20,70 +20,70 @@ type Logger struct { namespace string } -// Debug 打印 debug 级别日志 +// Debug 打印 debug 级别日志。 func (l *Logger) Debug(args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Debug(args...) } -// Debugf 格式化字符串后打印 debug 级别日志 +// Debugf 格式化字符串后打印 debug 级别日志。 func (l *Logger) Debugf(format string, args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Debugf(format, args...) } -// Info 打印 info 级别日志 +// Info 打印 info 级别日志。 func (l *Logger) Info(args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Info(args...) } -// Infof 格式化字符串后打印 info 级别日志 +// Infof 格式化字符串后打印 info 级别日志。 func (l *Logger) Infof(format string, args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Infof(format, args...) } -// Warn 打印 warn 级别日志 +// Warn 打印 warn 级别日志。 func (l *Logger) Warn(args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Warn(args...) } -// Warnf 格式化字符串后打印 warn 级别日志 +// Warnf 格式化字符串后打印 warn 级别日志。 func (l *Logger) Warnf(format string, args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Warnf(format, args...) } -// Error 打印错误日志 +// Error 打印错误日志。 func (l *Logger) Error(args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Error(args...) } -// Errorf 格式化字符串后打印错误日志 +// Errorf 格式化字符串后打印错误日志。 func (l *Logger) Errorf(format string, args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Errorf(format, args...) } -// Fatal 打印致命错误日志,打印后立即退出程序 +// Fatal 打印致命错误日志,打印后立即退出程序。 func (l *Logger) Fatal(args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) entry.Fatal(args...) } -// Fatalf 格式化字符串后打印致命错误日志,打印后立即退出程序 +// Fatalf 格式化字符串后打印致命错误日志,打印后立即退出程序。 func (l *Logger) Fatalf(format string, args ...interface{}) { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) @@ -99,6 +99,7 @@ type Fields logrus.Fields func (l *Logger) WithField(key string, value interface{}) *logrus.Entry { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) + return entry.WithField(key, value) } @@ -107,15 +108,17 @@ func (l *Logger) WithField(key string, value interface{}) *logrus.Entry { func (l *Logger) WithFields(fields logrus.Fields) *logrus.Entry { entry := logrus.NewEntry(l.Logger) SetCallFrame(entry, l.namespace, 1) + return entry.WithFields(fields) } -// SetCallFrame 设定调用栈 +// SetCallFrame 设定调用栈。 func SetCallFrame(entry *logrus.Entry, namespace string, skip int) { // 获取调用栈的 文件、行号 _, file, line, _ := runtime.Caller(skip + 1) pc, _, _, _ := runtime.Caller(skip + 2) funcDetail := runtime.FuncForPC(pc) + var funcName string if funcDetail != nil { funcName = funcDetail.Name() @@ -130,7 +133,7 @@ const ( runtimeCaller contextKey = "ContextKeyRuntimeCaller" ) -// SetCallerFrameWithFileAndLine 设定调用栈 +// SetCallerFrameWithFileAndLine 设定调用栈。 func SetCallerFrameWithFileAndLine(entry *logrus.Entry, namespace, functionName, file string, line int) { splitTarget := filepath.FromSlash("/" + namespace + "/") // 拆解文件名,移除项目所在路径和项目名称,只保留到项目内的文件路径 @@ -148,7 +151,7 @@ func SetCallerFrameWithFileAndLine(entry *logrus.Entry, namespace, functionName, }) } -// NewLogger 按需创建 logger 实例 +// NewLogger 按需创建 logger 实例。 func NewLogger(level logrus.Level, namespace string, logFilePath string, hook []logrus.Hook) *Logger { // 创建 logrus 实例 log := logrus.New() @@ -175,7 +178,7 @@ func NewLogger(level logrus.Level, namespace string, logFilePath string, hook [] return &Logger{Logger: log, namespace: namespace} } -// initLoggerFile 初始化日志文件 +// initLoggerFile 初始化日志文件。 func initLoggerFile(logger *logrus.Logger, logPath string) error { execPath, _ := os.Executable() // 获取日志文件目录 @@ -198,11 +201,6 @@ func initLoggerFile(logger *logrus.Logger, logPath string) error { if err2 != nil { return fmt.Errorf("failed to create %s log file: %w", logPath, err) } - // 检查是否创建完毕 - _, err2 = os.Stat(logPath) - if err2 != nil { - return fmt.Errorf("failed to check %s log file: %w", logPath, err) - } } else { // 否则返回错误 return err @@ -222,5 +220,6 @@ func initLoggerFile(logger *logrus.Logger, logPath string) error { // 设定多重输出流:一个是标准输出,一个是文件写入输出 mw := io.MultiWriter(os.Stdout, logFile) logger.SetOutput(mw) + return nil } diff --git a/pkg/openai/openai.go b/pkg/openai/openai.go index 914f4ab..f861b62 100644 --- a/pkg/openai/openai.go +++ b/pkg/openai/openai.go @@ -51,13 +51,14 @@ func NewClient(apiSecret string, apiHost string) (*Client, error) { } client := openai.NewClientWithConfig(config) + return &Client{ OpenAIClient: client, tiktokenEncoding: tokenizer, }, nil } -// truncateContentBasedOnTokens 基于 token 计算的方式截断文本 +// truncateContentBasedOnTokens 基于 token 计算的方式截断文本。 func (c *Client) TruncateContentBasedOnTokens(textContent string, limits int) string { tokens := c.tiktokenEncoding.Encode(textContent, nil, nil) if len(tokens) <= limits { @@ -67,35 +68,37 @@ func (c *Client) TruncateContentBasedOnTokens(textContent string, limits int) st truncated := c.tiktokenEncoding.Decode(tokens[:limits]) for len(truncated) > 0 { - // 假设 textContent = "小溪河水清澈见底", Encode 结果为 "[31809,36117,103,31106,111,53610,80866,162,122,230,90070,11795,243]" // 当 limits = 4, 那么 tokens[:limits] = "[31809,36117,103,31106]", Decode 结果为 "小溪\xe6\xb2" // 这里的 \xe6\xb2 是一个不完整的 UTF-8 编码,无法正确解析为一个完整的字符。下面得代码处理这种情况把它去掉。 - r, size := utf8.DecodeLastRuneInString(truncated) if r != utf8.RuneError { break } truncated = truncated[:len(truncated)-size] } + return truncated } -// SplitContentBasedByTokenLimitations 基于 token 计算的方式分割文本 +// SplitContentBasedByTokenLimitations 基于 token 计算的方式分割文本。 func (c *Client) SplitContentBasedByTokenLimitations(textContent string, limits int) []string { slices := make([]string, 0) + for { s := c.TruncateContentBasedOnTokens(textContent, limits) slices = append(slices, s) textContent = textContent[len(s):] + if textContent == "" { break } } + return slices } -// SummarizeWithQuestionsAsSimplifiedChinese 通过 OpenAI 的 Chat API 来为文章生成摘要和联想问题 +// SummarizeWithQuestionsAsSimplifiedChinese 通过 OpenAI 的 Chat API 来为文章生成摘要和联想问题。 func (c *Client) SummarizeWithQuestionsAsSimplifiedChinese(ctx context.Context, title, by, content string) (*openai.ChatCompletionResponse, error) { resp, err := c.OpenAIClient.CreateChatCompletion( ctx, @@ -167,9 +170,8 @@ func (c *Client) SummarizeWithOneChatHistory(ctx context.Context, llmFriendlyCha func (c *Client) SummarizeWithChatHistories(ctx context.Context, llmFriendlyChatHistories string) (*openai.ChatCompletionResponse, error) { sb := new(strings.Builder) - err := ChatHistorySummarizationPrompt.Execute(sb, ChatHistorySummarizationPromptInputs{ - ChatHistory: llmFriendlyChatHistories, - }) + + err := ChatHistorySummarizationPrompt.Execute(sb, ChatHistorySummarizationPromptInputs{ChatHistory: llmFriendlyChatHistories}) if err != nil { return nil, err } diff --git a/pkg/openai/openai_test.go b/pkg/openai/openai_test.go index 2927fcd..827c4be 100644 --- a/pkg/openai/openai_test.go +++ b/pkg/openai/openai_test.go @@ -8,10 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewClient(t *testing.T) { - _, _ = NewClient("", "https://openai.example.com") -} - func TestTruncateContentBasedOnTokens(t *testing.T) { tables := []struct { textContent string @@ -37,6 +33,7 @@ func TestTruncateContentBasedOnTokens(t *testing.T) { c, err := NewClient("", "") require.NoError(t, err) + for _, table := range tables { t.Run(table.textContent, func(t *testing.T) { actual := c.TruncateContentBasedOnTokens(table.textContent, table.limits) @@ -65,6 +62,7 @@ func TestSplitContentBasedOnTokenLimitations(t *testing.T) { c, err := NewClient("", "") require.NoError(t, err) + for i, table := range tables { t.Run(strconv.Itoa(i), func(t *testing.T) { actual := c.SplitContentBasedByTokenLimitations(table.textContent, table.limits) diff --git a/pkg/utils/concurrent.go b/pkg/utils/concurrent.go index 0861ffb..2f85510 100644 --- a/pkg/utils/concurrent.go +++ b/pkg/utils/concurrent.go @@ -8,7 +8,7 @@ import ( ) type InvokeOptions struct { - ctx context.Context + ctx context.Context //nolint:containedctx } func WithContext(ctx context.Context) options.CallOptions[InvokeOptions] { @@ -20,8 +20,8 @@ func WithContext(ctx context.Context) options.CallOptions[InvokeOptions] { func Invoke0(funcToBeRan func() error, callOpts ...options.CallOptions[InvokeOptions]) error { opts := options.ApplyCallOptions(callOpts, InvokeOptions{ctx: context.Background()}) - resChan := make(chan struct{}, 1) var err error + resChan := make(chan struct{}, 1) go func() { err = funcToBeRan() @@ -29,7 +29,9 @@ func Invoke0(funcToBeRan func() error, callOpts ...options.CallOptions[InvokeOpt }() var wg sync.WaitGroup + wg.Add(1) + go func() { select { case <-opts.ctx.Done(): @@ -39,7 +41,7 @@ func Invoke0(funcToBeRan func() error, callOpts ...options.CallOptions[InvokeOpt wg.Done() }() - wg.Wait() + return err } diff --git a/pkg/utils/debug.go b/pkg/utils/debug.go index 73ad89e..d449c41 100644 --- a/pkg/utils/debug.go +++ b/pkg/utils/debug.go @@ -8,29 +8,33 @@ import ( "github.com/davecgh/go-spew/spew" ) -// Print 格式化输出所有传入的值的字段、值、类型、大小 -func Print(any ...interface{}) { - fmt.Println(Sprint(any)) +// Print 格式化输出所有传入的值的字段、值、类型、大小。 +func Print(inputs ...interface{}) { + fmt.Println(Sprint(inputs)) } -// Sprint 格式化输出所有传入的值的字段、值、类型、大小,并返回字符串 -// NOTICE: 包含换行符 -func Sprint(any ...interface{}) string { - return spew.Sdump(any) +// Sprint 格式化输出所有传入的值的字段、值、类型、大小,并返回字符串。 +// +// NOTICE: 包含换行符。 +func Sprint(inputs ...interface{}) string { + return spew.Sdump(inputs) } -// PrintJSON 格式化输出 JSON 格式 -func PrintJSON(any ...interface{}) { - fmt.Println(SprintJSON(any)) +// PrintJSON 格式化输出 JSON 格式。 +func PrintJSON(inputs ...interface{}) { + fmt.Println(SprintJSON(inputs)) } -// SprintJSON 格式化输出 JSON 格式并返回字符串 -// NOTICE: 包含换行符 -func SprintJSON(any ...interface{}) string { +// SprintJSON 格式化输出 JSON 格式并返回字符串。 +// +// NOTICE: 包含换行符。 +func SprintJSON(inputs ...interface{}) string { strSlice := make([]string, 0) - for _, v := range any { + + for _, v := range inputs { b, _ := json.MarshalIndent(v, "", " ") strSlice = append(strSlice, string(b)) } + return strings.Join(strSlice, "\n") } diff --git a/pkg/utils/debug_test.go b/pkg/utils/debug_test.go index b7e159b..b139df7 100644 --- a/pkg/utils/debug_test.go +++ b/pkg/utils/debug_test.go @@ -11,6 +11,7 @@ func TestPrintAndPrintJSON(t *testing.T) { C int D []string } + type testStruct struct { A int B string diff --git a/pkg/utils/file.go b/pkg/utils/file.go index 730b909..5122df2 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -14,6 +14,7 @@ func ReadFileAsBytesBuffer(path string) (*bytes.Buffer, error) { defer file.Close() buf := new(bytes.Buffer) + _, err = io.Copy(buf, file) if err != nil { return nil, err diff --git a/pkg/utils/path.go b/pkg/utils/path.go index e26002f..4bd4e31 100644 --- a/pkg/utils/path.go +++ b/pkg/utils/path.go @@ -6,13 +6,15 @@ import ( "runtime" ) -// RelativePathOf 获取基于调用函数的调用对象相对位置的相对路径 +// RelativePathOf 获取基于调用函数的调用对象相对位置的相对路径。 func RelativePathOf(fp string) string { _, file, _, ok := runtime.Caller(1) if !ok { return "" } + callerDir := filepath.Dir(filepath.FromSlash(file)) + return filepath.FromSlash(filepath.Join(callerDir, fp)) } diff --git a/pkg/utils/random.go b/pkg/utils/random.go index 28ab053..c7ca291 100644 --- a/pkg/utils/random.go +++ b/pkg/utils/random.go @@ -9,39 +9,47 @@ import ( "strings" ) -// RandBytes 根据给定的长度生成字节,长度默认为 32 +const ( + defaultRandBytesLength = 32 + + defaultRandomHashStringRandomBytesLength = 64 + maxRandomHashStringLength = 64 +) + +// RandBytes 根据给定的长度生成字节,长度默认为 32。 func RandBytes(length ...int) ([]byte, error) { - b := make([]byte, 32) + b := make([]byte, defaultRandBytesLength) if len(length) != 0 { b = make([]byte, length[0]) } + _, err := rand.Read(b) if err != nil { return nil, err } + return b, nil } // RandomBase64Token 根据给定的字节长度生成 URL 安全的 Base64 字符串,长度默认为 32 -// 长度为原始字节数据的长度,并非 Base64 字符串实际长度,默认 32 情况下实际长度约为 44 +// 长度为原始字节数据的长度,并非 Base64 字符串实际长度,默认 32 情况下实际长度约为 44。 func RandomBase64Token(length ...int) (string, error) { b, err := RandBytes(length...) if err != nil { return "", err } + return base64.URLEncoding.EncodeToString(b), nil } -// RandomHashString 生成随机 SHA256 字符串,最大长度为 64 +// RandomHashString 生成随机 SHA256 字符串,最大长度为 64。 func RandomHashString(length ...int) string { - b, _ := RandBytes(1024) + b, _ := RandBytes(defaultRandomHashStringRandomBytesLength) + if len(length) != 0 { sliceLength := length[0] - if length[0] > 64 { - sliceLength = 64 - } - if length[0] <= 0 { - sliceLength = 64 + if sliceLength > maxRandomHashStringLength || sliceLength <= 0 { + sliceLength = maxRandomHashStringLength } return fmt.Sprintf("%x", sha256.Sum256(b))[:sliceLength] @@ -50,7 +58,7 @@ func RandomHashString(length ...int) string { return fmt.Sprintf("%x", sha256.Sum256(b)) } -// RandomInt64 生成随机整数 +// RandomInt64 生成随机整数。 func RandomInt64(max ...int64) int64 { innerMax := int64(0) if len(max) == 0 || (len(max) > 0 && max[0] <= 0) { @@ -61,30 +69,34 @@ func RandomInt64(max ...int64) int64 { nBig, _ := rand.Int(rand.Reader, big.NewInt(innerMax)) n := nBig.Int64() + return n } -// RandomInt64InRange 在区间内生成随机整数 +// RandomInt64InRange 在区间内生成随机整数。 func RandomInt64InRange(min, max int64) int64 { - if min >= max { - panic("min must be less than max") - } if max <= 0 { panic("max must be greater than 0") } + if min >= max { + panic("min must be less than max") + } nBig, _ := rand.Int(rand.Reader, big.NewInt(max-min)) n := nBig.Int64() + return n + min } -// RandomInt64String 在区间内生成随机整数 +// RandomInt64String 在区间内生成随机整数。 func RandomInt64String(digits int64) string { max := big.NewInt(0) min := big.NewInt(0) + max.SetString(strings.Repeat("9", int(digits)), 10) min.SetString(fmt.Sprintf("%s%s", "1", strings.Repeat("0", int(digits)-1)), 10) nBig, _ := rand.Int(rand.Reader, new(big.Int).Sub(max, min)) + return new(big.Int).Add(nBig, min).String() } diff --git a/pkg/utils/random_test.go b/pkg/utils/random_test.go index a736098..ee219f7 100644 --- a/pkg/utils/random_test.go +++ b/pkg/utils/random_test.go @@ -8,8 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestRandBytes 测试随机字节串工具函数 -func TestRanddomBytes(t *testing.T) { +func TestRandomBytes(t *testing.T) { t.Run("no args", func(t *testing.T) { assert := assert.New(t) @@ -33,8 +32,7 @@ func TestRanddomBytes(t *testing.T) { }) } -// TestRandBase64 测试随机 Base64 字符串工具函数 -func TestRanddomBase64Token(t *testing.T) { +func TestRandomBase64Token(t *testing.T) { t.Run("no args", func(t *testing.T) { assert := assert.New(t) diff --git a/pkg/utils/units.go b/pkg/utils/units.go index 1ded7b5..c4f172a 100644 --- a/pkg/utils/units.go +++ b/pkg/utils/units.go @@ -1,11 +1,11 @@ package utils -// Bytes Units +// Bytes Units. const ( - // KB = 1024 + // KB = 1024. BytesUnitKB = 1024 - // MB = 1024 * KB + // MB = 1024 * KB. BytesUnitMB = 1024 * BytesUnitKB - // GB = 1024 * MB + // GB = 1024 * MB. BytesUnitGB = 1024 * BytesUnitMB )