Compare commits

..

6 Commits

Author SHA1 Message Date
f8f647c12a update 2026-02-22 14:31:06 +08:00
d88178458d 更新 README.md 2025-12-25 21:26:34 +08:00
9b570c04c0 fix licence logic 2025-12-25 20:35:07 +08:00
5b824eac0b fix info output 2025-12-02 00:11:37 +08:00
df56cb550e fix utils.array.go 2025-11-13 21:07:34 +08:00
240d0f1e7c fix licence 2025-11-05 15:52:01 +08:00
16 changed files with 604 additions and 905 deletions

View File

@@ -11,6 +11,16 @@ go env -w GOINSECURE=git.apinb.com/*
go env -w GONOSUMDB=git.apinb.com/* go env -w GONOSUMDB=git.apinb.com/*
``` ```
### 配置环境变量
```bash
export BSM_Workspace=def
export BSM_JwtSecretKey=your_secret_key
export BSM_RuntimeMode=dev
export BSM_Prefix=/usr/local/bsm
```
## 核心功能模块 ## 核心功能模块
### 1. 服务管理 (service) ### 1. 服务管理 (service)
@@ -321,14 +331,6 @@ go env -w GONOSUMDB=git.apinb.com/*
- 支持许可证文件验证 - 支持许可证文件验证
### 配置环境变量
```bash
export BSM_Workspace=def
export BSM_JwtSecretKey=your_secret_key
export BSM_RuntimeMode=dev
export BSM_Prefix=/usr/local/bsm
```
### 安全建议 ### 安全建议

14
cache/redis/cache.go vendored
View File

@@ -5,6 +5,7 @@ package redis
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings"
"time" "time"
"git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/errcode"
@@ -15,19 +16,20 @@ import (
// prefix: 键前缀 // prefix: 键前缀
// params: 键参数 // params: 键参数
// 返回: 完整的缓存键 // 返回: 完整的缓存键
func (c *RedisClient) BuildKey(prefix string, params ...interface{}) string { func (c *RedisClient) BuildKey(prefix string, params ...any) string {
key := vars.CacheKeyPrefix + prefix var key strings.Builder
key.WriteString(vars.CacheKeyPrefix + prefix)
for _, param := range params { for _, param := range params {
key += fmt.Sprintf(":%v", param) key.WriteString(fmt.Sprintf(":%v", param))
} }
return key return key.String()
} }
// Get 获取缓存 // Get 获取缓存
// key: 缓存键 // key: 缓存键
// result: 存储结果的指针 // result: 存储结果的指针
// 返回: 错误信息 // 返回: 错误信息
func (c *RedisClient) Get(key string, result interface{}) error { func (c *RedisClient) Get(key string, result any) error {
if c.Client == nil { if c.Client == nil {
return errcode.ErrRedis return errcode.ErrRedis
} }
@@ -45,7 +47,7 @@ func (c *RedisClient) Get(key string, result interface{}) error {
// value: 缓存值 // value: 缓存值
// ttl: 过期时间 // ttl: 过期时间
// 返回: 错误信息 // 返回: 错误信息
func (c *RedisClient) Set(key string, value interface{}, ttl time.Duration) error { func (c *RedisClient) Set(key string, value any, ttl time.Duration) error {
if c.Client == nil { if c.Client == nil {
return errcode.ErrRedis return errcode.ErrRedis
} }

View File

@@ -37,6 +37,7 @@ func New(srvKey string, cfg any) {
// 配置文件不存在则读取Workspace配置文件 // 配置文件不存在则读取Workspace配置文件
if !utils.PathExists(cfp) { if !utils.PathExists(cfp) {
printer.Info("[BSM - %s] Config File: %s Not Found, Read Workspace Public Config File...", srvKey, cfp)
cfp = fmt.Sprintf("workspace_%s_%s.yaml", strings.ToLower(env.Runtime.Workspace), env.Runtime.Mode) cfp = fmt.Sprintf("workspace_%s_%s.yaml", strings.ToLower(env.Runtime.Workspace), env.Runtime.Mode)
cfp = filepath.Join(env.Runtime.Prefix, "etc", cfp) cfp = filepath.Join(env.Runtime.Prefix, "etc", cfp)
} }

View File

@@ -62,7 +62,7 @@ func (t *tokenJwt) GenerateJwt(id uint, identity, client, role string, owner any
// 解析JWT // 解析JWT
func (t *tokenJwt) ParseJwt(tokenstring string) (*Claims, error) { func (t *tokenJwt) ParseJwt(tokenstring string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (any, error) {
return []byte(t.SecretKey), nil return []byte(t.SecretKey), nil
}) })
if claims, ok := token.Claims.(*Claims); ok && token.Valid { if claims, ok := token.Claims.(*Claims); ok && token.Valid {

View File

@@ -44,7 +44,7 @@ func NewElastic(endpoints []string, username, password string) (*ES, error) {
// "time": time.Now().Unix(), // "time": time.Now().Unix(),
// "date": time.Now(), // "date": time.Now(),
// } // }
func (es *ES) CreateDocument(index string, id string, doc *interface{}) { func (es *ES) CreateDocument(index string, id string, doc *any) {
var buf bytes.Buffer var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(doc); err != nil { if err := json.NewEncoder(&buf).Encode(doc); err != nil {
log.Println("Elastic NewEncoder:", err) log.Println("Elastic NewEncoder:", err)
@@ -75,7 +75,7 @@ func (es *ES) CreateDocument(index string, id string, doc *interface{}) {
// index 如果文档不存在就创建,如果文档存在就更新 // index 如果文档不存在就创建,如果文档存在就更新
// update 更新一个文档,如果文档不存在就返回错误 // update 更新一个文档,如果文档不存在就返回错误
// delete 删除一个文档如果要删除的文档id不存在就返回错误 // delete 删除一个文档如果要删除的文档id不存在就返回错误
func (es *ES) Batch(index string, documens []map[string]interface{}, action string) { func (es *ES) Batch(index string, documens []map[string]any, action string) {
log.SetFlags(0) log.SetFlags(0)
var ( var (
@@ -162,7 +162,7 @@ func (es *ES) Batch(index string, documens []map[string]interface{}, action stri
} }
} }
func (es *ES) Search(index string, query map[string]interface{}) (res *esapi.Response, err error) { func (es *ES) Search(index string, query map[string]any) (res *esapi.Response, err error) {
var buf bytes.Buffer var buf bytes.Buffer
if err = json.NewEncoder(&buf).Encode(query); err != nil { if err = json.NewEncoder(&buf).Encode(query); err != nil {
return return
@@ -201,7 +201,7 @@ func (es *ES) Delete(index, idx string) (res *esapi.Response, err error) {
return return
} }
func (es *ES) DeleteByQuery(index []string, query map[string]interface{}) (res *esapi.Response, err error) { func (es *ES) DeleteByQuery(index []string, query map[string]any) (res *esapi.Response, err error) {
var buf bytes.Buffer var buf bytes.Buffer
if err = json.NewEncoder(&buf).Encode(query); err != nil { if err = json.NewEncoder(&buf).Encode(query); err != nil {
return return

91
go.mod
View File

@@ -1,92 +1,3 @@
module git.apinb.com/bsm-sdk/core module git.apinb.com/bsm-sdk/core
go 1.25.1 go 1.26.0
require (
github.com/allegro/bigcache/v3 v3.1.0
github.com/elastic/go-elasticsearch/v9 v9.2.0
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3
github.com/nats-io/nats.go v1.47.0
github.com/oklog/ulid/v2 v2.1.1
github.com/redis/go-redis/v9 v9.16.0
github.com/shirou/gopsutil v3.21.11+incompatible
go.etcd.io/etcd/client/pkg/v3 v3.6.5
go.etcd.io/etcd/client/v3 v3.6.5
google.golang.org/grpc v1.76.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.31.1
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.6.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/nkeys v0.4.11 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/tklauser/go-sysconf v0.3.15 // indirect
github.com/tklauser/numcpus v0.10.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/protobuf v1.36.10 // indirect
)

243
go.sum
View File

@@ -1,243 +0,0 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE=
github.com/elastic/elastic-transport-go/v8 v8.7.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v9 v9.2.0 h1:COeL/g20+ixnUbffe4Wfbu88emrHjAq/LhVfmrjqRQs=
github.com/elastic/go-elasticsearch/v9 v9.2.0/go.mod h1:2PB5YQPpY5tWbF65MRqzEXA31PZOdXCkloQSOZtU14I=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0=
github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/redis/go-redis/v9 v9.16.0 h1:OotgqgLSRCmzfqChbQyG1PHC3tLNR89DG4jdOERSEP4=
github.com/redis/go-redis/v9 v9.16.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA=
go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ=
go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8=
go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk=
go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U=
go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

View File

@@ -13,6 +13,8 @@ import (
"net" "net"
"os" "os"
"path" "path"
"path/filepath"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -42,45 +44,61 @@ type MachineInfo struct {
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 授权信息 // 授权信息
type Licence struct { type Licence struct {
CompanyName string `json:"licence_to"` // 授权公司 CompanyName string `json:"licence_to"` // 授权公司
CreateDate int `json:"create"` // 生效日期 CreateDate int `json:"create"` // 生效日期
ExpireDate int `json:"expire"` // 有效期 ExpireDate int `json:"expire"` // 有效期
MachineCodes []string `json:"machine_codes"` // 机器码列表 MachineCodes []string `json:"machine_codes"` // 机器码列表
Args map[string]string `json:"args"` // 许可证相关参数定义
} }
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
var ( var (
des_key string des_key string
des_iv string des_iv string
args map[string]string
Check_Licence_File bool = true // 是否检查部署授权文件 Check_Licence_File bool = true // 是否检查部署授权文件
) )
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
const ( const (
signKey = "1F36659EC27CFFF849E068EA80B1A4CA" SignKey = "1F36659EC27CFFF849E068EA80B1A4CA"
LICENCE_KEY = "BLOCKS_KEY" LicenceKey = "BLOCKS_KEY"
) )
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
func init() { func init() {
des_key = base64.StdEncoding.EncodeToString([]byte(signKey)) des_key = base64.StdEncoding.EncodeToString([]byte(SignKey))
des_iv = base64.StdEncoding.EncodeToString([]byte(signKey[:16])) des_iv = base64.StdEncoding.EncodeToString([]byte(SignKey[:16]))
} }
func WatchCheckLicence(licPath, licName string) { func WatchCheckLicence(licPath, licName string) {
utils.SetInterval(func() { utils.SetInterval(func() {
if CheckLicence(licPath, licName) == false { if !CheckLicence(licPath, licName) {
log.Println("授权文件失效,请重新部署授权文件:", licPath) exit()
os.Exit(99)
} }
}, time.Hour*1) }, time.Hour*1)
} }
func GetArgs(licPath, licName string) map[string]string {
if !CheckLicence(licPath, licName) {
exit()
}
return args
}
func exit() {
log.Println("授权文件失效,请重新部署授权文件!")
os.Exit(99)
}
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
func CheckLicence(licPath, licName string) bool { func CheckLicence(licPath, licName string) bool {
// 加载授权文件 // 加载授权文件
if licName == "" {
licName = "licence.key"
}
licPath = filepath.Join(licPath, licName)
content, err := LoadLicenceFromFile(licPath) content, err := LoadLicenceFromFile(licPath)
if err != nil { if err != nil {
return false return false
@@ -92,17 +110,14 @@ func CheckLicence(licPath, licName string) bool {
return false return false
} }
args = l.Args
return l.VerifyLicence(licName) return l.VerifyLicence(licName)
} }
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Licence 校验 // Licence 校验
func (l *Licence) VerifyLicence(licName string) bool { func (l *Licence) VerifyLicence(licName string) bool {
// 用于开发环境,为授权公司时跳过验证
if l.CompanyName == licName {
return true
}
today := StrToInt(time.Now().Format("20060102")) today := StrToInt(time.Now().Format("20060102"))
// 机器日期不在授权文件有限期之内 (早于生效日期,或超过有效期) // 机器日期不在授权文件有限期之内 (早于生效日期,或超过有效期)
if (today < l.CreateDate) || (today > l.ExpireDate) { if (today < l.CreateDate) || (today > l.ExpireDate) {
@@ -121,13 +136,7 @@ func (l *Licence) VerifyLicence(licName string) bool {
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 检查机器码是否存在授权列表中 // 检查机器码是否存在授权列表中
func (l *Licence) ValidMachineCode(code string) bool { func (l *Licence) ValidMachineCode(code string) bool {
result := false result := slices.Contains(l.MachineCodes, code)
for _, c := range l.MachineCodes {
if c == code {
result = true
break
}
}
return result return result
} }
@@ -135,7 +144,7 @@ func (l *Licence) ValidMachineCode(code string) bool {
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 加载授权文件 // 加载授权文件
func LoadLicenceFromFile(licPath string) (string, error) { func LoadLicenceFromFile(licPath string) (string, error) {
key_path := path.Join(licPath, "licence.key") key_path := path.Join(licPath)
if utils.PathExists(key_path) { if utils.PathExists(key_path) {
file, err := os.Open(key_path) file, err := os.Open(key_path)
if err != nil { if err != nil {
@@ -195,13 +204,14 @@ func EncryptStr(txt string) string {
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 生成授权文件 // 生成授权文件
func BuildLicence(company_name string, Create_date int, expire_date int, machine_codes []string) (string, error) { func BuildLicence(company_name string, Create_date int, expire_date int, machine_codes []string, args map[string]string) (string, error) {
// 构造licence // 构造licence
licence := &Licence{ licence := &Licence{
CompanyName: company_name, CompanyName: company_name,
CreateDate: Create_date, CreateDate: Create_date,
ExpireDate: expire_date, ExpireDate: expire_date,
MachineCodes: machine_codes, MachineCodes: machine_codes,
Args: args,
} }
content, err := json.Marshal(licence) content, err := json.Marshal(licence)
@@ -216,7 +226,7 @@ func BuildLicence(company_name string, Create_date int, expire_date int, machine
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 生成激活码 // 生成激活码
func GetLicenceCode(machinceCode string) string { func GetLicenceCode(machinceCode string) string {
return hash256(machinceCode + "&" + signKey) return hash256(machinceCode + "&" + SignKey)
} }
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -250,7 +260,7 @@ func getMacAddrs() []string {
if err != nil { if err != nil {
return macs return macs
} }
for i := 0; i < len(netfaces); i++ { for i := range netfaces {
if (netfaces[i].Flags&net.FlagUp) != 0 && (netfaces[i].Flags&net.FlagLoopback) == 0 { if (netfaces[i].Flags&net.FlagUp) != 0 && (netfaces[i].Flags&net.FlagLoopback) == 0 {
addrs, _ := netfaces[i].Addrs() addrs, _ := netfaces[i].Addrs()
for _, address := range addrs { for _, address := range addrs {

View File

@@ -159,7 +159,7 @@ func (l *Logger) sendToRemote(level, name, out string) {
if l.endpoint == "" { if l.endpoint == "" {
return return
} }
data := map[string]interface{}{ data := map[string]any{
"level": level, "level": level,
"name": name, "name": name,
"out": out, "out": out,
@@ -169,7 +169,7 @@ func (l *Logger) sendToRemote(level, name, out string) {
} }
// Debug 输出调试信息 // Debug 输出调试信息
func (l *Logger) Debug(v ...interface{}) { func (l *Logger) Debug(v ...any) {
if l.level <= vars.DEBUG { if l.level <= vars.DEBUG {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprint(v...) out := fmt.Sprint(v...)
@@ -181,7 +181,7 @@ func (l *Logger) Debug(v ...interface{}) {
} }
// Debugf 格式化输出调试信息 // Debugf 格式化输出调试信息
func (l *Logger) Debugf(format string, v ...interface{}) { func (l *Logger) Debugf(format string, v ...any) {
if l.level <= vars.DEBUG { if l.level <= vars.DEBUG {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprintf(format, v...) out := fmt.Sprintf(format, v...)
@@ -193,7 +193,7 @@ func (l *Logger) Debugf(format string, v ...interface{}) {
} }
// Info 输出信息 // Info 输出信息
func (l *Logger) Info(v ...interface{}) { func (l *Logger) Info(v ...any) {
if l.level <= vars.INFO { if l.level <= vars.INFO {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprint(v...) out := fmt.Sprint(v...)
@@ -205,7 +205,7 @@ func (l *Logger) Info(v ...interface{}) {
} }
// Infof 格式化输出信息 // Infof 格式化输出信息
func (l *Logger) Infof(format string, v ...interface{}) { func (l *Logger) Infof(format string, v ...any) {
if l.level <= vars.INFO { if l.level <= vars.INFO {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprintf(format, v...) out := fmt.Sprintf(format, v...)
@@ -217,7 +217,7 @@ func (l *Logger) Infof(format string, v ...interface{}) {
} }
// Warn 输出警告 // Warn 输出警告
func (l *Logger) Warn(v ...interface{}) { func (l *Logger) Warn(v ...any) {
if l.level <= vars.WARN { if l.level <= vars.WARN {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprint(v...) out := fmt.Sprint(v...)
@@ -229,7 +229,7 @@ func (l *Logger) Warn(v ...interface{}) {
} }
// Warnf 格式化输出警告 // Warnf 格式化输出警告
func (l *Logger) Warnf(format string, v ...interface{}) { func (l *Logger) Warnf(format string, v ...any) {
if l.level <= vars.WARN { if l.level <= vars.WARN {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprintf(format, v...) out := fmt.Sprintf(format, v...)
@@ -241,7 +241,7 @@ func (l *Logger) Warnf(format string, v ...interface{}) {
} }
// Error 输出错误 // Error 输出错误
func (l *Logger) Error(v ...interface{}) { func (l *Logger) Error(v ...any) {
if l.level <= vars.ERROR { if l.level <= vars.ERROR {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprint(v...) out := fmt.Sprint(v...)
@@ -253,7 +253,7 @@ func (l *Logger) Error(v ...interface{}) {
} }
// Errorf 格式化输出错误 // Errorf 格式化输出错误
func (l *Logger) Errorf(format string, v ...interface{}) { func (l *Logger) Errorf(format string, v ...any) {
if l.level <= vars.ERROR { if l.level <= vars.ERROR {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprintf(format, v...) out := fmt.Sprintf(format, v...)
@@ -265,7 +265,7 @@ func (l *Logger) Errorf(format string, v ...interface{}) {
} }
// Fatal 输出致命错误并退出程序 // Fatal 输出致命错误并退出程序
func (l *Logger) Fatal(v ...interface{}) { func (l *Logger) Fatal(v ...any) {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprint(v...) out := fmt.Sprint(v...)
if l.onRemote { if l.onRemote {
@@ -276,7 +276,7 @@ func (l *Logger) Fatal(v ...interface{}) {
} }
// Fatalf 格式化输出致命错误并退出程序 // Fatalf 格式化输出致命错误并退出程序
func (l *Logger) Fatalf(format string, v ...interface{}) { func (l *Logger) Fatalf(format string, v ...any) {
l.checkAndRotateLog() l.checkAndRotateLog()
out := fmt.Sprintf(format, v...) out := fmt.Sprintf(format, v...)
if l.onRemote { if l.onRemote {
@@ -287,17 +287,17 @@ func (l *Logger) Fatalf(format string, v ...interface{}) {
} }
// Print 输出信息兼容标准log包 // Print 输出信息兼容标准log包
func (l *Logger) Print(v ...interface{}) { func (l *Logger) Print(v ...any) {
l.Info(v...) l.Info(v...)
} }
// Printf 格式化输出信息兼容标准log包 // Printf 格式化输出信息兼容标准log包
func (l *Logger) Printf(format string, v ...interface{}) { func (l *Logger) Printf(format string, v ...any) {
l.Infof(format, v...) l.Infof(format, v...)
} }
// Println 输出信息并换行兼容标准log包 // Println 输出信息并换行兼容标准log包
func (l *Logger) Println(v ...interface{}) { func (l *Logger) Println(v ...any) {
l.Info(v...) l.Info(v...)
} }
@@ -326,91 +326,91 @@ func (l *Logger) Close() error {
// 全局日志函数兼容标准log包 // 全局日志函数兼容标准log包
// Debug 全局调试日志 // Debug 全局调试日志
func Debug(v ...interface{}) { func Debug(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Debug(v...) globalLogger.Debug(v...)
} }
} }
// Debugf 全局调试日志 // Debugf 全局调试日志
func Debugf(format string, v ...interface{}) { func Debugf(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Debugf(format, v...) globalLogger.Debugf(format, v...)
} }
} }
// Info 全局信息日志 // Info 全局信息日志
func Info(v ...interface{}) { func Info(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Info(v...) globalLogger.Info(v...)
} }
} }
// Infof 全局信息日志 // Infof 全局信息日志
func Infof(format string, v ...interface{}) { func Infof(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Infof(format, v...) globalLogger.Infof(format, v...)
} }
} }
// Warn 全局警告日志 // Warn 全局警告日志
func Warn(v ...interface{}) { func Warn(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Warn(v...) globalLogger.Warn(v...)
} }
} }
// Warnf 全局警告日志 // Warnf 全局警告日志
func Warnf(format string, v ...interface{}) { func Warnf(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Warnf(format, v...) globalLogger.Warnf(format, v...)
} }
} }
// Error 全局错误日志 // Error 全局错误日志
func Error(v ...interface{}) { func Error(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Error(v...) globalLogger.Error(v...)
} }
} }
// Errorf 全局错误日志 // Errorf 全局错误日志
func Errorf(format string, v ...interface{}) { func Errorf(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Errorf(format, v...) globalLogger.Errorf(format, v...)
} }
} }
// Fatal 全局致命错误日志 // Fatal 全局致命错误日志
func Fatal(v ...interface{}) { func Fatal(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Fatal(v...) globalLogger.Fatal(v...)
} }
} }
// Fatalf 全局致命错误日志 // Fatalf 全局致命错误日志
func Fatalf(format string, v ...interface{}) { func Fatalf(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Fatalf(format, v...) globalLogger.Fatalf(format, v...)
} }
} }
// Print 全局打印日志兼容标准log包 // Print 全局打印日志兼容标准log包
func Print(v ...interface{}) { func Print(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Print(v...) globalLogger.Print(v...)
} }
} }
// Printf 全局打印日志兼容标准log包 // Printf 全局打印日志兼容标准log包
func Printf(format string, v ...interface{}) { func Printf(format string, v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Printf(format, v...) globalLogger.Printf(format, v...)
} }
} }
// Println 全局打印日志兼容标准log包 // Println 全局打印日志兼容标准log包
func Println(v ...interface{}) { func Println(v ...any) {
if globalLogger != nil { if globalLogger != nil {
globalLogger.Println(v...) globalLogger.Println(v...)
} }

View File

@@ -16,25 +16,25 @@ func init() {
} }
// record INFO message. Color White // record INFO message. Color White
func Info(format string, a ...interface{}) { func Info(format string, a ...any) {
message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...) message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...)
logger.Print(message) logger.Print(message)
} }
// record Warn message. Color Orange // record Warn message. Color Orange
func Warn(format string, a ...interface{}) { func Warn(format string, a ...any) {
message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...) message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...)
logger.Print(message) logger.Print(message)
} }
// record Success message. Color Green // record Success message. Color Green
func Success(format string, a ...interface{}) { func Success(format string, a ...any) {
message := fmt.Sprintf("\033[32m[Succ] "+format+"\033[0m\n", a...) message := fmt.Sprintf("\033[32m[Succ] "+format+"\033[0m\n", a...)
logger.Print(message) logger.Print(message)
} }
// record ERROR message. Color Red // record ERROR message. Color Red
func Error(format string, a ...interface{}) { func Error(format string, a ...any) {
message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...) message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...)
logger.Print(message) logger.Print(message)
} }

View File

@@ -56,7 +56,7 @@ func WeChat_Pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
if n == 0 || n > len(data) { if n == 0 || n > len(data) {
return nil, ErrInvalidPKCS7Padding return nil, ErrInvalidPKCS7Padding
} }
for i := 0; i < n; i++ { for i := range n {
if data[len(data)-n+i] != c { if data[len(data)-n+i] != c {
return nil, ErrInvalidPKCS7Padding return nil, ErrInvalidPKCS7Padding
} }

View File

@@ -1,38 +1,54 @@
package utils package utils
import "slices"
import "strings" import "strings"
func In(target string, array []string) bool { // ArrayInString 判断字符串是否存在于字符串切片中
target = strings.Trim(target, "") // target: 待匹配的目标字符串
for _, v := range array { // array: 需要查找的字符串切片
if strings.Trim(v, "") == target { func ArrayInString(target string, array []string) bool {
return true target = strings.TrimSpace(target)
} return slices.Contains(array, target)
}
return false
} }
// 字符串数组是否存在 // ArrayInInt 判断整数是否存在于整型切片中
func StrArrayIndex(items []string, item string) int { // target: 待匹配的目标整数
for i, eachItem := range items { // array: 需要查找的整型切片
if eachItem == item { func ArrayInInt(target int, array []int) bool {
return i return slices.Contains(array, target)
}
}
return -1
} }
func RemoveRepeatSlice(in []string) (out []string) { // ArrayRemoveRepeatString 去除字符串切片中的重复元素(保持原有顺序)
_map := make(map[string]bool) // in: 原始字符串切片
for i := 0; i < len(in); i++ { // 返回: 去重后的字符串切片
if in[i] != "" { func ArrayRemoveRepeatString(in []string) (out []string) {
_map[in[i]] = true seen := make(map[string]struct{})
for _, v := range in {
trimmed := strings.TrimSpace(v)
if trimmed == "" {
continue
} }
if _, ok := seen[trimmed]; ok {
continue
}
seen[trimmed] = struct{}{}
out = append(out, trimmed)
}
return out
}
// ArrayRemoveRepeatInt 去除整型切片中的重复元素(保持原有顺序)
// in: 原始整型切片
// 返回: 去重后的整型切片
func ArrayRemoveRepeatInt(in []int) (out []int) {
seen := make(map[int]struct{})
for _, v := range in {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
} }
for key, _ := range _map {
out = append(out, key)
}
return out return out
} }

View File

@@ -103,7 +103,7 @@ func BinaryToDecimal(bit string) (num int) {
fields := strings.Split(bit, "") fields := strings.Split(bit, "")
lens := len(fields) lens := len(fields)
var tempF float64 = 0 var tempF float64 = 0
for i := 0; i < lens; i++ { for i := range lens {
floatNum := String2Float64(fields[i]) floatNum := String2Float64(fields[i])
tempF += floatNum * math.Pow(2, float64(lens-i-1)) tempF += floatNum * math.Pow(2, float64(lens-i-1))
} }

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
) )
func If(condition bool, trueValue, falseValue interface{}) interface{} { func If(condition bool, trueValue, falseValue any) any {
if condition { if condition {
return trueValue return trueValue
} }
@@ -21,8 +21,8 @@ func FirstToUpper(str string) string {
return strings.ToUpper(str[:1]) + strings.ToLower(str[1:]) return strings.ToUpper(str[:1]) + strings.ToLower(str[1:])
} }
func ParseParams(in map[string]string) map[string]interface{} { func ParseParams(in map[string]string) map[string]any {
out := make(map[string]interface{}) out := make(map[string]any)
for k, v := range in { for k, v := range in {
fv, err := strconv.ParseFloat(v, 64) fv, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {

View File

@@ -1,457 +1,457 @@
// Package utils 提供通用工具函数 // Package utils 提供通用工具函数
// //
// 二维码生成功能模块 // 二维码生成功能模块
// //
// 本模块提供了完整的二维码生成功能,支持: // 本模块提供了完整的二维码生成功能,支持:
// - 基础二维码生成保存为PNG文件 // - 基础二维码生成保存为PNG文件
// - 生成字节数组可用于HTTP响应、数据库存储等 // - 生成字节数组可用于HTTP响应、数据库存储等
// - Base64编码输出便于存储和传输 // - Base64编码输出便于存储和传输
// - Data URL格式可直接用于HTML <img>标签) // - Data URL格式可直接用于HTML <img>标签)
// - 自定义配置(尺寸、颜色、纠错级别) // - 自定义配置(尺寸、颜色、纠错级别)
// - 带Logo的二维码 // - 带Logo的二维码
// - 批量生成二维码 // - 批量生成二维码
package utils package utils
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"image/png" "image/png"
"os" "os"
"github.com/skip2/go-qrcode" "github.com/skip2/go-qrcode"
) )
// QRCodeErrorLevel 二维码纠错级别 // QRCodeErrorLevel 二维码纠错级别
// //
// 纠错级别决定了二维码能容忍多少损坏: // 纠错级别决定了二维码能容忍多少损坏:
// - 级别越高,容错能力越强,但二维码会更复杂 // - 级别越高,容错能力越强,但二维码会更复杂
// - 添加Logo时建议使用高级纠错 // - 添加Logo时建议使用高级纠错
// - 一般场景使用中级纠错即可 // - 一般场景使用中级纠错即可
type QRCodeErrorLevel int type QRCodeErrorLevel int
const ( const (
// QRCodeErrorLevelLow 低级纠错约7%容错) // QRCodeErrorLevelLow 低级纠错约7%容错)
// 适用场景:环境良好、追求小尺寸、内容较少 // 适用场景:环境良好、追求小尺寸、内容较少
QRCodeErrorLevelLow QRCodeErrorLevel = iota QRCodeErrorLevelLow QRCodeErrorLevel = iota
// QRCodeErrorLevelMedium 中级纠错约15%容错) // QRCodeErrorLevelMedium 中级纠错约15%容错)
// 适用场景:通用场景(默认推荐) // 适用场景:通用场景(默认推荐)
QRCodeErrorLevelMedium QRCodeErrorLevelMedium
// QRCodeErrorLevelQuartile 较高级纠错约25%容错) // QRCodeErrorLevelQuartile 较高级纠错约25%容错)
// 适用场景:需要较高容错能力 // 适用场景:需要较高容错能力
QRCodeErrorLevelQuartile QRCodeErrorLevelQuartile
// QRCodeErrorLevelHigh 高级纠错约30%容错) // QRCodeErrorLevelHigh 高级纠错约30%容错)
// 适用场景添加Logo、容易损坏的环境、长期使用 // 适用场景添加Logo、容易损坏的环境、长期使用
QRCodeErrorLevelHigh QRCodeErrorLevelHigh
) )
const ( const (
// DefaultQRCodeSize 默认二维码尺寸(像素) // DefaultQRCodeSize 默认二维码尺寸(像素)
DefaultQRCodeSize = 256 DefaultQRCodeSize = 256
// MinQRCodeSize 最小二维码尺寸 // MinQRCodeSize 最小二维码尺寸
MinQRCodeSize = 21 MinQRCodeSize = 21
// MaxQRCodeSize 最大二维码尺寸 // MaxQRCodeSize 最大二维码尺寸
MaxQRCodeSize = 8192 MaxQRCodeSize = 8192
) )
// QRCodeConfig 二维码配置结构 // QRCodeConfig 二维码配置结构
// //
// 用于自定义二维码的外观和质量参数 // 用于自定义二维码的外观和质量参数
// //
// 示例: // 示例:
// //
// config := &QRCodeConfig{ // config := &QRCodeConfig{
// Size: 512, // 尺寸512x512像素 // Size: 512, // 尺寸512x512像素
// ErrorLevel: QRCodeErrorLevelHigh, // 高级纠错 // ErrorLevel: QRCodeErrorLevelHigh, // 高级纠错
// ForegroundColor: color.RGBA{255, 0, 0, 255}, // 红色二维码 // ForegroundColor: color.RGBA{255, 0, 0, 255}, // 红色二维码
// BackgroundColor: color.White, // 白色背景 // BackgroundColor: color.White, // 白色背景
// } // }
type QRCodeConfig struct { type QRCodeConfig struct {
Size int // 尺寸像素范围21-8192 Size int // 尺寸像素范围21-8192
ErrorLevel QRCodeErrorLevel // 纠错级别,影响容错能力和复杂度 ErrorLevel QRCodeErrorLevel // 纠错级别,影响容错能力和复杂度
ForegroundColor color.Color // 前景色(二维码颜色),建议使用深色 ForegroundColor color.Color // 前景色(二维码颜色),建议使用深色
BackgroundColor color.Color // 背景色,建议使用浅色以保证对比度 BackgroundColor color.Color // 背景色,建议使用浅色以保证对比度
} }
// DefaultQRCodeConfig 返回默认配置 // DefaultQRCodeConfig 返回默认配置
// //
// 默认配置适用于大多数场景: // 默认配置适用于大多数场景:
// - 尺寸256x256像素适合手机扫描 // - 尺寸256x256像素适合手机扫描
// - 纠错级别中级15%容错) // - 纠错级别中级15%容错)
// - 颜色:黑白配色(最佳识别率) // - 颜色:黑白配色(最佳识别率)
// //
// 返回值: // 返回值:
// //
// *QRCodeConfig: 默认配置对象 // *QRCodeConfig: 默认配置对象
func DefaultQRCodeConfig() *QRCodeConfig { func DefaultQRCodeConfig() *QRCodeConfig {
return &QRCodeConfig{ return &QRCodeConfig{
Size: DefaultQRCodeSize, Size: DefaultQRCodeSize,
ErrorLevel: QRCodeErrorLevelMedium, ErrorLevel: QRCodeErrorLevelMedium,
ForegroundColor: color.Black, ForegroundColor: color.Black,
BackgroundColor: color.White, BackgroundColor: color.White,
} }
} }
// convertErrorLevel 转换纠错级别为底层库的纠错级别 // convertErrorLevel 转换纠错级别为底层库的纠错级别
// //
// 将自定义的纠错级别枚举转换为 go-qrcode 库所需的格式 // 将自定义的纠错级别枚举转换为 go-qrcode 库所需的格式
// //
// 参数: // 参数:
// //
// level: 自定义纠错级别 // level: 自定义纠错级别
// //
// 返回值: // 返回值:
// //
// qrcode.RecoveryLevel: go-qrcode库的纠错级别 // qrcode.RecoveryLevel: go-qrcode库的纠错级别
func convertErrorLevel(level QRCodeErrorLevel) qrcode.RecoveryLevel { func convertErrorLevel(level QRCodeErrorLevel) qrcode.RecoveryLevel {
switch level { switch level {
case QRCodeErrorLevelLow: case QRCodeErrorLevelLow:
return qrcode.Low return qrcode.Low
case QRCodeErrorLevelMedium: case QRCodeErrorLevelMedium:
return qrcode.Medium return qrcode.Medium
case QRCodeErrorLevelQuartile: case QRCodeErrorLevelQuartile:
return qrcode.High return qrcode.High
case QRCodeErrorLevelHigh: case QRCodeErrorLevelHigh:
return qrcode.Highest return qrcode.Highest
default: default:
return qrcode.Medium return qrcode.Medium
} }
} }
// GenerateQRCode 生成二维码并保存为PNG文件 // GenerateQRCode 生成二维码并保存为PNG文件
// //
// 这是最简单的二维码生成方法,适合快速生成标准黑白二维码。 // 这是最简单的二维码生成方法,适合快速生成标准黑白二维码。
// 使用中级纠错黑白配色PNG格式输出。 // 使用中级纠错黑白配色PNG格式输出。
// //
// 参数: // 参数:
// //
// content: 二维码内容支持URL、文本、vCard、WiFi等格式 // content: 二维码内容支持URL、文本、vCard、WiFi等格式
// filename: 保存的文件路径(.png文件 // filename: 保存的文件路径(.png文件
// size: 二维码尺寸(可选,单位:像素) // size: 二维码尺寸(可选,单位:像素)
// - 不传参数使用默认256x256 // - 不传参数使用默认256x256
// - 传一个参数:使用指定尺寸 // - 传一个参数:使用指定尺寸
// - 有效范围21-8192像素 // - 有效范围21-8192像素
// //
// 返回值: // 返回值:
// //
// error: 错误信息成功时返回nil // error: 错误信息成功时返回nil
// //
// 注意事项: // 注意事项:
// - 内容越长,二维码越复杂,建议尺寸>=256 // - 内容越长,二维码越复杂,建议尺寸>=256
// - 文件权限为0644 // - 文件权限为0644
// - 会覆盖已存在的同名文件 // - 会覆盖已存在的同名文件
func GenerateQRCode(content, filename string, size ...int) error { func GenerateQRCode(content, filename string, size ...int) error {
qrSize := DefaultQRCodeSize qrSize := DefaultQRCodeSize
if len(size) > 0 { if len(size) > 0 {
qrSize = size[0] qrSize = size[0]
if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize {
return fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) return fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize)
} }
} }
err := qrcode.WriteFile(content, qrcode.Medium, qrSize, filename) err := qrcode.WriteFile(content, qrcode.Medium, qrSize, filename)
if err != nil { if err != nil {
return fmt.Errorf("生成二维码失败: %w", err) return fmt.Errorf("生成二维码失败: %w", err)
} }
return nil return nil
} }
// GenerateQRCodeBytes 生成二维码字节数组PNG格式 // GenerateQRCodeBytes 生成二维码字节数组PNG格式
// //
// 生成二维码的字节数组而不保存到文件,适合: // 生成二维码的字节数组而不保存到文件,适合:
// - HTTP响应直接返回图片 // - HTTP响应直接返回图片
// - 存储到数据库 // - 存储到数据库
// - 通过网络传输 // - 通过网络传输
// - 进一步处理如添加到PDF // - 进一步处理如添加到PDF
// //
// 参数: // 参数:
// //
// content: 二维码内容 // content: 二维码内容
// size: 二维码尺寸可选默认256 // size: 二维码尺寸可选默认256
// //
// 返回值: // 返回值:
// //
// []byte: PNG格式的图片字节数组 // []byte: PNG格式的图片字节数组
// error: 错误信息 // error: 错误信息
func GenerateQRCodeBytes(content string, size ...int) ([]byte, error) { func GenerateQRCodeBytes(content string, size ...int) ([]byte, error) {
qrSize := DefaultQRCodeSize qrSize := DefaultQRCodeSize
if len(size) > 0 { if len(size) > 0 {
qrSize = size[0] qrSize = size[0]
if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize {
return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize)
} }
} }
bytes, err := qrcode.Encode(content, qrcode.Medium, qrSize) bytes, err := qrcode.Encode(content, qrcode.Medium, qrSize)
if err != nil { if err != nil {
return nil, fmt.Errorf("生成二维码失败: %w", err) return nil, fmt.Errorf("生成二维码失败: %w", err)
} }
return bytes, nil return bytes, nil
} }
// GenerateQRCodeBase64 生成Base64编码的二维码字符串 // GenerateQRCodeBase64 生成Base64编码的二维码字符串
// //
// 将二维码图片编码为Base64字符串便于 // 将二维码图片编码为Base64字符串便于
// - 存储到数据库的文本字段 // - 存储到数据库的文本字段
// - 通过JSON/XML传输 // - 通过JSON/XML传输
// - 避免二进制数据处理问题 // - 避免二进制数据处理问题
// //
// 参数: // 参数:
// //
// content: 二维码内容 // content: 二维码内容
// size: 二维码尺寸可选默认256 // size: 二维码尺寸可选默认256
// //
// 返回值: // 返回值:
// //
// string: Base64编码的字符串不包含data:image/png;base64,前缀) // string: Base64编码的字符串不包含data:image/png;base64,前缀)
// error: 错误信息 // error: 错误信息
func GenerateQRCodeBase64(content string, size ...int) (string, error) { func GenerateQRCodeBase64(content string, size ...int) (string, error) {
qrBytes, err := GenerateQRCodeBytes(content, size...) qrBytes, err := GenerateQRCodeBytes(content, size...)
if err != nil { if err != nil {
return "", err return "", err
} }
base64Str := base64.StdEncoding.EncodeToString(qrBytes) base64Str := base64.StdEncoding.EncodeToString(qrBytes)
return base64Str, nil return base64Str, nil
} }
// GenerateQRCodeWithConfig 使用自定义配置生成二维码 // GenerateQRCodeWithConfig 使用自定义配置生成二维码
// //
// 提供完全自定义的二维码生成能力,可以控制: // 提供完全自定义的二维码生成能力,可以控制:
// - 尺寸大小 // - 尺寸大小
// - 纠错级别 // - 纠错级别
// - 前景色和背景色 // - 前景色和背景色
// //
// 参数: // 参数:
// //
// content: 二维码内容 // content: 二维码内容
// config: 二维码配置对象nil时使用默认配置 // config: 二维码配置对象nil时使用默认配置
// //
// 返回值: // 返回值:
// //
// []byte: PNG格式的字节数组 // []byte: PNG格式的字节数组
// error: 错误信息 // error: 错误信息
// //
// 注意事项: // 注意事项:
// - 确保前景色和背景色有足够对比度 // - 确保前景色和背景色有足够对比度
// - 浅色前景配深色背景可能影响扫描 // - 浅色前景配深色背景可能影响扫描
func GenerateQRCodeWithConfig(content string, config *QRCodeConfig) ([]byte, error) { func GenerateQRCodeWithConfig(content string, config *QRCodeConfig) ([]byte, error) {
if config == nil { if config == nil {
config = DefaultQRCodeConfig() config = DefaultQRCodeConfig()
} }
// 验证尺寸 // 验证尺寸
if config.Size < MinQRCodeSize || config.Size > MaxQRCodeSize { if config.Size < MinQRCodeSize || config.Size > MaxQRCodeSize {
return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize)
} }
// 创建二维码对象 // 创建二维码对象
qr, err := qrcode.New(content, convertErrorLevel(config.ErrorLevel)) qr, err := qrcode.New(content, convertErrorLevel(config.ErrorLevel))
if err != nil { if err != nil {
return nil, fmt.Errorf("创建二维码失败: %w", err) return nil, fmt.Errorf("创建二维码失败: %w", err)
} }
// 设置颜色 // 设置颜色
qr.ForegroundColor = config.ForegroundColor qr.ForegroundColor = config.ForegroundColor
qr.BackgroundColor = config.BackgroundColor qr.BackgroundColor = config.BackgroundColor
// 生成PNG // 生成PNG
pngBytes, err := qr.PNG(config.Size) pngBytes, err := qr.PNG(config.Size)
if err != nil { if err != nil {
return nil, fmt.Errorf("生成PNG失败: %w", err) return nil, fmt.Errorf("生成PNG失败: %w", err)
} }
return pngBytes, nil return pngBytes, nil
} }
// GenerateQRCodeWithLogo 生成带Logo的二维码 // GenerateQRCodeWithLogo 生成带Logo的二维码
// //
// 在二维码中心嵌入Logo图片Logo会占据二维码约1/5的区域。 // 在二维码中心嵌入Logo图片Logo会占据二维码约1/5的区域。
// 使用高级纠错以确保Logo不影响二维码的可读性。 // 使用高级纠错以确保Logo不影响二维码的可读性。
// //
// 参数: // 参数:
// //
// content: 二维码内容 // content: 二维码内容
// logoPath: Logo图片文件路径支持PNG、JPEG等格式 // logoPath: Logo图片文件路径支持PNG、JPEG等格式
// size: 二维码尺寸可选默认256建议>=512以保证清晰度 // size: 二维码尺寸可选默认256建议>=512以保证清晰度
// //
// 返回值: // 返回值:
// //
// []byte: PNG格式的字节数组 // []byte: PNG格式的字节数组
// error: 错误信息 // error: 错误信息
// //
// 注意事项: // 注意事项:
// - Logo会自动缩放到二维码的1/5大小 // - Logo会自动缩放到二维码的1/5大小
// - 建议Logo使用正方形图片 // - 建议Logo使用正方形图片
// - 使用高级纠错级别(~30%容错) // - 使用高级纠错级别(~30%容错)
// - Logo会覆盖二维码中心区域 // - Logo会覆盖二维码中心区域
// - 建议二维码尺寸>=512以保证Logo清晰 // - 建议二维码尺寸>=512以保证Logo清晰
// - Logo文件必须存在且可读取 // - Logo文件必须存在且可读取
func GenerateQRCodeWithLogo(content, logoPath string, size ...int) ([]byte, error) { func GenerateQRCodeWithLogo(content, logoPath string, size ...int) ([]byte, error) {
qrSize := DefaultQRCodeSize qrSize := DefaultQRCodeSize
if len(size) > 0 { if len(size) > 0 {
qrSize = size[0] qrSize = size[0]
if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize { if qrSize < MinQRCodeSize || qrSize > MaxQRCodeSize {
return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize) return nil, fmt.Errorf("二维码尺寸必须在 %d 到 %d 之间", MinQRCodeSize, MaxQRCodeSize)
} }
} }
// 生成基础二维码 // 生成基础二维码
qr, err := qrcode.New(content, qrcode.High) qr, err := qrcode.New(content, qrcode.High)
if err != nil { if err != nil {
return nil, fmt.Errorf("创建二维码失败: %w", err) return nil, fmt.Errorf("创建二维码失败: %w", err)
} }
// 生成二维码图像 // 生成二维码图像
qrImage := qr.Image(qrSize) qrImage := qr.Image(qrSize)
// 读取Logo图片 // 读取Logo图片
logoFile, err := os.Open(logoPath) logoFile, err := os.Open(logoPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("打开Logo文件失败: %w", err) return nil, fmt.Errorf("打开Logo文件失败: %w", err)
} }
defer logoFile.Close() defer logoFile.Close()
logoImage, _, err := image.Decode(logoFile) logoImage, _, err := image.Decode(logoFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("解码Logo图片失败: %w", err) return nil, fmt.Errorf("解码Logo图片失败: %w", err)
} }
// 计算Logo位置和大小Logo占二维码的1/5 // 计算Logo位置和大小Logo占二维码的1/5
logoSize := qrSize / 5 logoSize := qrSize / 5
logoX := (qrSize - logoSize) / 2 logoX := (qrSize - logoSize) / 2
logoY := (qrSize - logoSize) / 2 logoY := (qrSize - logoSize) / 2
// 创建最终图像 // 创建最终图像
finalImage := image.NewRGBA(qrImage.Bounds()) finalImage := image.NewRGBA(qrImage.Bounds())
// 绘制二维码 // 绘制二维码
for y := 0; y < qrSize; y++ { for y := 0; y < qrSize; y++ {
for x := 0; x < qrSize; x++ { for x := 0; x < qrSize; x++ {
finalImage.Set(x, y, qrImage.At(x, y)) finalImage.Set(x, y, qrImage.At(x, y))
} }
} }
// 绘制Logo // 绘制Logo
logoOriginalBounds := logoImage.Bounds() logoOriginalBounds := logoImage.Bounds()
for y := 0; y < logoSize; y++ { for y := range logoSize {
for x := 0; x < logoSize; x++ { for x := range logoSize {
// 计算原始Logo的对应像素 // 计算原始Logo的对应像素
origX := x * logoOriginalBounds.Dx() / logoSize origX := x * logoOriginalBounds.Dx() / logoSize
origY := y * logoOriginalBounds.Dy() / logoSize origY := y * logoOriginalBounds.Dy() / logoSize
finalImage.Set(logoX+x, logoY+y, logoImage.At(origX, origY)) finalImage.Set(logoX+x, logoY+y, logoImage.At(origX, origY))
} }
} }
// 转换为PNG字节 // 转换为PNG字节
var buf bytes.Buffer var buf bytes.Buffer
if err := png.Encode(&buf, finalImage); err != nil { if err := png.Encode(&buf, finalImage); err != nil {
return nil, fmt.Errorf("编码PNG失败: %w", err) return nil, fmt.Errorf("编码PNG失败: %w", err)
} }
return buf.Bytes(), nil return buf.Bytes(), nil
} }
// SaveQRCodeBytes 保存二维码字节数组到文件 // SaveQRCodeBytes 保存二维码字节数组到文件
// //
// 将二维码字节数组保存为PNG文件常与 GenerateQRCodeBytes 或 // 将二维码字节数组保存为PNG文件常与 GenerateQRCodeBytes 或
// GenerateQRCodeWithConfig 配合使用。 // GenerateQRCodeWithConfig 配合使用。
// //
// 参数: // 参数:
// //
// data: PNG格式的二维码字节数组 // data: PNG格式的二维码字节数组
// filename: 保存的文件路径 // filename: 保存的文件路径
// //
// 返回值: // 返回值:
// //
// error: 错误信息 // error: 错误信息
// //
// 注意事项: // 注意事项:
// - 文件权限为0644 // - 文件权限为0644
// - 会覆盖已存在的文件 // - 会覆盖已存在的文件
// - 确保目录已存在 // - 确保目录已存在
func SaveQRCodeBytes(data []byte, filename string) error { func SaveQRCodeBytes(data []byte, filename string) error {
if err := os.WriteFile(filename, data, 0644); err != nil { if err := os.WriteFile(filename, data, 0644); err != nil {
return fmt.Errorf("保存文件失败: %w", err) return fmt.Errorf("保存文件失败: %w", err)
} }
return nil return nil
} }
// GenerateQRCodeDataURL 生成Data URL格式的二维码 // GenerateQRCodeDataURL 生成Data URL格式的二维码
// //
// 生成可以直接用于HTML <img>标签的Data URL格式字符串。 // 生成可以直接用于HTML <img>标签的Data URL格式字符串。
// Data URL包含了完整的图片数据无需额外的图片文件。 // Data URL包含了完整的图片数据无需额外的图片文件。
// //
// 参数: // 参数:
// //
// content: 二维码内容 // content: 二维码内容
// size: 二维码尺寸可选默认256 // size: 二维码尺寸可选默认256
// //
// 返回值: // 返回值:
// //
// string: Data URL格式字符串包含data:image/png;base64,前缀) // string: Data URL格式字符串包含data:image/png;base64,前缀)
// error: 错误信息 // error: 错误信息
// //
// 注意事项: // 注意事项:
// - Data URL字符串较长不适合存储到数据库 // - Data URL字符串较长不适合存储到数据库
// - 适合临时显示、前端渲染等场景 // - 适合临时显示、前端渲染等场景
// - 某些老旧浏览器可能不支持 // - 某些老旧浏览器可能不支持
func GenerateQRCodeDataURL(content string, size ...int) (string, error) { func GenerateQRCodeDataURL(content string, size ...int) (string, error) {
qrBytes, err := GenerateQRCodeBytes(content, size...) qrBytes, err := GenerateQRCodeBytes(content, size...)
if err != nil { if err != nil {
return "", err return "", err
} }
base64Str := base64.StdEncoding.EncodeToString(qrBytes) base64Str := base64.StdEncoding.EncodeToString(qrBytes)
dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Str) dataURL := fmt.Sprintf("data:image/png;base64,%s", base64Str)
return dataURL, nil return dataURL, nil
} }
// BatchGenerateQRCode 批量生成二维码文件 // BatchGenerateQRCode 批量生成二维码文件
// //
// 一次性生成多个二维码文件,适合: // 一次性生成多个二维码文件,适合:
// - 批量生成产品二维码 // - 批量生成产品二维码
// - 生成多个用户的会员卡 // - 生成多个用户的会员卡
// - 批量生成门票、优惠券等 // - 批量生成门票、优惠券等
// //
// 参数: // 参数:
// //
// items: map[内容]文件名例如map["产品A":"qr_a.png", "产品B":"qr_b.png"] // items: map[内容]文件名例如map["产品A":"qr_a.png", "产品B":"qr_b.png"]
// size: 二维码尺寸可选默认256所有二维码使用相同尺寸 // size: 二维码尺寸可选默认256所有二维码使用相同尺寸
// //
// 返回值: // 返回值:
// //
// []string: 失败的文件名列表成功时为nil // []string: 失败的文件名列表成功时为nil
// error: 错误信息(部分失败时返回错误,但成功的文件已生成) // error: 错误信息(部分失败时返回错误,但成功的文件已生成)
// //
// 注意事项: // 注意事项:
// - 即使部分失败,成功的二维码仍会生成 // - 即使部分失败,成功的二维码仍会生成
// - 建议检查返回的failed列表以确定哪些失败了 // - 建议检查返回的failed列表以确定哪些失败了
// - 大批量生成时注意磁盘空间 // - 大批量生成时注意磁盘空间
func BatchGenerateQRCode(items map[string]string, size ...int) ([]string, error) { func BatchGenerateQRCode(items map[string]string, size ...int) ([]string, error) {
var failed []string var failed []string
qrSize := DefaultQRCodeSize qrSize := DefaultQRCodeSize
if len(size) > 0 { if len(size) > 0 {
qrSize = size[0] qrSize = size[0]
} }
for content, filename := range items { for content, filename := range items {
err := GenerateQRCode(content, filename, qrSize) err := GenerateQRCode(content, filename, qrSize)
if err != nil { if err != nil {
failed = append(failed, filename) failed = append(failed, filename)
} }
} }
if len(failed) > 0 { if len(failed) > 0 {
return failed, fmt.Errorf("有 %d 个二维码生成失败", len(failed)) return failed, fmt.Errorf("有 %d 个二维码生成失败", len(failed))
} }
return nil, nil return nil, nil
} }

View File

@@ -9,7 +9,7 @@ func RandomString(l int) string {
str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" str := "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
bytes := []byte(str) bytes := []byte(str)
var result []byte = make([]byte, 0, l) var result []byte = make([]byte, 0, l)
for i := 0; i < l; i++ { for range l {
result = append(result, bytes[rand.IntN(len(bytes))]) result = append(result, bytes[rand.IntN(len(bytes))])
} }
return string(result) return string(result)
@@ -20,7 +20,7 @@ func RandomPureString(l int) string {
str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz" str := "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
bytes := []byte(str) bytes := []byte(str)
var result []byte = make([]byte, 0, l) var result []byte = make([]byte, 0, l)
for i := 0; i < l; i++ { for range l {
result = append(result, bytes[rand.IntN(len(bytes))]) result = append(result, bytes[rand.IntN(len(bytes))])
} }
return string(result) return string(result)
@@ -31,7 +31,7 @@ func RandomPureUpString(l int) string {
str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
bytes := []byte(str) bytes := []byte(str)
var result []byte = make([]byte, 0, l) var result []byte = make([]byte, 0, l)
for i := 0; i < l; i++ { for range l {
result = append(result, bytes[rand.IntN(len(bytes))]) result = append(result, bytes[rand.IntN(len(bytes))])
} }
return string(result) return string(result)
@@ -42,7 +42,7 @@ func RandomNumber(l int) string {
str := "0123456789" str := "0123456789"
bytes := []byte(str) bytes := []byte(str)
var result []byte var result []byte
for i := 0; i < l; i++ { for range l {
result = append(result, bytes[rand.IntN(len(bytes))]) result = append(result, bytes[rand.IntN(len(bytes))])
} }
return string(result) return string(result)