mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 05:56:38 +08:00
代码质量优化
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
version: "2"
|
||||
|
||||
run:
|
||||
timeout: 5m
|
||||
tests: false
|
||||
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
# 基础检查
|
||||
- govet
|
||||
- staticcheck
|
||||
- errcheck
|
||||
- ineffassign
|
||||
- unused
|
||||
|
||||
# 代码坏味道
|
||||
- dupl # 重复代码
|
||||
- mnd # 魔法数字
|
||||
- gocyclo # 圈复杂度
|
||||
- cyclop # 包/函数复杂度
|
||||
- nestif # if 嵌套太深
|
||||
- maintidx # 维护性指数
|
||||
- revive # 风格/命名/坏味道
|
||||
- gocritic # 各类代码问题
|
||||
|
||||
settings:
|
||||
dupl:
|
||||
threshold: 150
|
||||
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
|
||||
cyclop:
|
||||
max-complexity: 15
|
||||
package-average: 10
|
||||
|
||||
funlen:
|
||||
lines: 150
|
||||
statements: 80
|
||||
|
||||
nestif:
|
||||
min-complexity: 5
|
||||
|
||||
mnd:
|
||||
checks:
|
||||
- argument
|
||||
- case
|
||||
- condition
|
||||
- operation
|
||||
- return
|
||||
- assign
|
||||
@@ -473,21 +473,24 @@ export function FeatureMain() {
|
||||
|
||||
## 五、代码质量与审查规范
|
||||
|
||||
### 5.1 统一代码检查与提交
|
||||
### 5.1 Make 指令
|
||||
|
||||
| 指令 | 触发时机 | 说明 |
|
||||
|------|----------|------|
|
||||
| `make code-check` | **提交前必须执行** | 前端 TypeScript 类型检查 + ESLint 静态分析;后端 `golangci-lint` 代码规范扫描 |
|
||||
| `make build-test` | 功能完成后验证 | 前后端**并行**完整编译测试(`pnpm build` + `go build`),快速发现编译错误 |
|
||||
| `make swagger` | 新增/修改 API 后 | 自动生成/更新 Swagger 文档(`docs/swagger.json`) |
|
||||
| `make build-embedded` | 发布前 | 前端静态导出嵌入后端,生成单二进制产物 `bin/wavelet` |
|
||||
| `make license` | 新增 Go 文件后 | 自动为所有 Go 源文件添加/更新 License Header |
|
||||
| `make license-check` | CI 流水线 | 校验所有 Go 文件的 License Header 是否合规 |
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **代码开发完成后,提交前必须在项目根目录运行 `make code-check` 进行本地代码质量与风格检查,确保前端 ESLint 没有报错和警告(`--max-warnings 0`)。**
|
||||
> **代码开发完成后,提交前必须运行 `make code-check`,所有检查全部通过后方可提交。**
|
||||
|
||||
```bash
|
||||
# 执行根目录的检查指令
|
||||
make code-check
|
||||
```
|
||||
---
|
||||
|
||||
### 5.2 后端规范检查
|
||||
|
||||
**基础检查**:
|
||||
- 后端 Go 代码需要通过 CodeQL 扫描。较复杂的逻辑建议结合 Copilot 检查。
|
||||
|
||||
**API 文档**:
|
||||
- 所有 HTTP 接口都必须编写完整的 Swagger 注释。提交前需运行 `make swagger` 自动生成与更新接口文档。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: swagger license license-check build-embedded
|
||||
.PHONY: swagger license license-check build-embedded build-test
|
||||
|
||||
swagger:
|
||||
scripts/swagger.sh
|
||||
@@ -16,4 +16,21 @@ build-embedded:
|
||||
go build -tags embed_frontend -o bin/wavelet main.go
|
||||
|
||||
code-check:
|
||||
cd frontend && npx eslint . --max-warnings 0
|
||||
cd frontend && pnpm tsc --noEmit --jsx preserve && npx eslint . --max-warnings 0
|
||||
golangci-lint run
|
||||
|
||||
build-test:
|
||||
@echo "==> Running frontend and backend build tests in parallel..."
|
||||
@PIDS=""; \
|
||||
STATUS=0; \
|
||||
( cd frontend && pnpm build 2>&1 | sed 's/^/[frontend] /' ) & PIDS="$$PIDS $$!"; \
|
||||
( go build -o /dev/null ./... 2>&1 | sed 's/^/[backend] /' ) & PIDS="$$PIDS $$!"; \
|
||||
for PID in $$PIDS; do \
|
||||
wait $$PID || STATUS=1; \
|
||||
done; \
|
||||
if [ $$STATUS -eq 0 ]; then \
|
||||
echo "==> All build tests passed."; \
|
||||
else \
|
||||
echo "==> Build test FAILED." >&2; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
} from "@/components/ui/dialog"
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/ui/select"
|
||||
import {Label} from "@/components/ui/label"
|
||||
import {AuthService} from "@/lib/services"
|
||||
import {AuthService, ChangePasswordRequest, UpdateProfileRequest} from "@/lib/services"
|
||||
import {UploadService} from "@/lib/services/upload/upload.service"
|
||||
import {ImageCrop, ImageCropApply, ImageCropContent, ImageCropReset} from "@/components/ui/image-crop"
|
||||
import {toast} from "sonner"
|
||||
@@ -92,7 +92,7 @@ export function ProfileMain() {
|
||||
}, [user, isEditDialogOpen])
|
||||
|
||||
const changePasswordMutation = useMutation({
|
||||
mutationFn: AuthService.changePassword,
|
||||
mutationFn: (req: ChangePasswordRequest) => AuthService.changePassword(req),
|
||||
onSuccess: () => {
|
||||
toast.success("密码修改成功")
|
||||
setOldPassword("")
|
||||
@@ -122,7 +122,7 @@ export function ProfileMain() {
|
||||
}
|
||||
|
||||
const updateProfileMutation = useMutation({
|
||||
mutationFn: AuthService.updateProfile,
|
||||
mutationFn: (req: UpdateProfileRequest) => AuthService.updateProfile(req),
|
||||
onSuccess: () => {
|
||||
toast.success("个人信息修改成功")
|
||||
setIsEditDialogOpen(false)
|
||||
|
||||
@@ -81,11 +81,11 @@ func TestListAuthSources(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var sources []model.AuthSource
|
||||
json.Unmarshal(dataBytes, &sources)
|
||||
_ = json.Unmarshal(dataBytes, &sources)
|
||||
|
||||
if len(sources) != 1 {
|
||||
t.Errorf("expected 1 auth source, got %d", len(sources))
|
||||
|
||||
@@ -103,7 +103,7 @@ func HandleLogWebSocket(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
// 订阅 ring buffer
|
||||
ch := logger.GlobalRingBuffer.Subscribe()
|
||||
@@ -296,7 +296,7 @@ func GetAccessLogs(c *gin.Context) {
|
||||
c.JSON(http.StatusInternalServerError, util.Err("查询 ClickHouse 日志明细失败: "+err.Error()))
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var list []accessLogItem
|
||||
var fetchUserIDs []uint64
|
||||
@@ -424,7 +424,7 @@ func GetLogsAnalytics(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
defer trendRows.Close()
|
||||
defer func() { _ = trendRows.Close() }()
|
||||
for trendRows.Next() {
|
||||
var dt time.Time
|
||||
var cnt uint64
|
||||
@@ -454,7 +454,7 @@ func GetLogsAnalytics(c *gin.Context) {
|
||||
|
||||
browserCounts := make(map[string]uint64)
|
||||
if err == nil {
|
||||
defer uaRows.Close()
|
||||
defer func() { _ = uaRows.Close() }()
|
||||
for uaRows.Next() {
|
||||
var ua string
|
||||
var cnt uint64
|
||||
@@ -493,7 +493,7 @@ func GetLogsAnalytics(c *gin.Context) {
|
||||
userCountMap := make(map[uint64]uint64)
|
||||
|
||||
if err == nil {
|
||||
defer userRows.Close()
|
||||
defer func() { _ = userRows.Close() }()
|
||||
for userRows.Next() {
|
||||
var uid uint64
|
||||
var cnt uint64
|
||||
|
||||
@@ -206,7 +206,7 @@ func UpdateSystemConfig(c *gin.Context) {
|
||||
updates := map[string]interface{}{
|
||||
"description": req.Description,
|
||||
}
|
||||
if !(key == model.ConfigKeySMTPPassword && req.Value == "******") {
|
||||
if key != model.ConfigKeySMTPPassword || req.Value != "******" {
|
||||
updates["value"] = req.Value
|
||||
config.Value = req.Value
|
||||
}
|
||||
|
||||
@@ -140,11 +140,11 @@ func TestListSystemConfigs(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var configs []model.SystemConfig
|
||||
json.Unmarshal(dataBytes, &configs)
|
||||
_ = json.Unmarshal(dataBytes, &configs)
|
||||
|
||||
// Defaults seed 23 configurations
|
||||
if len(configs) != 23 {
|
||||
@@ -158,11 +158,11 @@ func TestListSystemConfigs(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var configs []model.SystemConfig
|
||||
json.Unmarshal(dataBytes, &configs)
|
||||
_ = json.Unmarshal(dataBytes, &configs)
|
||||
|
||||
if len(configs) != 1 || configs[0].Key != model.ConfigKeyMaxAPIKeysPerUser {
|
||||
t.Errorf("expected 1 business config (max_api_keys_per_user), got %d: %v", len(configs), configs)
|
||||
@@ -187,11 +187,11 @@ func TestGetSystemConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var cfg model.SystemConfig
|
||||
json.Unmarshal(dataBytes, &cfg)
|
||||
_ = json.Unmarshal(dataBytes, &cfg)
|
||||
|
||||
if cfg.Value != "Wavelet" {
|
||||
t.Errorf("expected 'Wavelet', got '%s'", cfg.Value)
|
||||
@@ -240,7 +240,7 @@ func TestUpdateSystemConfig(t *testing.T) {
|
||||
|
||||
// Verify Redis
|
||||
var redisConfig model.SystemConfig
|
||||
db.HGetJSON(context.Background(), model.SystemConfigRedisHashKey, model.ConfigKeySiteName, &redisConfig)
|
||||
_ = db.HGetJSON(context.Background(), model.SystemConfigRedisHashKey, model.ConfigKeySiteName, &redisConfig)
|
||||
if redisConfig.Value != "Super Site Name" {
|
||||
t.Errorf("redis cache value not updated, got '%s'", redisConfig.Value)
|
||||
}
|
||||
@@ -275,7 +275,7 @@ func TestTestSMTP(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start mock smtp server: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
defer func() { _ = l.Close() }()
|
||||
|
||||
port := l.Addr().(*net.TCPAddr).Port
|
||||
|
||||
@@ -284,40 +284,40 @@ func TestTestSMTP(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
writer := bufio.NewWriter(conn)
|
||||
reader := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
// 220 Ready
|
||||
writer.WriteString("220 mock.smtp.com SMTP Ready\r\n")
|
||||
writer.Flush()
|
||||
_, _ = writer.WriteString("220 mock.smtp.com SMTP Ready\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read HELO/EHLO
|
||||
tp.ReadLine()
|
||||
writer.WriteString("250-mock.smtp.com\r\n250 AUTH PLAIN\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("250-mock.smtp.com\r\n250 AUTH PLAIN\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read AUTH PLAIN
|
||||
tp.ReadLine()
|
||||
writer.WriteString("235 Authentication successful\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("235 Authentication successful\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read MAIL FROM
|
||||
tp.ReadLine()
|
||||
writer.WriteString("250 OK\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("250 OK\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read RCPT TO
|
||||
tp.ReadLine()
|
||||
writer.WriteString("250 OK\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("250 OK\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read DATA
|
||||
tp.ReadLine()
|
||||
writer.WriteString("354 Start mail input\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("354 Start mail input\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read body lines until dot
|
||||
for {
|
||||
@@ -326,13 +326,13 @@ func TestTestSMTP(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
writer.WriteString("250 OK\r\n")
|
||||
writer.Flush()
|
||||
_, _ = writer.WriteString("250 OK\r\n")
|
||||
_ = writer.Flush()
|
||||
|
||||
// Read QUIT
|
||||
tp.ReadLine()
|
||||
writer.WriteString("221 Bye\r\n")
|
||||
writer.Flush()
|
||||
_, _ = tp.ReadLine()
|
||||
_, _ = writer.WriteString("221 Bye\r\n")
|
||||
_ = writer.Flush()
|
||||
}()
|
||||
|
||||
payload := TestSMTPRequest{
|
||||
|
||||
@@ -45,7 +45,7 @@ func setupTaskTestEnvironment(t *testing.T) func() {
|
||||
})
|
||||
return func() {
|
||||
if task.AsynqClient != nil {
|
||||
task.AsynqClient.Close()
|
||||
_ = task.AsynqClient.Close()
|
||||
task.AsynqClient = nil
|
||||
}
|
||||
cleanup()
|
||||
@@ -89,11 +89,11 @@ func TestListTaskTypes(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var taskMetas []task.TaskMeta
|
||||
json.Unmarshal(dataBytes, &taskMetas)
|
||||
_ = json.Unmarshal(dataBytes, &taskMetas)
|
||||
|
||||
if len(taskMetas) == 0 {
|
||||
t.Error("expected at least one dispatchable task type")
|
||||
@@ -131,7 +131,7 @@ func TestDispatchTask(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, w.Code, "Body: %s", w.Body.String())
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.Empty(t, resp.ErrorMsg)
|
||||
assert.NotNil(t, resp.Data)
|
||||
|
||||
|
||||
@@ -136,11 +136,11 @@ func TestListTemplates(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var templates []model.Template
|
||||
json.Unmarshal(dataBytes, &templates)
|
||||
_ = json.Unmarshal(dataBytes, &templates)
|
||||
|
||||
if len(templates) != 2 {
|
||||
t.Errorf("expected 2 templates, got %d", len(templates))
|
||||
|
||||
@@ -131,11 +131,11 @@ func TestListUsers(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var listResp listUsersResponse
|
||||
json.Unmarshal(dataBytes, &listResp)
|
||||
_ = json.Unmarshal(dataBytes, &listResp)
|
||||
|
||||
if len(listResp.Users) != 1 || listResp.Users[0].ID != 1001 {
|
||||
t.Errorf("expected 1 user with ID 1001, got total %d", len(listResp.Users))
|
||||
@@ -148,11 +148,11 @@ func TestListUsers(t *testing.T) {
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
dataBytes, _ := json.Marshal(resp.Data)
|
||||
var listResp listUsersResponse
|
||||
json.Unmarshal(dataBytes, &listResp)
|
||||
_ = json.Unmarshal(dataBytes, &listResp)
|
||||
|
||||
if len(listResp.Users) != 1 || listResp.Users[0].Username != "bob" {
|
||||
t.Errorf("expected bob, got %v", listResp.Users)
|
||||
@@ -226,7 +226,7 @@ func TestUpdateUserStatus(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg != cannotDisable {
|
||||
t.Errorf("expected error message '%s', got '%s'", cannotDisable, resp.ErrorMsg)
|
||||
}
|
||||
@@ -326,7 +326,7 @@ func TestCreateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp util.ResponseAny
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg != usernameExists {
|
||||
t.Errorf("expected error '%s', got '%s'", usernameExists, resp.ErrorMsg)
|
||||
}
|
||||
|
||||
@@ -214,15 +214,6 @@ func oidcDiscoveryResponse() *http.Response {
|
||||
}
|
||||
}
|
||||
|
||||
func jwksResponse() *http.Response {
|
||||
jwksJSON, _ := json.Marshal(testJWKS)
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader(jwksJSON)),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
|
||||
type mockClaims struct {
|
||||
ID uint64 `json:"id"`
|
||||
Issuer string `json:"iss"`
|
||||
@@ -822,7 +813,7 @@ func TestCallbackBind(t *testing.T) {
|
||||
router.GET("/test-helper/login-777", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Set(UserIDKey, uint64(777))
|
||||
session.Save()
|
||||
_ = session.Save()
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
@@ -874,7 +865,7 @@ func TestCallbackBind(t *testing.T) {
|
||||
router.GET("/test-helper/login-888", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Set(UserIDKey, uint64(888))
|
||||
session.Save()
|
||||
_ = session.Save()
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
@@ -937,7 +928,7 @@ func TestExternalAccountsListAndDelete(t *testing.T) {
|
||||
router.GET("/test-helper/login-555", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Set(UserIDKey, uint64(555))
|
||||
session.Save()
|
||||
_ = session.Save()
|
||||
c.String(200, "ok")
|
||||
})
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ func ServeFileByID(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Stream from CDN/S3
|
||||
defer obj.Body.Close()
|
||||
defer func() { _ = obj.Body.Close() }()
|
||||
|
||||
// Respond with the file content
|
||||
c.DataFromReader(http.StatusOK, obj.ContentLength, obj.ContentType, obj.Body, nil)
|
||||
|
||||
@@ -88,7 +88,7 @@ func UploadFile(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, util.Err(ErrOpenFileFailed))
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
// 校验大小
|
||||
if header.Size > maxUploadSize {
|
||||
@@ -318,7 +318,7 @@ func DownloadFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
defer obj.Body.Close()
|
||||
defer func() { _ = obj.Body.Close() }()
|
||||
_, _ = io.Copy(c.Writer, obj.Body)
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ func BatchDownloadFiles(c *gin.Context) {
|
||||
|
||||
// 开启实时 ZIP 压缩器并直接输出给 Response Writer
|
||||
zipWriter := zip.NewWriter(c.Writer)
|
||||
defer zipWriter.Close()
|
||||
defer func() { _ = zipWriter.Close() }()
|
||||
|
||||
// 用于解决 ZIP 内部文件名称发生碰撞冲突的问题
|
||||
usedNames := make(map[string]int)
|
||||
|
||||
@@ -95,7 +95,7 @@ func createMultipartRequest(t *testing.T, fieldName, fileName string, fileConten
|
||||
func TestUploadFile(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer os.RemoveAll("uploads") // Clean up local files created during tests
|
||||
defer func() { _ = os.RemoveAll("uploads") }() // Clean up local files created during tests
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
@@ -211,7 +211,7 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp testResponse
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
if resp.ErrorMsg == "" || !strings.Contains(resp.ErrorMsg, ErrUnsupportedFormat) {
|
||||
t.Errorf("expected unsupported format error, got: %v", resp)
|
||||
}
|
||||
@@ -247,7 +247,7 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp2 testResponse
|
||||
json.Unmarshal(w2.Body.Bytes(), &resp2)
|
||||
_ = json.Unmarshal(w2.Body.Bytes(), &resp2)
|
||||
|
||||
if resp2.ErrorMsg != "" {
|
||||
t.Fatalf("second upload was unsuccessful: %s", resp2.ErrorMsg)
|
||||
@@ -304,7 +304,7 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
var resp testResponse
|
||||
json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
|
||||
if resp.ErrorMsg != "" {
|
||||
t.Fatalf("local upload failed: %s", resp.ErrorMsg)
|
||||
@@ -334,7 +334,7 @@ func TestUploadFile(t *testing.T) {
|
||||
func TestDownloadFile(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer os.RemoveAll("uploads")
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
@@ -403,7 +403,7 @@ func TestDownloadFile(t *testing.T) {
|
||||
func TestBatchDownloadFiles(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer os.RemoveAll("uploads")
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
@@ -495,7 +495,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
t.Fatalf("failed to open zip file entry %s: %v", f.Name, err)
|
||||
}
|
||||
content, _ := io.ReadAll(rc)
|
||||
rc.Close()
|
||||
_ = rc.Close()
|
||||
extracted[f.Name] = string(content)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ limitations under the License.
|
||||
package upload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
@@ -27,7 +28,7 @@ const maxS3KeyLength = 1024
|
||||
// ValidateS3Key validates an S3 object key for safety.
|
||||
func ValidateS3Key(key string) error {
|
||||
if key == "" {
|
||||
return fmt.Errorf(ErrS3KeyRequired)
|
||||
return errors.New(ErrS3KeyRequired)
|
||||
}
|
||||
|
||||
if len(key) > maxS3KeyLength {
|
||||
@@ -35,11 +36,11 @@ func ValidateS3Key(key string) error {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(key, "/") {
|
||||
return fmt.Errorf(ErrS3KeyStartsWithSlash)
|
||||
return errors.New(ErrS3KeyStartsWithSlash)
|
||||
}
|
||||
|
||||
if strings.Contains(key, "\x00") {
|
||||
return fmt.Errorf(ErrS3KeyContainsNullBytes)
|
||||
return errors.New(ErrS3KeyContainsNullBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -125,7 +125,7 @@ func sendEmailVerificationCode(ctx context.Context, email, scene, templateName s
|
||||
|
||||
// 存验证码,5分钟有效
|
||||
if err := db.SetJSON(ctx, codeKey, code, 5*time.Minute); err != nil {
|
||||
return fmt.Errorf(errGenerateEmailCodeFailed)
|
||||
return errors.New(errGenerateEmailCodeFailed)
|
||||
}
|
||||
// 存冷却,60秒有效
|
||||
_ = db.SetJSON(ctx, cooldownKey, "1", 60*time.Second)
|
||||
@@ -139,7 +139,7 @@ func sendEmailVerificationCode(ctx context.Context, email, scene, templateName s
|
||||
payloadBytes, _ := json.Marshal(payload)
|
||||
_, err = task.DispatchTask(ctx, task.TaskTypeSendEmail, payloadBytes, "system")
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDispatchEmailTaskFailed)
|
||||
return errors.New(errDispatchEmailTaskFailed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestLogRingBuffer_WriteAndQuery(t *testing.T) {
|
||||
rb := NewLogRingBuffer(5)
|
||||
|
||||
// Write some logs
|
||||
rb.Write([]byte("line1\nline2\nline3\n"))
|
||||
_, _ = rb.Write([]byte("line1\nline2\nline3\n"))
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
@@ -43,7 +43,7 @@ func TestLogRingBuffer_WriteAndQuery(t *testing.T) {
|
||||
func TestLogRingBuffer_CapacityOverflow(t *testing.T) {
|
||||
rb := NewLogRingBuffer(3)
|
||||
|
||||
rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
@@ -56,7 +56,7 @@ func TestLogRingBuffer_CapacityOverflow(t *testing.T) {
|
||||
func TestLogRingBuffer_QueryLatest(t *testing.T) {
|
||||
rb := NewLogRingBuffer(10)
|
||||
|
||||
rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
|
||||
// Query latest 2
|
||||
entries, hasMore := rb.Query(0, 2)
|
||||
@@ -69,7 +69,7 @@ func TestLogRingBuffer_QueryLatest(t *testing.T) {
|
||||
func TestLogRingBuffer_QueryByCursor(t *testing.T) {
|
||||
rb := NewLogRingBuffer(10)
|
||||
|
||||
rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
|
||||
// First get all to find indices
|
||||
all, _ := rb.Query(0, 10)
|
||||
@@ -87,7 +87,7 @@ func TestLogRingBuffer_QueryByCursor(t *testing.T) {
|
||||
func TestLogRingBuffer_QueryByCursorWithLimit(t *testing.T) {
|
||||
rb := NewLogRingBuffer(10)
|
||||
|
||||
rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
|
||||
// Query 2 entries before index 4
|
||||
entries, hasMore := rb.Query(4, 2)
|
||||
@@ -107,7 +107,7 @@ func TestLogRingBuffer_QueryEmpty(t *testing.T) {
|
||||
|
||||
func TestLogRingBuffer_QueryNonExistentCursor(t *testing.T) {
|
||||
rb := NewLogRingBuffer(5)
|
||||
rb.Write([]byte("a\nb\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\n"))
|
||||
|
||||
entries, hasMore := rb.Query(999, 10)
|
||||
assert.False(t, hasMore)
|
||||
@@ -122,7 +122,7 @@ func TestLogRingBuffer_Subscribe(t *testing.T) {
|
||||
ch := rb.Subscribe()
|
||||
defer rb.Unsubscribe(ch)
|
||||
|
||||
rb.Write([]byte("hello\n"))
|
||||
_, _ = rb.Write([]byte("hello\n"))
|
||||
|
||||
entry := <-ch
|
||||
assert.Equal(t, "hello", entry.Data)
|
||||
@@ -137,7 +137,7 @@ func TestLogRingBuffer_SubscribeMultiple(t *testing.T) {
|
||||
ch2 := rb.Subscribe()
|
||||
defer rb.Unsubscribe(ch2)
|
||||
|
||||
rb.Write([]byte("msg\n"))
|
||||
_, _ = rb.Write([]byte("msg\n"))
|
||||
|
||||
e1 := <-ch1
|
||||
e2 := <-ch2
|
||||
@@ -148,7 +148,7 @@ func TestLogRingBuffer_SubscribeMultiple(t *testing.T) {
|
||||
func TestLogRingBuffer_WriteNoNewline(t *testing.T) {
|
||||
rb := NewLogRingBuffer(5)
|
||||
|
||||
rb.Write([]byte("partial"))
|
||||
_, _ = rb.Write([]byte("partial"))
|
||||
|
||||
entries, _ := rb.Query(0, 10)
|
||||
assert.Equal(t, 1, len(entries))
|
||||
@@ -169,7 +169,7 @@ func TestLogRingBuffer_WriteEmpty(t *testing.T) {
|
||||
func TestLogRingBuffer_QueryAfterOverflow(t *testing.T) {
|
||||
rb := NewLogRingBuffer(3)
|
||||
|
||||
rb.Write([]byte("1\n2\n3\n4\n5\n6\n7\n"))
|
||||
_, _ = rb.Write([]byte("1\n2\n3\n4\n5\n6\n7\n"))
|
||||
|
||||
entries, hasMore := rb.Query(0, 10)
|
||||
assert.False(t, hasMore)
|
||||
@@ -187,7 +187,7 @@ func TestLogRingBuffer_QueryAfterOverflow(t *testing.T) {
|
||||
func TestLogRingBuffer_NextCursor(t *testing.T) {
|
||||
rb := NewLogRingBuffer(10)
|
||||
|
||||
rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
_, _ = rb.Write([]byte("a\nb\nc\nd\ne\n"))
|
||||
|
||||
// Query latest 2, should return next_cursor pointing to first returned entry
|
||||
entries, _ := rb.Query(0, 2)
|
||||
|
||||
@@ -277,7 +277,7 @@ func TestListTaskExecutions(t *testing.T) {
|
||||
assert.Equal(t, "list_002", items[0].TaskID)
|
||||
|
||||
// 按类型筛选
|
||||
items, total, err = ListTaskExecutions(ctx, ListTaskExecutionsRequest{TaskType: "other:task", Page: 1, PageSize: 10})
|
||||
_, total, err = ListTaskExecutions(ctx, ListTaskExecutionsRequest{TaskType: "other:task", Page: 1, PageSize: 10})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), total)
|
||||
|
||||
|
||||
@@ -106,12 +106,14 @@ func GetObjectViaCache(ctx context.Context, key string) (*ObjectInfo, error) {
|
||||
}
|
||||
|
||||
func getLocalCacheFile(ctx context.Context, localPath, metaPath string) (*ObjectInfo, error) {
|
||||
ctx, span := otel_trace.Start(ctx, "S3.GetLocalCacheFile", trace.WithSpanKind(trace.SpanKindClient))
|
||||
_, span := otel_trace.Start(ctx, "S3.GetLocalCacheFile", trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
|
||||
// 尝试打开本地缓存文件
|
||||
file, err := os.Open(localPath)
|
||||
defer file.Close()
|
||||
if err == nil {
|
||||
defer func() { _ = file.Close() }()
|
||||
}
|
||||
|
||||
// 文件不存在
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
@@ -149,7 +151,7 @@ func getLocalCacheFile(ctx context.Context, localPath, metaPath string) (*Object
|
||||
}
|
||||
|
||||
func saveToLocalCache(ctx context.Context, localPath, metaPath string, objInfo *ObjectInfo) error {
|
||||
ctx, span := otel_trace.Start(ctx, "S3.SaveToLocalCache", trace.WithSpanKind(trace.SpanKindClient))
|
||||
_, span := otel_trace.Start(ctx, "S3.SaveToLocalCache", trace.WithSpanKind(trace.SpanKindClient))
|
||||
defer span.End()
|
||||
|
||||
// 创建目录
|
||||
@@ -186,7 +188,7 @@ func saveFile(localPath string, data io.Reader) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
defer func() { _ = os.Remove(tempFile.Name()) }()
|
||||
|
||||
// 将内容写入临时文件
|
||||
if _, err := tempFile.ReadFrom(data); err != nil {
|
||||
|
||||
@@ -227,7 +227,7 @@ func GetObjectViaProxy(ctx context.Context, key string) (*ObjectInfo, error) {
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close()
|
||||
span.SetStatus(codes.Error, fmt.Sprintf("cdn returned status %d", resp.StatusCode))
|
||||
return nil, fmt.Errorf(errCDNStatusFailed, resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -155,7 +156,7 @@ func RetryTask(ctx context.Context, id uint64) (string, error) {
|
||||
}
|
||||
|
||||
if !execution.Retryable {
|
||||
return "", fmt.Errorf(errTaskNotRetryable)
|
||||
return "", errors.New(errTaskNotRetryable)
|
||||
}
|
||||
|
||||
if execution.RetryCount >= execution.MaxRetry {
|
||||
|
||||
@@ -73,7 +73,7 @@ func setupTest(t *testing.T) func() {
|
||||
RegisterHandler(testTaskType, successHandler())
|
||||
return func() {
|
||||
if AsynqClient != nil {
|
||||
AsynqClient.Close()
|
||||
_ = AsynqClient.Close()
|
||||
AsynqClient = nil
|
||||
}
|
||||
cleanup()
|
||||
|
||||
@@ -74,7 +74,7 @@ func SetupTestEnvironment(t *testing.T) (*gorm.DB, *miniredis.Miniredis, func())
|
||||
|
||||
// Cleanup function
|
||||
cleanup := func() {
|
||||
redisClient.Close()
|
||||
_ = redisClient.Close()
|
||||
mr.Close()
|
||||
// Reset database and Redis references
|
||||
db.SetDB(nil)
|
||||
|
||||
@@ -36,7 +36,7 @@ type ChallengeConfig struct {
|
||||
Count int // Number of puzzles (c)
|
||||
Size int // Salt length (s)
|
||||
Difficulty int // Difficulty prefix length (d)
|
||||
ExpiresMs time.Duration // Challenge TTL
|
||||
Expires time.Duration // Challenge TTL
|
||||
}
|
||||
|
||||
// ChallengeResponse is returned to the client
|
||||
@@ -156,12 +156,12 @@ func GenerateChallenge(secret []byte, conf ChallengeConfig, scope string) (*Chal
|
||||
if conf.Difficulty <= 0 {
|
||||
conf.Difficulty = 4
|
||||
}
|
||||
if conf.ExpiresMs <= 0 {
|
||||
conf.ExpiresMs = 10 * time.Minute
|
||||
if conf.Expires <= 0 {
|
||||
conf.Expires = 10 * time.Minute
|
||||
}
|
||||
|
||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||
expires := now + int64(conf.ExpiresMs/time.Millisecond)
|
||||
expires := now + int64(conf.Expires/time.Millisecond)
|
||||
|
||||
payload := ChallengePayload{
|
||||
Nonce: randomHex(25),
|
||||
|
||||
@@ -75,7 +75,7 @@ func (m *Manager) Generate(ctx context.Context, scope string) (*ChallengeRespons
|
||||
Count: m.getChallengeCount(ctx),
|
||||
Size: m.getChallengeSize(ctx),
|
||||
Difficulty: m.getChallengeDifficulty(ctx),
|
||||
ExpiresMs: m.getChallengeTTL(ctx),
|
||||
Expires: m.getChallengeTTL(ctx),
|
||||
}
|
||||
return GenerateChallenge(m.conf.Secret, c, scope)
|
||||
}
|
||||
@@ -190,14 +190,6 @@ func (m *Manager) VerifyToken(ctx context.Context, token string, expectedScope s
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// sGet safely calls store.Get, treating a nil store as a miss.
|
||||
func sGet(ctx context.Context, store Store, key string) (string, bool, error) {
|
||||
if store == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
return store.Get(ctx, key)
|
||||
}
|
||||
|
||||
// sGetAndDelete safely calls store.GetAndDelete, treating a nil store as a miss.
|
||||
func sGetAndDelete(ctx context.Context, store Store, key string) (string, bool, error) {
|
||||
if store == nil {
|
||||
|
||||
@@ -41,11 +41,6 @@ func fnv1aResume(state uint32, str string) uint32 {
|
||||
return h
|
||||
}
|
||||
|
||||
// prng generates a hex string of specified length using a seed
|
||||
func prng(seed string, length int) string {
|
||||
return prngFromHash(fnv1a(seed), length)
|
||||
}
|
||||
|
||||
// prngFromHash generates a hex string of specified length using an initial hash state
|
||||
func prngFromHash(initialHash uint32, length int) string {
|
||||
state := initialHash
|
||||
|
||||
@@ -58,16 +58,12 @@ func Request(ctx context.Context, method, url string, body io.Reader, headers, c
|
||||
return nil, fmt.Errorf(errCreateHTTPRequestFailed, err)
|
||||
}
|
||||
|
||||
if cookies != nil {
|
||||
for key, value := range cookies {
|
||||
req.AddCookie(&http.Cookie{Name: key, Value: value})
|
||||
}
|
||||
for key, value := range cookies {
|
||||
req.AddCookie(&http.Cookie{Name: key, Value: value})
|
||||
}
|
||||
|
||||
if headers != nil {
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
|
||||
@@ -71,14 +71,14 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf(errDialTLSFailed, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
_ = conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
client, err := smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPClientCreationFailed, err)
|
||||
}
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf(errSMTPAuthFailed, err)
|
||||
@@ -96,7 +96,7 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf(errSMTPDataCommandFailed, err)
|
||||
}
|
||||
defer w.Close()
|
||||
defer func() { _ = w.Close() }()
|
||||
|
||||
_, err = w.Write([]byte(message))
|
||||
if err != nil {
|
||||
@@ -119,7 +119,7 @@ func SendMailHTML(cfg Config, to string, subject, body string) error {
|
||||
func SendMailWithLog(cfg Config, to string, subject, body string) (string, error) {
|
||||
var logBuf bytes.Buffer
|
||||
logLine := func(dir string, format string, args ...interface{}) {
|
||||
logBuf.WriteString(fmt.Sprintf("[%s] %s\n", dir, fmt.Sprintf(format, args...)))
|
||||
fmt.Fprintf(&logBuf, "[%s] %s\n", dir, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(cfg.Host, strconv.Itoa(cfg.Port))
|
||||
@@ -141,7 +141,7 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
logLine("Error", "Connection failed: %v", err)
|
||||
return logBuf.String(), err
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
logLine("System", "Connected successfully.")
|
||||
|
||||
// Set a 10-second session deadline for read/write operations
|
||||
@@ -152,7 +152,7 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
logLine("Error", "SMTP client handshake failed: %v", err)
|
||||
return logBuf.String(), err
|
||||
}
|
||||
defer client.Close()
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
// If not 465, support STARTTLS if available
|
||||
if cfg.Port != 465 {
|
||||
@@ -222,11 +222,11 @@ func SendMailWithLog(cfg Config, to string, subject, body string) (string, error
|
||||
|
||||
logLine("System", "Sending message body...")
|
||||
if _, err = w.Write([]byte(message)); err != nil {
|
||||
w.Close()
|
||||
_ = w.Close()
|
||||
logLine("Error", "Writing message body failed: %v", err)
|
||||
return logBuf.String(), err
|
||||
}
|
||||
w.Close()
|
||||
_ = w.Close()
|
||||
logLine("S", "250 OK")
|
||||
|
||||
logLine("C", "QUIT")
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestSendMailMock(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("failed to start mock smtp server: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
defer func() { _ = l.Close() }()
|
||||
|
||||
port := l.Addr().(*net.TCPAddr).Port
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestSendMailMock(t *testing.T) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
|
||||
writer := bufio.NewWriter(conn)
|
||||
reader := bufio.NewReader(conn)
|
||||
|
||||
Reference in New Issue
Block a user