mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
modernize 37→5(-32):interface{}→any、内置 max/min、slices/maps 辅助、strings.Cut/SplitSeq、strings.Builder(修复 mail.go O(n²) 拼接)。逐 hunk 核对语义等价;omitzero 冲突修复被自动跳过(wire 格式不变);手动清 4 处遗留 sort import + 2 处 QF1012。
Result: {"status":"keep","total_issues":74,"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":5,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":73,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":38}
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
{"type":"config","name":"前后端代码质量优化(符合最佳实践)","metricName":"total_issues","metricUnit":"","bestDirection":"lower"}
|
{"type":"config","name":"前后端代码质量优化(符合最佳实践)","metricName":"total_issues","metricUnit":"","bestDirection":"lower"}
|
||||||
{"run":1,"commit":"305d609","metric":108,"metrics":{"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":2,"golint_intrange":3,"golint_modernize":37,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":107,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":36},"status":"checks_failed","description":"基线:总问题 108(golangci 107 + eslint 1)。checks 失败的唯一原因:repo 自带 golangci gate 有 2 个既有 gosec G115 问题(预期内,首次修复后即绿)。","timestamp":1786871594292,"segment":0,"confidence":null,"asi":{"hypothesis":"baseline","next_action_hint":"修复 internal/apps/edge/observability/linux.go 的 2 个 G115 gosec 问题后 checks.sh 才能通过;之后每次迭代即可正常 keep/discard"}}
|
{"run":1,"commit":"305d609","metric":108,"metrics":{"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":2,"golint_intrange":3,"golint_modernize":37,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":107,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":36},"status":"checks_failed","description":"基线:总问题 108(golangci 107 + eslint 1)。checks 失败的唯一原因:repo 自带 golangci gate 有 2 个既有 gosec G115 问题(预期内,首次修复后即绿)。","timestamp":1786871594292,"segment":0,"confidence":null,"asi":{"hypothesis":"baseline","next_action_hint":"修复 internal/apps/edge/observability/linux.go 的 2 个 G115 gosec 问题后 checks.sh 才能通过;之后每次迭代即可正常 keep/discard"}}
|
||||||
|
{"run":2,"commit":"f1f6bb8","metric":106,"metrics":{"golint_canonicalheader":8,"golint_errname":1,"golint_errorlint":12,"golint_forcetypeassert":3,"golint_gosec":0,"golint_intrange":3,"golint_modernize":37,"golint_nilnil":3,"golint_perfsprint":18,"golint_prealloc":3,"golint_recvcheck":7,"golint_usestdlibvars":3,"golint_wastedassign":7,"golint_total":105,"eslint_problems":1,"eslint_errors":0,"eslint_warnings":1,"tsc_errors":0,"measure_s":38},"status":"keep","description":"修复 internal/apps/edge/observability/linux.go 的 2 个 gosec G115 整数溢出转换:helper 改为接收 int64 b,用 gosec 认可的饱和乘法模式(uint64 域乘积 + 上界比较),去掉原 //nolint:gosec,语义不变(Bsize 恒为正)。repo 自带 gate 首次全绿。","timestamp":1786872064145,"segment":0,"confidence":null,"asi":{"hypothesis":"修复 gosec G115:multiplyUint64ToInt64 改为 accept int64 b 并采用 gosec 认可的饱和乘法模式","insight":"gosec G115 不接受分支上界证明(a > MaxInt64/b),但接受先算 uint64 乘积再 if v > MaxInt64 饱和的模式,无需 nolint","next_action_hint":"下一步批量清理 modernize(37)/perfsprint(18) 等自动可修复类别,用 golangci-lint --fix 后人工核对 diff"}}
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ type GetTableDataRequest struct {
|
|||||||
|
|
||||||
// TableDataResponse 动态数据表响应结构体
|
// TableDataResponse 动态数据表响应结构体
|
||||||
type TableDataResponse struct {
|
type TableDataResponse struct {
|
||||||
Columns []string `json:"columns"`
|
Columns []string `json:"columns"`
|
||||||
Total int64 `json:"total"`
|
Total int64 `json:"total"`
|
||||||
Results []map[string]interface{} `json:"results"`
|
Results []map[string]any `json:"results"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecuteSQLRequest 执行自定义 SQL 请求结构体
|
// ExecuteSQLRequest 执行自定义 SQL 请求结构体
|
||||||
@@ -62,11 +62,11 @@ type ExecuteSQLRequest struct {
|
|||||||
|
|
||||||
// ExecuteSQLResponse 执行自定义 SQL 响应结构体
|
// ExecuteSQLResponse 执行自定义 SQL 响应结构体
|
||||||
type ExecuteSQLResponse struct {
|
type ExecuteSQLResponse struct {
|
||||||
Type string `json:"type"` // "select" 或 "exec"
|
Type string `json:"type"` // "select" 或 "exec"
|
||||||
Columns []string `json:"columns,omitempty"`
|
Columns []string `json:"columns,omitempty"`
|
||||||
Results []map[string]interface{} `json:"results,omitempty"`
|
Results []map[string]any `json:"results,omitempty"`
|
||||||
AffectedRows int64 `json:"affected_rows"`
|
AffectedRows int64 `json:"affected_rows"`
|
||||||
ExecutionTimeMs int64 `json:"execution_time_ms"`
|
ExecutionTimeMs int64 `json:"execution_time_ms"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatBytes 格式化字节大小为可读字符串
|
// formatBytes 格式化字节大小为可读字符串
|
||||||
@@ -119,10 +119,7 @@ func getSQLiteOverview(gormDB *gorm.DB) (DBOverviewResponse, error) {
|
|||||||
|
|
||||||
var sizeStr string
|
var sizeStr string
|
||||||
if fi, err := os.Stat(name); err == nil {
|
if fi, err := os.Stat(name); err == nil {
|
||||||
size := fi.Size()
|
size := max(fi.Size(), 0)
|
||||||
if size < 0 {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
sizeStr = formatBytes(uint64(size))
|
sizeStr = formatBytes(uint64(size))
|
||||||
} else {
|
} else {
|
||||||
sizeStr = "0 B"
|
sizeStr = "0 B"
|
||||||
@@ -165,10 +162,7 @@ func getPostgresOverview(gormDB *gorm.DB) (DBOverviewResponse, error) {
|
|||||||
var sizeStr string
|
var sizeStr string
|
||||||
var sizeBytes sql.NullInt64
|
var sizeBytes sql.NullInt64
|
||||||
if err := gormDB.Raw("SELECT pg_database_size(current_database())").Scan(&sizeBytes).Error; err == nil && sizeBytes.Valid {
|
if err := gormDB.Raw("SELECT pg_database_size(current_database())").Scan(&sizeBytes).Error; err == nil && sizeBytes.Valid {
|
||||||
size := sizeBytes.Int64
|
size := max(sizeBytes.Int64, 0)
|
||||||
if size < 0 {
|
|
||||||
size = 0
|
|
||||||
}
|
|
||||||
sizeStr = formatBytes(uint64(size))
|
sizeStr = formatBytes(uint64(size))
|
||||||
} else {
|
} else {
|
||||||
sizeStr = "0 B"
|
sizeStr = "0 B"
|
||||||
@@ -294,10 +288,7 @@ func GetDBTableData(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := (req.Page - 1) * req.PageSize
|
offset := max((req.Page-1)*req.PageSize, 0)
|
||||||
if offset < 0 {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
limit := req.PageSize
|
limit := req.PageSize
|
||||||
if limit <= 0 {
|
if limit <= 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
@@ -332,11 +323,11 @@ func GetDBTableData(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// scanTableRows 扫描并提取数据表行数据,做截断处理
|
// scanTableRows 扫描并提取数据表行数据,做截断处理
|
||||||
func scanTableRows(rows *sql.Rows, cols []string) ([]map[string]interface{}, error) {
|
func scanTableRows(rows *sql.Rows, cols []string) ([]map[string]any, error) {
|
||||||
results := make([]map[string]interface{}, 0)
|
results := make([]map[string]any, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
columns := make([]interface{}, len(cols))
|
columns := make([]any, len(cols))
|
||||||
columnPointers := make([]interface{}, len(cols))
|
columnPointers := make([]any, len(cols))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
columnPointers[i] = &columns[i]
|
columnPointers[i] = &columns[i]
|
||||||
}
|
}
|
||||||
@@ -345,7 +336,7 @@ func scanTableRows(rows *sql.Rows, cols []string) ([]map[string]interface{}, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rowMap := make(map[string]interface{})
|
rowMap := make(map[string]any)
|
||||||
for i, colName := range cols {
|
for i, colName := range cols {
|
||||||
val := columns[i]
|
val := columns[i]
|
||||||
if b, ok := val.([]byte); ok {
|
if b, ok := val.([]byte); ok {
|
||||||
@@ -385,10 +376,10 @@ func executeSQLQuery(gormDB *gorm.DB, sqlStr string, startTime time.Time) (Execu
|
|||||||
return ExecuteSQLResponse{}, err
|
return ExecuteSQLResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make([]map[string]interface{}, 0)
|
results := make([]map[string]any, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
columns := make([]interface{}, len(cols))
|
columns := make([]any, len(cols))
|
||||||
columnPointers := make([]interface{}, len(cols))
|
columnPointers := make([]any, len(cols))
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
columnPointers[i] = &columns[i]
|
columnPointers[i] = &columns[i]
|
||||||
}
|
}
|
||||||
@@ -397,7 +388,7 @@ func executeSQLQuery(gormDB *gorm.DB, sqlStr string, startTime time.Time) (Execu
|
|||||||
return ExecuteSQLResponse{}, err
|
return ExecuteSQLResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rowMap := make(map[string]interface{})
|
rowMap := make(map[string]any)
|
||||||
for i, colName := range cols {
|
for i, colName := range cols {
|
||||||
val := columns[i]
|
val := columns[i]
|
||||||
if b, ok := val.([]byte); ok {
|
if b, ok := val.([]byte); ok {
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ func getUpgrader() *websocket.Upgrader {
|
|||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
if sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyServerAddress); err == nil && sc.Value != "" {
|
if sc, err := repository.GetSystemConfigByKey(ctx, model.ConfigKeyServerAddress); err == nil && sc.Value != "" {
|
||||||
originToCheck := strings.TrimRight(strings.TrimSpace(origin), "/")
|
originToCheck := strings.TrimRight(strings.TrimSpace(origin), "/")
|
||||||
allowedOrigins := strings.Split(sc.Value, ",")
|
allowedOrigins := strings.SplitSeq(sc.Value, ",")
|
||||||
for _, allowed := range allowedOrigins {
|
for allowed := range allowedOrigins {
|
||||||
allowed = strings.TrimRight(strings.TrimSpace(allowed), "/")
|
allowed = strings.TrimRight(strings.TrimSpace(allowed), "/")
|
||||||
if allowed != "" && strings.EqualFold(allowed, originToCheck) {
|
if allowed != "" && strings.EqualFold(allowed, originToCheck) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package push
|
package push
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -66,13 +68,7 @@ func ListDefinitions() []Definition {
|
|||||||
}
|
}
|
||||||
// Add any others
|
// Add any others
|
||||||
for t, d := range definitions {
|
for t, d := range definitions {
|
||||||
found := false
|
found := slices.Contains(order, t)
|
||||||
for _, o := range order {
|
|
||||||
if o == t {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
if !found {
|
||||||
res = append(res, d)
|
res = append(res, d)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/infra/task"
|
"github.com/Rain-kl/Wavelet/internal/infra/task"
|
||||||
@@ -34,9 +35,7 @@ func (m NotificationMessage) Flatten() map[string]any {
|
|||||||
keyContent: m.Content,
|
keyContent: m.Content,
|
||||||
keyLevel: m.Level,
|
keyLevel: m.Level,
|
||||||
}
|
}
|
||||||
for k, v := range m.Ext {
|
maps.Copy(res, m.Ext)
|
||||||
res[k] = v
|
|
||||||
}
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -163,13 +164,7 @@ func selectLatestRelease(repository string, releases []githubRelease) (githubRel
|
|||||||
}
|
}
|
||||||
expectedNames := expectedAssetNames(repository, release.TagName)
|
expectedNames := expectedAssetNames(repository, release.TagName)
|
||||||
for _, asset := range release.Assets {
|
for _, asset := range release.Assets {
|
||||||
matched := false
|
matched := slices.Contains(expectedNames, asset.Name)
|
||||||
for _, name := range expectedNames {
|
|
||||||
if asset.Name == name {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !matched || asset.BrowserDownloadURL == "" || asset.State != "uploaded" {
|
if !matched || asset.BrowserDownloadURL == "" || asset.State != "uploaded" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -348,10 +343,8 @@ func getCandidateBinaryNames(executable string, repository string) []string {
|
|||||||
if runtime.GOOS == windowsOS && !strings.HasSuffix(strings.ToLower(name), ".exe") {
|
if runtime.GOOS == windowsOS && !strings.HasSuffix(strings.ToLower(name), ".exe") {
|
||||||
name += ".exe"
|
name += ".exe"
|
||||||
}
|
}
|
||||||
for _, existing := range names {
|
if slices.Contains(names, name) {
|
||||||
if existing == name {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
names = append(names, name)
|
names = append(names, name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package user
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -93,17 +94,13 @@ func abortUserLogicError(c *gin.Context, err error, notFoundMsg string, forbidde
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
for _, m := range badRequestMsgs {
|
if slices.Contains(badRequestMsgs, msg) {
|
||||||
if msg == m {
|
response.AbortBadRequest(c, msg)
|
||||||
response.AbortBadRequest(c, msg)
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for _, m := range forbiddenMsgs {
|
if slices.Contains(forbiddenMsgs, msg) {
|
||||||
if msg == m {
|
response.AbortForbidden(c, msg)
|
||||||
response.AbortForbidden(c, msg)
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
logger.ErrorF(c.Request.Context(), "Admin user error: %v", err)
|
logger.ErrorF(c.Request.Context(), "Admin user error: %v", err)
|
||||||
response.AbortInternal(c, "内部服务器错误")
|
response.AbortInternal(c, "内部服务器错误")
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func ensureWorldTraversablePath(targetDir string) error {
|
|||||||
if current == "" || current == "." {
|
if current == "" || current == "." {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for depth := 0; depth < maxDepth; depth++ {
|
for range maxDepth {
|
||||||
if err := os.Chmod(current, DefaultDirPerm); err != nil { //nolint:gosec // parent dirs must be traversable by the runtime user
|
if err := os.Chmod(current, DefaultDirPerm); err != nil { //nolint:gosec // parent dirs must be traversable by the runtime user
|
||||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ func (s *Service) ensurePagesProject(ctx context.Context, snapshot *state.Snapsh
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for attempt := 0; attempt < pagesLatestPullAttempts; attempt++ {
|
for attempt := range pagesLatestPullAttempts {
|
||||||
latest, err := s.client.GetPagesProjectLatestHash(ctx, projectID)
|
latest, err := s.client.GetPagesProjectLatestHash(ctx, projectID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fetch Pages project %d latest hash: %w", projectID, err)
|
return fmt.Errorf("fetch Pages project %d latest hash: %w", projectID, err)
|
||||||
@@ -359,10 +359,7 @@ func validatePagesPackageMetadata(
|
|||||||
if extractedBytes == 0 {
|
if extractedBytes == 0 {
|
||||||
extractedBytes = 1
|
extractedBytes = 1
|
||||||
}
|
}
|
||||||
maxFileBytes := extractedBytes
|
maxFileBytes := min(extractedBytes, agentPagesMaxFileBytes)
|
||||||
if maxFileBytes > agentPagesMaxFileBytes {
|
|
||||||
maxFileBytes = agentPagesMaxFileBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagesPackageLimits{
|
return pagesPackageLimits{
|
||||||
PackageBytes: metadata.PackageSize,
|
PackageBytes: metadata.PackageSize,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sort"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -363,7 +363,7 @@ func referencedWAFIPGroupIDs(supportFiles []protocol.SupportFile) ([]uint, error
|
|||||||
for id := range seen {
|
for id := range seen {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
slices.Sort(ids)
|
||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,10 +78,7 @@ func (m *Manager) Redeem(ctx context.Context, token string, solutions []int, sco
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UnixNano() / int64(time.Millisecond)
|
now := time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
nonceTTL := time.Duration(payload.Expires-now) * time.Millisecond
|
nonceTTL := max(time.Duration(payload.Expires-now)*time.Millisecond, time.Second)
|
||||||
if nonceTTL < time.Second {
|
|
||||||
nonceTTL = time.Second
|
|
||||||
}
|
|
||||||
|
|
||||||
set, err := m.store.SetNX(ctx, nonceKey, "1", nonceTTL)
|
set, err := m.store.SetNX(ctx, nonceKey, "1", nonceTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -96,10 +96,7 @@ func ReadMemInfo() (int64, int64) {
|
|||||||
if total == 0 {
|
if total == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
used := total - (memAvailableKB * 1024)
|
used := max(total-(memAvailableKB*1024), 0)
|
||||||
if used < 0 {
|
|
||||||
used = 0
|
|
||||||
}
|
|
||||||
return total, used
|
return total, used
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +135,8 @@ func ReadLinuxCPUStat() (uint64, uint64) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
lines := strings.Split(string(content), "\n")
|
lines := strings.SplitSeq(string(content), "\n")
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
if !strings.HasPrefix(line, "cpu ") {
|
if !strings.HasPrefix(line, "cpu ") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -260,10 +257,7 @@ func StatFilesystem(path string) (int64, int64) {
|
|||||||
}
|
}
|
||||||
total := multiplyUint64Int64(stat.Blocks, stat.Bsize)
|
total := multiplyUint64Int64(stat.Blocks, stat.Bsize)
|
||||||
free := multiplyUint64Int64(stat.Bavail, stat.Bsize)
|
free := multiplyUint64Int64(stat.Bavail, stat.Bsize)
|
||||||
used := total - free
|
used := max(total-free, 0)
|
||||||
if used < 0 {
|
|
||||||
used = 0
|
|
||||||
}
|
|
||||||
return total, used
|
return total, used
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ func (s *Service) downloadChecksum(ctx context.Context, url string, assetName st
|
|||||||
|
|
||||||
func parseSHA256Checksum(content string, assetName string) (string, error) {
|
func parseSHA256Checksum(content string, assetName string) (string, error) {
|
||||||
assetName = strings.TrimSpace(assetName)
|
assetName = strings.TrimSpace(assetName)
|
||||||
for _, line := range strings.Split(content, "\n") {
|
for line := range strings.SplitSeq(content, "\n") {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if line == "" || strings.HasPrefix(line, "#") {
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ func (m *Manager) restartProcess(ctx context.Context, relayID string, configPath
|
|||||||
startedAt := time.Now()
|
startedAt := time.Now()
|
||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), frpcConfigFilePerm)
|
_ = os.WriteFile(pidPath, fmt.Appendf(nil, "%d", cmd.Process.Pid), frpcConfigFilePerm)
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
}
|
}
|
||||||
_ = os.Remove(pidPath)
|
_ = os.Remove(pidPath)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package oauth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/model"
|
"github.com/Rain-kl/Wavelet/internal/model"
|
||||||
@@ -109,10 +110,5 @@ func buildOAuthConfig(ctx context.Context, source *model.AuthSource, redirectURL
|
|||||||
}
|
}
|
||||||
|
|
||||||
func containsScope(scopes []string, scope string) bool {
|
func containsScope(scopes []string, scope string) bool {
|
||||||
for _, item := range scopes {
|
return slices.Contains(scopes, scope)
|
||||||
if item == scope {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,18 +179,9 @@ func buildNodeAccessLogRecords(nodeID string, direct []NodeAccessLog, buffered [
|
|||||||
records := make([]*model.OpenFlareAccessLog, 0, total)
|
records := make([]*model.OpenFlareAccessLog, 0, total)
|
||||||
appendLogs := func(logs []NodeAccessLog) {
|
appendLogs := func(logs []NodeAccessLog) {
|
||||||
for _, item := range logs {
|
for _, item := range logs {
|
||||||
bytesSent := item.BytesSent
|
bytesSent := max(item.BytesSent, 0)
|
||||||
if bytesSent < 0 {
|
requestLength := max(item.RequestLength, 0)
|
||||||
bytesSent = 0
|
requestTimeMs := max(item.RequestTimeMs, 0)
|
||||||
}
|
|
||||||
requestLength := item.RequestLength
|
|
||||||
if requestLength < 0 {
|
|
||||||
requestLength = 0
|
|
||||||
}
|
|
||||||
requestTimeMs := item.RequestTimeMs
|
|
||||||
if requestTimeMs < 0 {
|
|
||||||
requestTimeMs = 0
|
|
||||||
}
|
|
||||||
record := &model.OpenFlareAccessLog{
|
record := &model.OpenFlareAccessLog{
|
||||||
NodeID: nodeID,
|
NodeID: nodeID,
|
||||||
LoggedAt: timeFromUnix(item.LoggedAtUnix, reportedAt),
|
LoggedAt: timeFromUnix(item.LoggedAtUnix, reportedAt),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -97,7 +98,7 @@ func buildAgentWAFIPGroups(ctx context.Context, ids []uint) ([]WAFIPGroup, error
|
|||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return []WAFIPGroup{}, nil
|
return []WAFIPGroup{}, nil
|
||||||
}
|
}
|
||||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
slices.Sort(ids)
|
||||||
groups, err := repository.ListOpenFlareWAFIPGroupsByIDs(ctx, ids)
|
groups, err := repository.ListOpenFlareWAFIPGroupsByIDs(ctx, ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -204,7 +205,7 @@ func activeConfigWAFIPGroupIDs(ctx context.Context) ([]uint, error) {
|
|||||||
for id := range idSet {
|
for id := range idSet {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
slices.Sort(ids)
|
||||||
return ids, nil
|
return ids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ func (client *HTTPClient) do(ctx context.Context, method, path string, query url
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
requestURL := buildRequestURL(client.baseURL, path, query)
|
requestURL := buildRequestURL(client.baseURL, path, query)
|
||||||
for attempt := 0; attempt < maxRequestAttempts; attempt++ {
|
for attempt := range maxRequestAttempts {
|
||||||
statusCode, retryHeader, responseBody, requestErr := client.send(ctx, method, requestURL, encodedBody)
|
statusCode, retryHeader, responseBody, requestErr := client.send(ctx, method, requestURL, encodedBody)
|
||||||
if requestErr != nil {
|
if requestErr != nil {
|
||||||
return requestErr
|
return requestErr
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -429,7 +430,7 @@ func buildSnapshotWAFIPGroups(ctx context.Context, idSet map[uint]struct{}) ([]s
|
|||||||
for id := range idSet {
|
for id := range idSet {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
slices.Sort(ids)
|
||||||
groups, err := listWAFIPGroupsByIDs(ctx, ids)
|
groups, err := listWAFIPGroupsByIDs(ctx, ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -629,7 +630,7 @@ func buildCertificateSupportFiles(ctx context.Context, routes []snapshotRoute) (
|
|||||||
for certID := range certIDSet {
|
for certID := range certIDSet {
|
||||||
certIDs = append(certIDs, certID)
|
certIDs = append(certIDs, certID)
|
||||||
}
|
}
|
||||||
sort.Slice(certIDs, func(i, j int) bool { return certIDs[i] < certIDs[j] })
|
slices.Sort(certIDs)
|
||||||
files := make([]SupportFile, 0, len(certIDs)*supportFilesPerCertificate)
|
files := make([]SupportFile, 0, len(certIDs)*supportFilesPerCertificate)
|
||||||
for _, certID := range certIDs {
|
for _, certID := range certIDs {
|
||||||
certificate, err := repository.GetTLSCertificateByID(ctx, certID)
|
certificate, err := repository.GetTLSCertificateByID(ctx, certID)
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ func validateOpenRestyCacheUseStale(key, trimmed string) error {
|
|||||||
"http_500": {}, "http_502": {}, "http_503": {}, "http_504": {},
|
"http_500": {}, "http_502": {}, "http_503": {}, "http_504": {},
|
||||||
"http_403": {}, "http_404": {}, "http_429": {}, "off": {},
|
"http_403": {}, "http_404": {}, "http_429": {}, "off": {},
|
||||||
}
|
}
|
||||||
for _, token := range strings.Fields(trimmed) {
|
for token := range strings.FieldsSeq(trimmed) {
|
||||||
if _, ok := allowedTokens[token]; !ok {
|
if _, ok := allowedTokens[token]; !ok {
|
||||||
return fmt.Errorf("%s 包含不支持的值 %q", key, token)
|
return fmt.Errorf("%s 包含不支持的值 %q", key, token)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ func validateOriginAddress(address string) error {
|
|||||||
if len(address) > maxOriginHostnameLength {
|
if len(address) > maxOriginHostnameLength {
|
||||||
return errors.New(errOriginAddressInvalid)
|
return errors.New(errOriginAddressInvalid)
|
||||||
}
|
}
|
||||||
labels := strings.Split(address, ".")
|
labels := strings.SplitSeq(address, ".")
|
||||||
for _, label := range labels {
|
for label := range labels {
|
||||||
if len(label) == 0 || len(label) > 63 {
|
if len(label) == 0 || len(label) > 63 {
|
||||||
return errors.New(errOriginAddressInvalid)
|
return errors.New(errOriginAddressInvalid)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,18 +80,11 @@ func resolvePagesLimits(ctx context.Context) pagesLimits {
|
|||||||
|
|
||||||
historyCount := defaultPagesMaxHistoryCount
|
historyCount := defaultPagesMaxHistoryCount
|
||||||
if value, err := repository.GetIntByKey(ctx, model.ConfigKeyPagesMaxHistoryCount); err == nil {
|
if value, err := repository.GetIntByKey(ctx, model.ConfigKeyPagesMaxHistoryCount); err == nil {
|
||||||
if value < 0 {
|
historyCount = max(value, 0)
|
||||||
historyCount = 0
|
|
||||||
} else {
|
|
||||||
historyCount = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
packageBytes := int64(packageMB) * bytesPerMiB
|
packageBytes := int64(packageMB) * bytesPerMiB
|
||||||
extractedBytes := packageBytes * pagesExtractedSizeMultiplier
|
extractedBytes := max(packageBytes*pagesExtractedSizeMultiplier, pagesMinExtractedSizeBytes)
|
||||||
if extractedBytes < pagesMinExtractedSizeBytes {
|
|
||||||
extractedBytes = pagesMinExtractedSizeBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagesLimits{
|
return pagesLimits{
|
||||||
PackageBytes: packageBytes,
|
PackageBytes: packageBytes,
|
||||||
@@ -154,7 +147,7 @@ func normalizePagesFallbackPath(raw string) (string, error) {
|
|||||||
return "", errors.New("spa fallback 回退路径不能包含空白或控制字符")
|
return "", errors.New("spa fallback 回退路径不能包含空白或控制字符")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, segment := range strings.Split(value, "/") {
|
for segment := range strings.SplitSeq(value, "/") {
|
||||||
if segment == "." || segment == ".." {
|
if segment == "." || segment == ".." {
|
||||||
return "", errors.New("spa fallback 回退路径不能包含 . 或 .. 路径段")
|
return "", errors.New("spa fallback 回退路径不能包含 . 或 .. 路径段")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -513,7 +513,7 @@ func pruneProjectDeploymentHistory(ctx context.Context, projectID uint, keepCoun
|
|||||||
// Two passes: first pass after upload, second pass heals a concurrent race
|
// Two passes: first pass after upload, second pass heals a concurrent race
|
||||||
// that inserted another deployment between our list and delete.
|
// that inserted another deployment between our list and delete.
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for pass := 0; pass < 2; pass++ {
|
for range 2 {
|
||||||
deleted, err := pruneProjectDeploymentHistoryOnce(ctx, projectID, keepCount, preserveCandidateID)
|
deleted, err := pruneProjectDeploymentHistoryOnce(ctx, projectID, keepCount, preserveCandidateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -611,7 +611,7 @@ func lockSourceDeploymentUploadsTx(
|
|||||||
if hasIngest && ingestResult.Upload.ID != 0 && ingestResult.Upload.ID != target.UploadID {
|
if hasIngest && ingestResult.Upload.ID != 0 && ingestResult.Upload.ID != target.UploadID {
|
||||||
uploadIDs = append(uploadIDs, ingestResult.Upload.ID)
|
uploadIDs = append(uploadIDs, ingestResult.Upload.ID)
|
||||||
}
|
}
|
||||||
sort.Slice(uploadIDs, func(i, j int) bool { return uploadIDs[i] < uploadIDs[j] })
|
slices.Sort(uploadIDs)
|
||||||
var records []model.Upload
|
var records []model.Upload
|
||||||
if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).
|
if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).
|
||||||
Where("id IN ?", uploadIDs).
|
Where("id IN ?", uploadIDs).
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ func validateOriginAddress(address string) error {
|
|||||||
if len(address) > maxOriginHostnameLength {
|
if len(address) > maxOriginHostnameLength {
|
||||||
return errors.New(errProxyRouteOriginInvalid)
|
return errors.New(errProxyRouteOriginInvalid)
|
||||||
}
|
}
|
||||||
labels := strings.Split(address, ".")
|
labels := strings.SplitSeq(address, ".")
|
||||||
for _, label := range labels {
|
for label := range labels {
|
||||||
if len(label) == 0 || len(label) > 63 {
|
if len(label) == 0 || len(label) > 63 {
|
||||||
return errors.New(errProxyRouteOriginInvalid)
|
return errors.New(errProxyRouteOriginInvalid)
|
||||||
}
|
}
|
||||||
@@ -167,8 +167,8 @@ func buildOriginURLFromParts(scheme, address, port, uri string) (string, error)
|
|||||||
Host: formatOriginHost(normalizedAddress, normalizedPort),
|
Host: formatOriginHost(normalizedAddress, normalizedPort),
|
||||||
}
|
}
|
||||||
if normalizedURI != "" {
|
if normalizedURI != "" {
|
||||||
if strings.HasPrefix(normalizedURI, "?") {
|
if after, ok := strings.CutPrefix(normalizedURI, "?"); ok {
|
||||||
parsed.RawQuery = strings.TrimPrefix(normalizedURI, "?")
|
parsed.RawQuery = after
|
||||||
} else {
|
} else {
|
||||||
pathQuery := strings.SplitN(normalizedURI, "?", originURIPathQueryParts)
|
pathQuery := strings.SplitN(normalizedURI, "?", originURIPathQueryParts)
|
||||||
parsed.Path = pathQuery[0]
|
parsed.Path = pathQuery[0]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package proxy_route
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"sort"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ func lockPagesProjectsForRouteMutation(tx *gorm.DB, previousProjectID uint, rout
|
|||||||
if nextProjectID != 0 && nextProjectID != previousProjectID {
|
if nextProjectID != 0 && nextProjectID != previousProjectID {
|
||||||
projectIDs = append(projectIDs, nextProjectID)
|
projectIDs = append(projectIDs, nextProjectID)
|
||||||
}
|
}
|
||||||
sort.Slice(projectIDs, func(i int, j int) bool { return projectIDs[i] < projectIDs[j] })
|
slices.Sort(projectIDs)
|
||||||
|
|
||||||
for _, projectID := range projectIDs {
|
for _, projectID := range projectIDs {
|
||||||
project, err := repository.LockPagesProjectByIDTx(tx, projectID)
|
project, err := repository.LockPagesProjectByIDTx(tx, projectID)
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ func splitAcmeDomains(primaryDomain, otherDomains string) []string {
|
|||||||
if !strings.Contains(otherDomains, "\n") && strings.Contains(otherDomains, ",") {
|
if !strings.Contains(otherDomains, "\n") && strings.Contains(otherDomains, ",") {
|
||||||
separator = ","
|
separator = ","
|
||||||
}
|
}
|
||||||
for _, domain := range strings.Split(otherDomains, separator) {
|
for domain := range strings.SplitSeq(otherDomains, separator) {
|
||||||
domain = strings.TrimSpace(domain)
|
domain = strings.TrimSpace(domain)
|
||||||
if domain != "" {
|
if domain != "" {
|
||||||
domains = append(domains, domain)
|
domains = append(domains, domain)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -180,8 +181,8 @@ func (c *SocketIOClient) pollLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug("Received polling payload from Uptime Kuma", "length", len(bodyStr))
|
slog.Debug("Received polling payload from Uptime Kuma", "length", len(bodyStr))
|
||||||
packets := strings.Split(bodyStr, "\x1e")
|
packets := strings.SplitSeq(bodyStr, "\x1e")
|
||||||
for _, pkt := range packets {
|
for pkt := range packets {
|
||||||
if len(pkt) == 0 {
|
if len(pkt) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -352,9 +353,7 @@ func (c *SocketIOClient) GetMonitorList() map[string]Monitor {
|
|||||||
defer c.monitorListMutex.RUnlock()
|
defer c.monitorListMutex.RUnlock()
|
||||||
|
|
||||||
m := make(map[string]Monitor, len(c.monitorList))
|
m := make(map[string]Monitor, len(c.monitorList))
|
||||||
for k, v := range c.monitorList {
|
maps.Copy(m, c.monitorList)
|
||||||
m[k] = v
|
|
||||||
}
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package waf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ func sortedUniqueStrings(values []string) []string {
|
|||||||
|
|
||||||
func sortedUniqueUints(values []uint) []uint {
|
func sortedUniqueUints(values []uint) []uint {
|
||||||
result := append([]uint(nil), values...)
|
result := append([]uint(nil), values...)
|
||||||
sort.Slice(result, func(i, j int) bool { return result[i] < result[j] })
|
slices.Sort(result)
|
||||||
write := 0
|
write := 0
|
||||||
for _, value := range result {
|
for _, value := range result {
|
||||||
if write == 0 || result[write-1] != value {
|
if write == 0 || result[write-1] != value {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -374,12 +375,7 @@ func decodeStrictConfig(raw json.RawMessage, dst any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validSourceHandle(t RuleNodeType, handle string) bool {
|
func validSourceHandle(t RuleNodeType, handle string) bool {
|
||||||
for _, expected := range requiredHandles(t) {
|
return slices.Contains(requiredHandles(t), handle)
|
||||||
if handle == expected {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
func requiredHandles(t RuleNodeType) []string {
|
func requiredHandles(t RuleNodeType) []string {
|
||||||
switch t {
|
switch t {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -325,9 +326,7 @@ func evaluateParsedIPGroupAutoConfig(ctx context.Context, config ipGroupAutoConf
|
|||||||
lastSeen = time.Unix(item.LastSeenEpoch, 0).UTC()
|
lastSeen = time.Unix(item.LastSeenEpoch, 0).UTC()
|
||||||
}
|
}
|
||||||
statusCounts := make(map[int]int, len(item.StatusCounts))
|
statusCounts := make(map[int]int, len(item.StatusCounts))
|
||||||
for code, count := range item.StatusCounts {
|
maps.Copy(statusCounts, item.StatusCounts)
|
||||||
statusCounts[code] = count
|
|
||||||
}
|
|
||||||
accumulators[ip] = &ipGroupAutoAccumulator{
|
accumulators[ip] = &ipGroupAutoAccumulator{
|
||||||
ip: ip,
|
ip: ip,
|
||||||
requestCount: item.RequestCount,
|
requestCount: item.RequestCount,
|
||||||
@@ -479,7 +478,7 @@ func selectJSONMappingNodes(payload any, mappingRule string) ([]any, error) {
|
|||||||
}
|
}
|
||||||
rule = strings.TrimPrefix(rule, "$.")
|
rule = strings.TrimPrefix(rule, "$.")
|
||||||
nodes := []any{payload}
|
nodes := []any{payload}
|
||||||
for _, rawSegment := range strings.Split(rule, ".") {
|
for rawSegment := range strings.SplitSeq(rule, ".") {
|
||||||
segment := strings.TrimSpace(rawSegment)
|
segment := strings.TrimSpace(rawSegment)
|
||||||
if segment == "" {
|
if segment == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -183,10 +183,7 @@ func emptyStatsSeries(since, until time.Time, bucketMinutes int) []StatsPoint {
|
|||||||
}
|
}
|
||||||
// Cap points to keep chart readable.
|
// Cap points to keep chart readable.
|
||||||
maxPoints := 120
|
maxPoints := 120
|
||||||
capacity := int(end.Sub(start)/bucket) + 1
|
capacity := min(int(end.Sub(start)/bucket)+1, maxPoints)
|
||||||
if capacity > maxPoints {
|
|
||||||
capacity = maxPoints
|
|
||||||
}
|
|
||||||
points := make([]StatsPoint, 0, capacity)
|
points := make([]StatsPoint, 0, capacity)
|
||||||
for cursor := start; !cursor.After(end) && len(points) < maxPoints; cursor = cursor.Add(bucket) {
|
for cursor := start; !cursor.After(end) && len(points) < maxPoints; cursor = cursor.Add(bucket) {
|
||||||
points = append(points, StatsPoint{BucketStartedAt: cursor})
|
points = append(points, StatsPoint{BucketStartedAt: cursor})
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func (m *Manager) supervise(ctx context.Context, generation uint64) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = os.WriteFile(m.pidPath, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), frpsPidFilePerm)
|
_ = os.WriteFile(m.pidPath, fmt.Appendf(nil, "%d", cmd.Process.Pid), frpsPidFilePerm)
|
||||||
|
|
||||||
m.cmd = cmd
|
m.cmd = cmd
|
||||||
m.status = "healthy"
|
m.status = "healthy"
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func validateAllowedExtension(ctx context.Context, ext string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedExts := strings.Split(strings.ToLower(sc.Value), ",")
|
allowedExts := strings.SplitSeq(strings.ToLower(sc.Value), ",")
|
||||||
for _, allowedExt := range allowedExts {
|
for allowedExt := range allowedExts {
|
||||||
if strings.TrimSpace(allowedExt) == ext {
|
if strings.TrimSpace(allowedExt) == ext {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,18 +193,18 @@ func migrateObjects(
|
|||||||
const batchSize = 50
|
const batchSize = 50
|
||||||
const migrationConcurrency = 10
|
const migrationConcurrency = 10
|
||||||
const sha256HexLength = 64
|
const sha256HexLength = 64
|
||||||
var migrated int64
|
var migrated atomic.Int64
|
||||||
var lastFilePath string
|
var lastFilePath string
|
||||||
for {
|
for {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return atomic.LoadInt64(&migrated), fmt.Errorf("storage migration canceled: %w", err)
|
return migrated.Load(), fmt.Errorf("storage migration canceled: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
task.AppendLog(ctx, "正在查询待迁移对象批次,当前已完成迁移: %d/%d", atomic.LoadInt64(&migrated), total)
|
task.AppendLog(ctx, "正在查询待迁移对象批次,当前已完成迁移: %d/%d", migrated.Load(), total)
|
||||||
|
|
||||||
objects, err := repository.ListDistinctActiveStorageObjects(ctx, lastFilePath, batchSize)
|
objects, err := repository.ListDistinctActiveStorageObjects(ctx, lastFilePath, batchSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return atomic.LoadInt64(&migrated), fmt.Errorf("query source objects: %w", err)
|
return migrated.Load(), fmt.Errorf("query source objects: %w", err)
|
||||||
}
|
}
|
||||||
if len(objects) == 0 {
|
if len(objects) == 0 {
|
||||||
task.AppendLog(ctx, "所有对象迁移完毕")
|
task.AppendLog(ctx, "所有对象迁移完毕")
|
||||||
@@ -223,18 +223,18 @@ func migrateObjects(
|
|||||||
if err := migrateSingleObject(ctx, sourceBackend, targetBackend, obj, sha256HexLength); err != nil {
|
if err := migrateSingleObject(ctx, sourceBackend, targetBackend, obj, sha256HexLength); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
atomic.AddInt64(&migrated, 1)
|
migrated.Add(1)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.Wait(); err != nil {
|
if err := g.Wait(); err != nil {
|
||||||
return atomic.LoadInt64(&migrated), err
|
return migrated.Load(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
task.AppendLog(ctx, "当前批次迁移完成。迁移进度: %d/%d", atomic.LoadInt64(&migrated), total)
|
task.AppendLog(ctx, "当前批次迁移完成。迁移进度: %d/%d", migrated.Load(), total)
|
||||||
}
|
}
|
||||||
return atomic.LoadInt64(&migrated), nil
|
return migrated.Load(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func migrateSingleObject(
|
func migrateSingleObject(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Rain-kl/Wavelet/internal/apps/upload/shared"
|
"github.com/Rain-kl/Wavelet/internal/apps/upload/shared"
|
||||||
@@ -11,32 +12,17 @@ import (
|
|||||||
|
|
||||||
// IsImageExtension reports whether ext is a common image format.
|
// IsImageExtension reports whether ext is a common image format.
|
||||||
func IsImageExtension(ext string) bool {
|
func IsImageExtension(ext string) bool {
|
||||||
for _, imgExt := range []string{"jpg", "jpeg", "png", "webp", "gif"} {
|
return slices.Contains([]string{"jpg", "jpeg", "png", "webp", "gif"}, ext)
|
||||||
if ext == imgExt {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsArchiveExtension reports whether ext is a common archive format.
|
// IsArchiveExtension reports whether ext is a common archive format.
|
||||||
func IsArchiveExtension(ext string) bool {
|
func IsArchiveExtension(ext string) bool {
|
||||||
for _, e := range []string{"zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "xz"} {
|
return slices.Contains([]string{"zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "xz"}, ext)
|
||||||
if ext == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDocumentExtension reports whether ext is a common document format.
|
// IsDocumentExtension reports whether ext is a common document format.
|
||||||
func IsDocumentExtension(ext string) bool {
|
func IsDocumentExtension(ext string) bool {
|
||||||
for _, e := range []string{"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "md", "csv", "json", "yaml", "yml", "xml"} {
|
return slices.Contains([]string{"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "md", "csv", "json", "yaml", "yml", "xml"}, ext)
|
||||||
if ext == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeImageQuality normalizes the requested image quality query parameter.
|
// NormalizeImageQuality normalizes the requested image quality query parameter.
|
||||||
|
|||||||
+6
-12
@@ -27,34 +27,28 @@ var allCmd = &cobra.Command{
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// 启动 API HTTP 服务
|
// 启动 API HTTP 服务
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
log.Println("[All] 启动 API 服务")
|
log.Println("[All] 启动 API 服务")
|
||||||
router.Serve(func() {
|
router.Serve(func() {
|
||||||
printStartupBanner(startupState{mode: "API + Worker + Scheduler", relationalDB: latestMigrationState.relationalDB, clickHouseDB: latestMigrationState.clickHouseDB, listensForHTTP: true})
|
printStartupBanner(startupState{mode: "API + Worker + Scheduler", relationalDB: latestMigrationState.relationalDB, clickHouseDB: latestMigrationState.clickHouseDB, listensForHTTP: true})
|
||||||
})
|
})
|
||||||
}()
|
})
|
||||||
|
|
||||||
// 启动 Asynq Worker 任务处理服务
|
// 启动 Asynq Worker 任务处理服务
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
log.Println("[All] 启动 Worker 服务")
|
log.Println("[All] 启动 Worker 服务")
|
||||||
if err := worker.StartWorker(); err != nil {
|
if err := worker.StartWorker(); err != nil {
|
||||||
log.Printf("[All] Worker 启动失败: %v\n", err)
|
log.Printf("[All] Worker 启动失败: %v\n", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
// 启动 Asynq 定时任务调度器
|
// 启动 Asynq 定时任务调度器
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
log.Println("[All] 启动 Scheduler 服务")
|
log.Println("[All] 启动 Scheduler 服务")
|
||||||
if err := scheduler.StartScheduler(); err != nil {
|
if err := scheduler.StartScheduler(); err != nil {
|
||||||
log.Printf("[All] Scheduler 启动失败: %v\n", err)
|
log.Printf("[All] Scheduler 启动失败: %v\n", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,10 +31,11 @@ func findConfigPath(configPath string) string {
|
|||||||
if _, err := os.Stat(configPath); err == nil {
|
if _, err := os.Stat(configPath); err == nil {
|
||||||
return configPath
|
return configPath
|
||||||
}
|
}
|
||||||
dir := "."
|
var dir strings.Builder
|
||||||
for i := 0; i < 5; i++ {
|
dir.WriteString(".")
|
||||||
dir += "/.."
|
for range 5 {
|
||||||
path := dir + "/" + configPath
|
dir.WriteString("/..")
|
||||||
|
path := dir.String() + "/" + configPath
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,26 +31,26 @@ func (l *gormZapLogger) LogMode(level gormLogger.LogLevel) gormLogger.Interface
|
|||||||
return &clone
|
return &clone
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *gormZapLogger) Info(ctx context.Context, fmt string, args ...interface{}) {
|
func (l *gormZapLogger) Info(ctx context.Context, fmt string, args ...any) {
|
||||||
if l.logLevel >= gormLogger.Info {
|
if l.logLevel >= gormLogger.Info {
|
||||||
logger.InfoF(ctx, fmt, args...)
|
logger.InfoF(ctx, fmt, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *gormZapLogger) Warn(ctx context.Context, fmt string, args ...interface{}) {
|
func (l *gormZapLogger) Warn(ctx context.Context, fmt string, args ...any) {
|
||||||
if l.logLevel >= gormLogger.Warn {
|
if l.logLevel >= gormLogger.Warn {
|
||||||
logger.WarnF(ctx, fmt, args...)
|
logger.WarnF(ctx, fmt, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *gormZapLogger) Error(ctx context.Context, fmt string, args ...interface{}) {
|
func (l *gormZapLogger) Error(ctx context.Context, fmt string, args ...any) {
|
||||||
if l.logLevel >= gormLogger.Error {
|
if l.logLevel >= gormLogger.Error {
|
||||||
logger.ErrorF(ctx, fmt, args...)
|
logger.ErrorF(ctx, fmt, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParamsFilter 让 GORM 的 Trace 回调只接收参数化 SQL,避免绑定值被 Dialector.Explain 展开到日志。
|
// ParamsFilter 让 GORM 的 Trace 回调只接收参数化 SQL,避免绑定值被 Dialector.Explain 展开到日志。
|
||||||
func (l *gormZapLogger) ParamsFilter(_ context.Context, sql string, _ ...interface{}) (string, []interface{}) {
|
func (l *gormZapLogger) ParamsFilter(_ context.Context, sql string, _ ...any) (string, []any) {
|
||||||
return sql, nil
|
return sql, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ func (l *gormZapLogger) Trace(ctx context.Context, begin time.Time, fc func() (s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatRows(rows int64) interface{} {
|
func formatRows(rows int64) any {
|
||||||
if rows == -1 {
|
if rows == -1 {
|
||||||
return "-"
|
return "-"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ func IsFinalAttempt(ctx context.Context) bool {
|
|||||||
|
|
||||||
// AppendLog 追加日志到任务执行记录
|
// AppendLog 追加日志到任务执行记录
|
||||||
// 在 TaskHandler.Execute 中调用,日志会自动追加到 TaskExecution.Log 字段
|
// 在 TaskHandler.Execute 中调用,日志会自动追加到 TaskExecution.Log 字段
|
||||||
func AppendLog(ctx context.Context, format string, args ...interface{}) {
|
func AppendLog(ctx context.Context, format string, args ...any) {
|
||||||
taskID := GetTaskID(ctx)
|
taskID := GetTaskID(ctx)
|
||||||
if taskID == "" {
|
if taskID == "" {
|
||||||
// 上下文中没有 taskID,降级到普通日志
|
// 上下文中没有 taskID,降级到普通日志
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type redisClientConnOpt struct {
|
|||||||
options redis.Options
|
options redis.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt redisClientConnOpt) MakeRedisClient() interface{} {
|
func (opt redisClientConnOpt) MakeRedisClient() any {
|
||||||
return redis.NewClient(&opt.options)
|
return redis.NewClient(&opt.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ type redisClusterConnOpt struct {
|
|||||||
options redis.ClusterOptions
|
options redis.ClusterOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt redisClusterConnOpt) MakeRedisClient() interface{} {
|
func (opt redisClusterConnOpt) MakeRedisClient() any {
|
||||||
return redis.NewClusterClient(&opt.options)
|
return redis.NewClusterClient(&opt.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ type redisFailoverConnOpt struct {
|
|||||||
maintNotificationsEnabled bool
|
maintNotificationsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt redisFailoverConnOpt) MakeRedisClient() interface{} {
|
func (opt redisFailoverConnOpt) MakeRedisClient() any {
|
||||||
client := redis.NewFailoverClient(&opt.options)
|
client := redis.NewFailoverClient(&opt.options)
|
||||||
// go-redis v9.16 does not expose maintenance notification settings on
|
// go-redis v9.16 does not expose maintenance notification settings on
|
||||||
// FailoverOptions, so apply the configured mode before the client is used.
|
// FailoverOptions, so apply the configured mode before the client is used.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ package analytics
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||||
@@ -367,7 +367,7 @@ func mergeNodeMetricHourlyPreferRollup(rollup, raw []NodeMetricHourly) []NodeMet
|
|||||||
}
|
}
|
||||||
result := make([]NodeMetricHourly, 0, len(order))
|
result := make([]NodeMetricHourly, 0, len(order))
|
||||||
// Keep chronological order of first-seen keys; re-sort by hour for stability.
|
// Keep chronological order of first-seen keys; re-sort by hour for stability.
|
||||||
sort.Slice(order, func(i, j int) bool { return order[i] < order[j] })
|
slices.Sort(order)
|
||||||
for _, key := range order {
|
for _, key := range order {
|
||||||
result = append(result, byHour[key])
|
result = append(result, byHour[key])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,14 +418,8 @@ func openFlareAccessLogPaginateBounds(total int, page int, pageSize int) (int, i
|
|||||||
if pageSize <= 0 {
|
if pageSize <= 0 {
|
||||||
return 0, total
|
return 0, total
|
||||||
}
|
}
|
||||||
start := page * pageSize
|
start := min(page*pageSize, total)
|
||||||
if start > total {
|
end := min(start+pageSize, total)
|
||||||
start = total
|
|
||||||
}
|
|
||||||
end := start + pageSize
|
|
||||||
if end > total {
|
|
||||||
end = total
|
|
||||||
}
|
|
||||||
return start, end
|
return start, end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func loggerMiddleware() gin.HandlerFunc {
|
|||||||
|
|
||||||
func logRequest(ctx context.Context, c *gin.Context, path string, start, end time.Time, latency time.Duration) {
|
func logRequest(ctx context.Context, c *gin.Context, path string, start, end time.Time, latency time.Duration) {
|
||||||
format := "[LoggerMiddleware] %s %s\nStartTime: %s\nEndTime: %s\nLatency: %d\nClientIP: %s\nResponse: %d %d"
|
format := "[LoggerMiddleware] %s %s\nStartTime: %s\nEndTime: %s\nLatency: %d\nClientIP: %s\nResponse: %d %d"
|
||||||
args := []interface{}{
|
args := []any{
|
||||||
c.Request.Method,
|
c.Request.Method,
|
||||||
path,
|
path,
|
||||||
start.Format(time.RFC3339),
|
start.Format(time.RFC3339),
|
||||||
@@ -89,8 +89,8 @@ func isOriginAllowed(ctx context.Context, origin string) bool {
|
|||||||
if err != nil || sc.Value == "" {
|
if err != nil || sc.Value == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
allowedOrigins := strings.Split(sc.Value, ",")
|
allowedOrigins := strings.SplitSeq(sc.Value, ",")
|
||||||
for _, allowed := range allowedOrigins {
|
for allowed := range allowedOrigins {
|
||||||
allowed = strings.TrimRight(strings.TrimSpace(allowed), "/")
|
allowed = strings.TrimRight(strings.TrimSpace(allowed), "/")
|
||||||
if allowed != "" && strings.EqualFold(allowed, origin) {
|
if allowed != "" && strings.EqualFold(allowed, origin) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ type Response[T any] struct {
|
|||||||
// Any 用于 Swagger 文档的响应类型(非泛型)
|
// Any 用于 Swagger 文档的响应类型(非泛型)
|
||||||
// swag 不支持泛型,使用此类型替代 Response[T]
|
// swag 不支持泛型,使用此类型替代 Response[T]
|
||||||
type Any struct {
|
type Any struct {
|
||||||
ErrorMsg string `json:"error_msg" example:""`
|
ErrorMsg string `json:"error_msg" example:""`
|
||||||
Data interface{} `json:"data"`
|
Data any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIError 统一的 API 业务错误类型,可被全局错误处理中间件捕获
|
// APIError 统一的 API 业务错误类型,可被全局错误处理中间件捕获
|
||||||
|
|||||||
Vendored
+2
-3
@@ -6,6 +6,7 @@ package ram
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"maps"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -119,9 +120,7 @@ func Set(item CacheItem) {
|
|||||||
currentMap, ok := managerCache.GetIfPresent(item.Type)
|
currentMap, ok := managerCache.GetIfPresent(item.Type)
|
||||||
newMap := make(map[string]cacheEntry)
|
newMap := make(map[string]cacheEntry)
|
||||||
if ok {
|
if ok {
|
||||||
for k, v := range currentMap {
|
maps.Copy(newMap, currentMap)
|
||||||
newMap[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var expireAt time.Time
|
var expireAt time.Time
|
||||||
|
|||||||
+2
-2
@@ -238,14 +238,14 @@ func VerifyChallengeSolutions(token string, solutions []int, secret []byte, expe
|
|||||||
func Solve(token string, count, size, difficulty int) []int {
|
func Solve(token string, count, size, difficulty int) []int {
|
||||||
solutions := make([]int, count)
|
solutions := make([]int, count)
|
||||||
tokenFnv := fnv1a(token)
|
tokenFnv := fnv1a(token)
|
||||||
for i := 0; i < count; i++ {
|
for i := range count {
|
||||||
idxStr := strconv.Itoa(i + 1)
|
idxStr := strconv.Itoa(i + 1)
|
||||||
saltSeed := fnv1aResume(tokenFnv, idxStr)
|
saltSeed := fnv1aResume(tokenFnv, idxStr)
|
||||||
targetSeed := fnv1aResume(saltSeed, "d")
|
targetSeed := fnv1aResume(saltSeed, "d")
|
||||||
salt := prngFromHash(saltSeed, size)
|
salt := prngFromHash(saltSeed, size)
|
||||||
target := prngFromHash(targetSeed, difficulty)
|
target := prngFromHash(targetSeed, difficulty)
|
||||||
|
|
||||||
for nonce := 0; nonce < 1000000; nonce++ {
|
for nonce := range 1000000 {
|
||||||
hashInput := salt + strconv.Itoa(nonce)
|
hashInput := salt + strconv.Itoa(nonce)
|
||||||
hashBytes := sha256.Sum256([]byte(hashInput))
|
hashBytes := sha256.Sum256([]byte(hashInput))
|
||||||
hashHex := hex.EncodeToString(hashBytes[:])
|
hashHex := hex.EncodeToString(hashBytes[:])
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func CountryCentroidByName(name string) (lat float64, lon float64, ok bool) {
|
|||||||
return v.lat, v.lon, true
|
return v.lat, v.lon, true
|
||||||
}
|
}
|
||||||
// Try comma-separated parts (city / region / country / ISO).
|
// Try comma-separated parts (city / region / country / ISO).
|
||||||
for _, part := range strings.Split(name, ",") {
|
for part := range strings.SplitSeq(name, ",") {
|
||||||
part = strings.TrimSpace(part)
|
part = strings.TrimSpace(part)
|
||||||
if part == "" {
|
if part == "" {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -78,25 +78,25 @@ func Init(cfg Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DebugF 输出 Debug 级别日志
|
// DebugF 输出 Debug 级别日志
|
||||||
func DebugF(ctx context.Context, format string, args ...interface{}) {
|
func DebugF(ctx context.Context, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
logger.Ctx(ctx).Debug(msg, getTraceIDFields(ctx)...)
|
logger.Ctx(ctx).Debug(msg, getTraceIDFields(ctx)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfoF 输出 Info 级别日志
|
// InfoF 输出 Info 级别日志
|
||||||
func InfoF(ctx context.Context, format string, args ...interface{}) {
|
func InfoF(ctx context.Context, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
logger.Ctx(ctx).Info(msg, getTraceIDFields(ctx)...)
|
logger.Ctx(ctx).Info(msg, getTraceIDFields(ctx)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarnF 输出 Warn 级别日志
|
// WarnF 输出 Warn 级别日志
|
||||||
func WarnF(ctx context.Context, format string, args ...interface{}) {
|
func WarnF(ctx context.Context, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
logger.Ctx(ctx).Warn(msg, getTraceIDFields(ctx)...)
|
logger.Ctx(ctx).Warn(msg, getTraceIDFields(ctx)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorF 输出 Error 级别日志
|
// ErrorF 输出 Error 级别日志
|
||||||
func ErrorF(ctx context.Context, format string, args ...interface{}) {
|
func ErrorF(ctx context.Context, format string, args ...any) {
|
||||||
msg := fmt.Sprintf(format, args...)
|
msg := fmt.Sprintf(format, args...)
|
||||||
logger.Ctx(ctx).Error(msg, getTraceIDFields(ctx)...)
|
logger.Ctx(ctx).Error(msg, getTraceIDFields(ctx)...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,10 +141,7 @@ func (r *LogRingBuffer) Query(cursor int, limit int) ([]LogEntry, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 返回 cut 之前的最后 limit 条
|
// 返回 cut 之前的最后 limit 条
|
||||||
start := cut - limit
|
start := max(cut-limit, 0)
|
||||||
if start < 0 {
|
|
||||||
start = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
hasMore := start > 0
|
hasMore := start > 0
|
||||||
return ordered[start:cut], hasMore
|
return ordered[start:cut], hasMore
|
||||||
|
|||||||
+10
-10
@@ -55,21 +55,21 @@ func SendMailHTML(ctx context.Context, cfg Config, to string, subject, body stri
|
|||||||
header["MIME-Version"] = "1.0"
|
header["MIME-Version"] = "1.0"
|
||||||
header["Content-Type"] = "text/html; charset=UTF-8"
|
header["Content-Type"] = "text/html; charset=UTF-8"
|
||||||
|
|
||||||
message := ""
|
var message strings.Builder
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
fmt.Fprintf(&message, "%s: %s\r\n", k, v)
|
||||||
}
|
}
|
||||||
message += "\r\n" + body
|
message.WriteString("\r\n" + body)
|
||||||
|
|
||||||
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
auth := smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.Host)
|
||||||
|
|
||||||
// If using SSL port 465, we connection via TLS dial
|
// If using SSL port 465, we connection via TLS dial
|
||||||
if cfg.Port == smtpSSLPort {
|
if cfg.Port == smtpSSLPort {
|
||||||
return sendMailViaSSL(ctx, addr, auth, cfg, to, message)
|
return sendMailViaSSL(ctx, addr, auth, cfg, to, message.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// For standard port (587 / 25), use smtp.SendMail directly (handles STARTTLS automatically if server supports it)
|
// For standard port (587 / 25), use smtp.SendMail directly (handles STARTTLS automatically if server supports it)
|
||||||
err := smtp.SendMail(addr, auth, cfg.Username, []string{to}, []byte(message))
|
err := smtp.SendMail(addr, auth, cfg.Username, []string{to}, []byte(message.String()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(errSendMailFailed, err)
|
return fmt.Errorf(errSendMailFailed, err)
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ func sendMailViaSSL(ctx context.Context, addr string, auth smtp.Auth, cfg Config
|
|||||||
// SendMailWithLog sends a test email and records a detailed SMTP connection log
|
// SendMailWithLog sends a test email and records a detailed SMTP connection log
|
||||||
func SendMailWithLog(ctx context.Context, cfg Config, to string, subject, body string) (string, error) {
|
func SendMailWithLog(ctx context.Context, cfg Config, to string, subject, body string) (string, error) {
|
||||||
var logBuf bytes.Buffer
|
var logBuf bytes.Buffer
|
||||||
logLine := func(dir string, format string, args ...interface{}) {
|
logLine := func(dir string, format string, args ...any) {
|
||||||
fmt.Fprintf(&logBuf, "[%s] %s\n", dir, fmt.Sprintf(format, args...))
|
fmt.Fprintf(&logBuf, "[%s] %s\n", dir, fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,14 +227,14 @@ func SendMailWithLog(ctx context.Context, cfg Config, to string, subject, body s
|
|||||||
header["MIME-Version"] = "1.0"
|
header["MIME-Version"] = "1.0"
|
||||||
header["Content-Type"] = "text/html; charset=UTF-8"
|
header["Content-Type"] = "text/html; charset=UTF-8"
|
||||||
|
|
||||||
message := ""
|
var message strings.Builder
|
||||||
for k, v := range header {
|
for k, v := range header {
|
||||||
message += fmt.Sprintf("%s: %s\r\n", k, v)
|
fmt.Fprintf(&message, "%s: %s\r\n", k, v)
|
||||||
}
|
}
|
||||||
message += "\r\n" + body
|
message.WriteString("\r\n" + body)
|
||||||
|
|
||||||
logLine("System", "Sending message body...")
|
logLine("System", "Sending message body...")
|
||||||
if _, err = w.Write([]byte(message)); err != nil {
|
if _, err = w.Write([]byte(message.String())); err != nil {
|
||||||
_ = w.Close()
|
_ = w.Close()
|
||||||
logLine("Error", "Writing message body failed: %v", err)
|
logLine("Error", "Writing message body failed: %v", err)
|
||||||
return logBuf.String(), err
|
return logBuf.String(), err
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func validateLogicalPathRunes(raw string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateLogicalPathSegments(raw string) error {
|
func validateLogicalPathSegments(raw string) error {
|
||||||
for _, segment := range strings.Split(raw, "/") {
|
for segment := range strings.SplitSeq(raw, "/") {
|
||||||
if len(segment) >= 2 && segment[1] == ':' {
|
if len(segment) >= 2 && segment[1] == ':' {
|
||||||
return fmt.Errorf("pages path contains a Windows drive: %s", raw)
|
return fmt.Errorf("pages path contains a Windows drive: %s", raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -414,7 +414,7 @@ func pagesFallbackPath(deployment *PagesDeployment) string {
|
|||||||
if value == "/" || strings.HasSuffix(value, "/") || strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") || strings.ContainsAny(value, " \t\r\n") {
|
if value == "/" || strings.HasSuffix(value, "/") || strings.Contains(value, "\\") || strings.ContainsAny(value, "\"';") || strings.ContainsAny(value, " \t\r\n") {
|
||||||
return indexHTML
|
return indexHTML
|
||||||
}
|
}
|
||||||
for _, segment := range strings.Split(value, "/") {
|
for segment := range strings.SplitSeq(value, "/") {
|
||||||
if segment == "." || segment == ".." {
|
if segment == "." || segment == ".." {
|
||||||
return indexHTML
|
return indexHTML
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ func ParseStatusCodeTag(tag string) (lo, hi int, err error) {
|
|||||||
if tag == "" {
|
if tag == "" {
|
||||||
return 0, 0, fmt.Errorf("状态码标签不能为空")
|
return 0, 0, fmt.Errorf("状态码标签不能为空")
|
||||||
}
|
}
|
||||||
if i := strings.IndexByte(tag, '-'); i >= 0 {
|
if before, after, ok := strings.Cut(tag, "-"); ok {
|
||||||
lo, err = strconv.Atoi(tag[:i])
|
lo, err = strconv.Atoi(before)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
||||||
}
|
}
|
||||||
hi, err = strconv.Atoi(tag[i+1:])
|
hi, err = strconv.Atoi(after)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
return 0, 0, fmt.Errorf("无效状态码区间: %s", tag)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package util
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Interface2String converts a string, int, or float64 value to its string representation.
|
// Interface2String converts a string, int, or float64 value to its string representation.
|
||||||
func Interface2String(inter interface{}) string {
|
func Interface2String(inter any) string {
|
||||||
switch v := inter.(type) {
|
switch v := inter.(type) {
|
||||||
case string:
|
case string:
|
||||||
return v
|
return v
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ package util
|
|||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
func compareVersionNumbers(left, right VersionInfo) int {
|
func compareVersionNumbers(left, right VersionInfo) int {
|
||||||
maxLen := len(left.Numbers)
|
maxLen := max(len(right.Numbers), len(left.Numbers))
|
||||||
if len(right.Numbers) > maxLen {
|
|
||||||
maxLen = len(right.Numbers)
|
|
||||||
}
|
|
||||||
for index := 0; index < maxLen; index++ {
|
for index := 0; index < maxLen; index++ {
|
||||||
leftValue := 0
|
leftValue := 0
|
||||||
rightValue := 0
|
rightValue := 0
|
||||||
@@ -37,10 +34,7 @@ func compareGitDescribeDistance(left, right VersionInfo) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compareGitDescribeTails(left, right VersionInfo) int {
|
func compareGitDescribeTails(left, right VersionInfo) int {
|
||||||
maxLen := len(left.GitDescribeTail)
|
maxLen := max(len(right.GitDescribeTail), len(left.GitDescribeTail))
|
||||||
if len(right.GitDescribeTail) > maxLen {
|
|
||||||
maxLen = len(right.GitDescribeTail)
|
|
||||||
}
|
|
||||||
for index := 0; index < maxLen; index++ {
|
for index := 0; index < maxLen; index++ {
|
||||||
if index >= len(left.GitDescribeTail) {
|
if index >= len(left.GitDescribeTail) {
|
||||||
return -1
|
return -1
|
||||||
@@ -69,10 +63,7 @@ func comparePrereleaseIdentifiers(left, right VersionInfo) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
maxLen := len(left.Prerelease)
|
maxLen := max(len(right.Prerelease), len(left.Prerelease))
|
||||||
if len(right.Prerelease) > maxLen {
|
|
||||||
maxLen = len(right.Prerelease)
|
|
||||||
}
|
|
||||||
for index := 0; index < maxLen; index++ {
|
for index := 0; index < maxLen; index++ {
|
||||||
if index >= len(left.Prerelease) {
|
if index >= len(left.Prerelease) {
|
||||||
return -1
|
return -1
|
||||||
|
|||||||
Reference in New Issue
Block a user