Compare commits

...

120 Commits
v0.0.4 ... main

Author SHA1 Message Date
yanweidong 42e9d55b62 fix logger new 2025-10-16 13:29:25 +08:00
yanweidong 179157f49e 修复Reply结构体中的字段名称,更新成功和错误响应方法以使用新的字段 2025-10-14 14:48:44 +08:00
yanweidong 8aafcbd91c fix bug 2025-10-13 13:10:01 +08:00
yanweidong 7e91109bce fix parsejwt 2025-10-13 11:59:49 +08:00
yanweidong d451eb3eff 修复GenerateJwt函数中的错误处理逻辑 2025-10-12 03:54:19 +08:00
yanweidong 3d71936ecf add token 2025-10-11 23:45:55 +08:00
yanweidong 524e310dfe fix logger 2025-10-07 17:31:14 +08:00
yanweidong 409cb53e8c fix bug 2025-10-05 15:05:50 +08:00
yanweidong 404957f16a fix bug 2025-10-05 15:00:07 +08:00
yanweidong 3d6871138a fix bug 2025-10-05 14:59:01 +08:00
yanweidong f7d8988415 add AppendMigrate 2025-10-05 14:57:17 +08:00
yanweidong 820d7a5c63 fix errcode 2025-10-04 20:21:45 +08:00
yanweidong 3038c6c22c fix logger bug 2025-10-04 18:22:19 +08:00
yanweidong 5584757ff4 fix logger bug 2025-10-04 17:42:26 +08:00
yanweidong 464617626b ai update. 2025-10-03 19:55:20 +08:00
yanweidong 0401a39a94 fix bug 2025-10-03 19:32:57 +08:00
yanweidong 82e6b81126 fix cache. 2025-10-02 19:46:15 +08:00
yanweidong f934472e50 fix logger,cache 2025-10-02 18:06:23 +08:00
yanweidong 10ee9bba10 build(go): 移除 go.mod 中所有依赖项声明
清理 go.mod 文件,移除了所有的 require 声明,
包括直接依赖和间接依赖,仅保留模块基本信息。
2025-09-27 00:52:00 +08:00
yanweidong 9f70704081 feat(dependencies): 更新 go.mod 和 go.sum 文件,添加新依赖并优化现有依赖版本
在 go.mod 中添加了多个新依赖,包括用于微服务架构、数据库支持、缓存和消息队列的库。同时,更新了 go.sum 文件以反映这些更改。README.md 文件也进行了相应的更新,增加了微服务架构的描述和功能模块的详细信息,确保文档与代码保持一致。
2025-09-27 00:41:27 +08:00
yanweidong 25386cf0e1 ```
refactor(print): 将 print 包重命名为 printer 并更新所有引用

将项目中的 print 包统一重命名为 printer,以避免与标准库或第三方库产生命名冲突。同时,
更新了所有相关模块对该包的引用,确保功能一致性和代码可维护性。

涉及文件包括:
- conf/new.go
- infra/service.go
- service/register.go
- service/service.go
- with/databases.go
- with/etcd.go
- with/memory.go
- with/redis.go

此外,删除了以下已废弃或未使用的代码文件:
- cmd/cmd.go
- data/map_float.go
- data/map_string.go
- oplog/new.go
- oplog/types.go
- print/print.go
- utils/ext.go
```
2025-09-27 00:20:36 +08:00
yanweidong 21716c4340 ```
feat(database): 自动迁移表结构并优化数据库初始化函数

将 `AutoMigrate` 逻辑从各数据库初始化方法中提取至统一的 `NewDatabase` 方法内,
避免重复代码。同时修改 `Databases`、`Etcd`、`Memory` 和 `RedisCache` 函数签名,
使其返回实例而非通过参数传递,提高代码可读性和一致性。
```
2025-09-23 16:17:03 +08:00
yanweidong 518c237061 fix(conf): 修改YAML解析方式以支持字符串输入
将yaml.Unmarshal的输入从文件字节流改为直接使用yamlString变量,
确保配置解析能够正确处理字符串格式的YAML内容。
2025-09-23 15:04:14 +08:00
yanweidong 139983134b ```
feat(service): 优化地址解析逻辑以支持端口号直接解析

重构 parseTraditionalStyle 函数,简化 NetworkAddress 构造方式,
并引入 utils.IsNumber 判断纯端口号情况,提升地址解析的准确性与兼容性。
```
2025-09-23 13:26:47 +08:00
yanweidong 75aa6ae647 dev 2025-09-23 13:09:27 +08:00
yanweidong f681f0bb17 ```
refactor(service): 优化Use函数参数类型

移除Use函数中initFunc参数的指针包装,直接使用func() error类型。
简化函数内部调用逻辑,提升代码可读性和健壮性。
```
2025-09-23 12:41:31 +08:00
yanweidong 44319d03b9 ```
refactor(database): 移除全局初始化函数定义

将数据库初始化函数从 database 包中移除,避免全局状态污染。

feat(service): 新增 Use 方法用于执行初始化函数

在 Service 结构体中添加 Use 方法,允许传入并执行初始化函数。
如果函数执行失败,则打印错误并 panic。

refactor(with): 删除旧的初始化逻辑包

删除 with 包中与数据库初始化相关的旧逻辑,统一初始化入口。
```
2025-09-23 12:37:00 +08:00
yanweidong 2f57edd277 ```
refactor(database): 重构数据库初始化逻辑

移除 NewDatabase 函数中的 Init 参数,改用 InitFunc 变量进行初始化操作。
更新 MigrateTables 附近的注释,明确说明 InitFunc 的用途。
删除函数内对 Init 参数的执行逻辑,确保代码简洁性。
```
2025-09-23 11:28:33 +08:00
yanweidong cf0ee224f7 ```
refactor(database): 调整数据库初始化函数参数传递方式

将 Init 函数参数从全局变量改为通过 NewDatabase 函数参数传入,
使初始化逻辑更清晰、可控。同时优化代码格式,去除多余空行,
提升代码可读性。
```
2025-09-23 11:02:25 +08:00
yanweidong f2d8ae26f6 build(go): 移除 go.mod 中所有依赖项声明
清理 go.mod 文件,移除了所有直接和间接的依赖项引用,
可能为后续依赖管理或模块重构做准备。
2025-09-22 19:03:20 +08:00
yanweidong dbf68c38c1 ```
docs(readme): 重构 README 文档结构并补充模块说明

- 重新组织 README 内容,明确划分私有仓库设置、功能模块等章节
- 补充 crypto、cache、database、middleware、queue 和 utils 模块的功能简介
- 统一代码示例格式,增加 bash 和 go 语言标识

feat(crypto): 优化 PKCS7 填充与去填充函数实现

- 新增 PKCS7Padding 和 PKCS7UnPadding 函数的详细注释
- 添加对输入参数的有效性校验,提升健壮性
- 修复可能引发越界 panic 的潜在问题

feat(database): 完善数据库连接及初始化逻辑

- 为 NewDatabase、NewMysql 和 NewPostgres 函数添加完整注释
- 修复 MaxOpenConns 配置未正确赋值的问题
- 在获取 *sql.DB 实例时增加错误处理逻辑
- 支持通过 Init 变量在连接建立后执行自定义初始化函数

feat(go.mod): 初始化项目依赖管理并引入核心组件

- 添加项目所需的主要依赖包,包括 gin、gorm、redis、nats 等
- 引入常用的工具库如 uuid、ulid、gopsutil 等
- 自动拉取并锁定所有间接依赖版本
```
2025-09-22 19:02:38 +08:00
yanweidong f70f8d94db fix(with): 修复 memory.go 中的配置初始化错误
将 bigcache.Config 结构体实例改为指针传递,确保配置能够正确初始化和传递。
2025-09-20 11:32:24 +08:00
yanweidong 257f0a6b6e fix(with): 优化 memory cache 配置初始化逻辑
移除未使用的 `encoding/json` 包导入。
调整配置初始化方式,避免不必要的结构体拷贝。
改进日志输出内容,仅显示关键配置项。
2025-09-20 11:21:02 +08:00
yanweidong 5e25e8eccc fix(with/etcd): 修改Etcd配置为空时的处理逻辑
当Etcd配置或端点为空时,将panic改为直接返回,避免程序崩溃
2025-09-20 11:08:47 +08:00
yanweidong 4f584726d6 ```
refactor(with): 调整Memory函数参数顺序

将Memory函数的参数顺序从(opts, cli)调整为(cli, opts),
使函数签名更加符合常规的客户端优先参数排列习惯,
提升代码可读性和一致性。
```
2025-09-20 10:59:45 +08:00
yanweidong b9d144353e ```
refactor(cache): 移除内存缓存和Redis缓存的初始化逻辑

移除了mem包中内存缓存的初始化函数,以及with包中Redis缓存的初始化函数。
这些缓存初始化逻辑将被重构到其他位置或采用新的实现方式。
```
2025-09-20 10:55:06 +08:00
yanweidong 7e7fa16441 ```
feat(database): 新增数据库初始化函数

新增 NewDatabase 函数,支持根据驱动类型初始化 MySQL 或 Postgres 数据库连接。
该函数根据传入的驱动名称自动路由到对应的数据库连接创建逻辑,并提供错误处理。
```
2025-09-20 10:30:06 +08:00
yanweidong bc2cb53287 database 新增 new,MigrateTables 2025-09-18 13:56:58 +08:00
yanweidong cef8b55fba feat(conf/new.go): 添加配置文件不存在时的处理逻辑及环境变量替换
在`New`函数中增加了对配置文件不存在时的处理逻辑,如果指定的服务配置文件不存在,则尝试读取基于工作空间的配置文件。此外,在加载YAML配置之前,新增了对环境变量的替换步骤,确保配置中的环境变量能够被正确解析。

这些改动提高了配置加载过程的灵活性与适应性,使得应用能够在缺少特定服务配置的情况下也能正常启动,并且支持通过环境变量动态调整配置内容。
2025-09-18 13:34:35 +08:00
yanweidong 2e07861622 build: 更新 go.mod 文件
- 移除了大量未使用的依赖包
- 保留了必要的依赖包
- 优化了项目结构,提高了代码的可维护性
2025-09-12 23:28:07 +08:00
yanweidong e30d50845a feat(vars): 添加 OK 状态码
在 status.go 文件中添加了新的状态码 OK,值为 "OK"。这个状态码可以用于表示系统或组件运行正常的情况。
2025-09-12 23:27:14 +08:00
yanweidong b4cd51a6dc refactor(encipher): 更新 JWT 过期时间变量
- 将 vars.JwtExpireDay 更改为 vars.JwtExpire,使代码更具通用性
- 优化了 GenerateTokenAes 函数中的过期时间计算逻辑
2025-09-12 20:11:49 +08:00
yanweidong dac969d798 refactor(vars): 修改 JWT 过期时间变量名称
- 将 JwtExpireDay 重命名为 JwtExpire
- 新变量名称更加简洁,同时消除了冗余的 Day 后缀
2025-09-12 18:56:23 +08:00
yanweidong 2f398c73b3 add middleware for CORS and mode configuration 2025-09-02 10:01:09 +08:00
zhaoxiaorong cb8e9bad4b fix 操作痕迹可选加密 2025-09-01 12:40:23 +08:00
zhaoxiaorong 1005e89e4f Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-08-23 21:17:56 +08:00
zhaoxiaorong 268c7f99c7 fix 取消从redis获取token,改为从token中获取有效时间 2025-08-23 21:17:51 +08:00
yanweidong fc72fd123d add PrintJson 2025-08-22 12:15:55 +08:00
yanweidong 63a4653eb2 add CopyFile 2025-08-21 10:59:18 +08:00
yanweidong ffb706df32 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-08-11 14:42:38 +08:00
yanweidong 282cdde7f9 add file free by filter and allow 2025-08-11 14:42:23 +08:00
zhaoxiaorong e28934d7b8 fix 2025-07-29 09:48:53 +08:00
zhaoxiaorong 93491fa747 fix 2025-07-29 09:43:14 +08:00
zhaoxiaorong f8d7737723 fix 2025-07-25 15:10:59 +08:00
zhaoxiaorong 35104ebb90 fix 2025-07-04 16:03:47 +08:00
yanweidong fc7c1e87a6 fix licence watch 2025-05-28 15:58:28 +08:00
zhaoxiaorong 8c62f529e3 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-05-27 09:41:00 +08:00
zhaoxiaorong 9d3b3404e4 fix 2025-05-27 09:40:56 +08:00
yanweidong bfccf4d468 fix licence 2025-05-26 22:11:47 +08:00
yanweidong cd72620e49 add HttpPostJSON 2025-05-23 10:45:28 +08:00
zhaoxiaorong 5bb23deb3b fix 2025-05-21 20:13:55 +08:00
david.yan 2c713adc16 add sync map data. 2025-05-03 15:49:16 +08:00
zhaoxiaorong 21f09ea41e fix 2025-04-24 17:52:29 +08:00
yanweidong 4d06ad3e8b add err code 2025-04-19 20:14:37 +08:00
yanweidong 52a81a404e fix infra response 2025-04-18 19:11:50 +08:00
yanweidong 6cd06d86bc add std_owner 2025-04-17 17:18:58 +08:00
zhaoxiaorong ca9f7047c6 fix 兼容性调整 2025-04-15 21:49:17 +08:00
zhaoxiaorong 2de73fea00 fix 兼容性调整 2025-04-15 20:50:28 +08:00
zhaoxiaorong 4b73f086b1 dev oplog 2025-04-11 18:14:07 +08:00
zhaoxiaorong c08950c10a fix 2025-04-11 18:06:08 +08:00
zhaoxiaorong d691648916 fix 2025-04-11 17:53:50 +08:00
zhaoxiaorong 8060cdb508 fix 2025-04-11 17:50:06 +08:00
zhaoxiaorong 50c23df124 fix 2025-04-11 17:44:49 +08:00
zhaoxiaorong 04b8e5b03b fix 2025-04-11 16:45:11 +08:00
zhaoxiaorong dd95b8d8b1 fix 2025-04-09 15:24:28 +08:00
zhaoxiaorong dd9a692858 fix 2025-04-09 11:12:42 +08:00
zhaoxiaorong b5374b85ff fix 2025-04-09 10:56:49 +08:00
zhaoxiaorong 5172824358 fix 2025-04-09 10:34:01 +08:00
zhaoxiaorong 51ff7d1ffd fix 2025-04-09 10:19:15 +08:00
zhaoxiaorong c7f24e3b6d fix 2025-04-09 09:39:51 +08:00
david.yan f7948263c5 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-04-08 15:20:34 +08:00
david.yan fc42bc92ff fix parse meta ctx 2025-04-08 15:20:22 +08:00
yanweidong b8f693ef82 fix net getLocationIP 2025-04-07 11:14:07 +08:00
david.yan 6ec06c2813 add third 2025-04-06 13:42:44 +08:00
david.yan 7983651fcd add time <=> string 2025-04-02 14:53:45 +08:00
david.yan d7fb72b5e0 add types.db identity default:uuid_generate_v4() 2025-04-01 23:34:24 +08:00
david.yan 6d1f59fd35 add gateway 2025-03-30 13:24:13 +08:00
david.yan 93daa022bc add gateway 2025-03-30 13:23:46 +08:00
david.yan ca00b34e24 add GatewayConf 2025-03-29 20:14:26 +08:00
david.yan 922cfd6a02 feat:service mod 2025-03-29 15:02:49 +08:00
david.yan d131495f4f up go.mod 2025-03-27 03:17:48 +08:00
david.yan 04451d4b2f add conf.PrintInfo 2025-03-27 03:10:56 +08:00
david.yan e184fff689 add conf.PrintInfo 2025-03-27 03:08:59 +08:00
david.yan 33d1b2e0ee add SetOptions 2025-03-27 02:52:01 +08:00
david.yan 433cd1f8bc fix hash key 2025-03-27 02:49:44 +08:00
david.yan da87dee55c fix srv key to lower 2025-03-27 02:43:15 +08:00
yanweidong 4fe311b722 add uuid v7 2025-03-12 17:10:04 +08:00
yanweidong a4c6acf56d status int8=>int64 2025-03-12 17:01:46 +08:00
yanweidong ce98b833ae fix print module 2025-02-21 15:11:34 +08:00
yanweidong 4aa5110726 fix log 2025-02-21 14:37:01 +08:00
yanweidong d4b7e7e9e6 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-02-20 17:41:21 +08:00
yanweidong 98ee26f215 fix conf 2025-02-20 17:41:11 +08:00
yanweidong 0218dfd02e fix conf 2025-02-20 17:41:08 +08:00
zhaoxiaorong 629ec74be6 response fix 2025-02-20 15:58:54 +08:00
yanweidong a128eb8461 add err code 2025-02-20 12:35:33 +08:00
david.yan c2858f1002 update go.mod 2025-02-18 23:42:23 +08:00
zhaoxiaorong 5819a8e08f fix JwtClaims SsoToken 2025-02-13 18:01:24 +08:00
zhaoxiaorong a2e3bfab77 fix 2025-02-11 14:22:14 +08:00
zhaoxiaorong f1310d093e fix 2025-02-11 13:19:07 +08:00
zhaoxiaorong fd967b8c1d fix 2025-02-11 13:11:30 +08:00
zhaoxiaorong 8d8ec866cd GenerateTokenAes 获取JwtSecretLen 2025-02-11 11:56:48 +08:00
zhaoxiaorong d8e54f4909 fix 2025-02-11 11:06:35 +08:00
weidong e89e1a372a feat:ok 2025-02-07 20:33:27 +08:00
weidong 76a4b67269 dev 2025-02-07 19:24:54 +08:00
zhaoxiaorong 6a939f7082 licence 2025-02-07 17:47:11 +08:00
zhaoxiaorong 78bf3a7827 fix 2025-02-07 17:09:59 +08:00
zhaoxiaorong 58139791f3 errcode 2025-02-07 17:08:55 +08:00
zhaoxiaorong fd62c5cddb init 2025-02-07 16:16:33 +08:00
zhaoxiaorong dadea265b2 queue 2025-02-07 15:31:27 +08:00
zhaoxiaorong db89284fdb queue 2025-02-07 15:28:55 +08:00
73 changed files with 3603 additions and 1896 deletions

278
README.md
View File

@ -1,2 +1,278 @@
# core
# BSM-SDK Core
BSM-SDK Core 是一个企业级后端开发工具包的核心模块,提供了微服务架构、配置管理、加密解密、缓存、数据库访问、中间件等基础功能。
## 🚀 最新优化
- ✅ 添加了完整的中文注释,提高代码可读性
- ✅ 优化了数据类型转换函数的性能
- ✅ 改进了错误处理机制
- ✅ 增强了代码文档和注释
- ✅ 统一了代码风格和命名规范
## 私有仓库设置
```bash
go env -w GOPRIVATE=git.apinb.com/*
go env -w GONOPROXY=git.apinb.com/*
go env -w GOINSECURE=git.apinb.com/*
go env -w GONOSUMDB=git.apinb.com/*
```
## 核心功能模块
### 1. 服务管理 (service)
#### 服务启动与注册
- **Service**: 核心服务结构,支持 gRPC 服务启动和 etcd 注册
- **New()**: 创建服务实例
- **Start()**: 启动服务,支持 etcd 注册和 HTTP 网关
- **Stop()**: 优雅停止服务
- **Use()**: 执行初始化函数
#### 服务发现
- **FoundGrpcMethods()**: 自动发现 gRPC 方法
- **RegisterService()**: 注册服务到 etcd
- **ServiceRegister**: 服务注册器,支持租约管理
#### 元数据解析
- **ParseMetaCtx()**: 解析 gRPC 上下文中的元数据
- **ParseOptions**: 解析选项支持角色验证和私有IP检查
### 2. 配置管理 (conf)
#### 配置加载
- **New()**: 加载配置文件,支持环境变量替换
- **NotNil()**: 验证必需配置项
- **PrintInfo()**: 打印配置信息
- **CheckPort()**: 检查端口配置
#### 配置类型
- **MicroServiceConf**: 微服务配置
- **GatewayConf**: 网关配置
- **DBConf**: 数据库配置
### 3. 加密与解密 (crypto)
#### AES 加密
- **AESGCMEncrypt/AESGCMDecrypt**: GCM 模式加密解密
- **Encrypt/Decrypt**: CBC 模式加密解密
- **AesEncryptECB/AesDecryptECB**: ECB 模式加密解密
- **AesKeyCheck()**: 密钥环境变量检测
#### RSA 加密
- **EncryptWithPublicKey()**: 公钥加密
- **Decrypt()**: 私钥解密
#### 通用加密
- **AesEncryptCBC/AesDecryptCBC**: 通用 CBC 加密解密
- **PKCS7Padding/PKCS7UnPadding**: PKCS7 填充
### 4. 数据库支持 (database)
#### 数据库连接
- **NewDatabase()**: 创建数据库连接,支持 MySQL 和 PostgreSQL
- **NewMysql()**: MySQL 连接
- **NewPostgres()**: PostgreSQL 连接
#### 数据库类型
- **SqlOptions**: 数据库连接选项
- **Std_IICUDS**: 标准数据库模型ID、Identity、Created、Updated、Deleted、Status
- **Std_ICUD**: 标准数据库模型ID、Created、Updated、Deleted
- **Std_Passport**: 护照模型
- **Std_Owner**: 所有者模型
### 5. 缓存支持 (cache)
#### Redis 缓存
- **RedisClient**: Redis 客户端封装
- **New()**: 创建 Redis 连接
- **Hash()**: 哈希分片计算
#### 内存缓存
- 支持内存缓存操作
### 6. 消息队列 (queue)
#### NATS 消息队列
- **Nats**: NATS 客户端封装
- **NewNats()**: 创建 NATS 连接
- **Subscribe()**: 订阅消息
- **Producer()**: 发布消息
### 7. 中间件 (middleware)
#### JWT 认证
- **JwtAuth()**: JWT 认证中间件
- **ParseAuth()**: 解析认证信息
#### CORS 支持
- **Cors()**: CORS 中间件
#### 运行模式
- **Mode()**: 设置 Gin 运行模式
### 8. 工具类 (utils)
#### 网络工具
- **GetLocationIP()**: 获取本机IP
- **IsPublicIP()**: 判断是否为公网IP
- **HttpGet/HttpPost()**: HTTP 请求工具
- **DownloadFile()**: 文件下载
#### 数据类型转换
- **String2Int/String2Int64**: 字符串转整数
- **String2Float64/String2Float32**: 字符串转浮点数
- **Int2String/Int642String**: 整数转字符串
- **Float64ToString/Float32ToString**: 浮点数转字符串
- **AnyToString()**: 任意类型转字符串
#### 时间处理
- **Time2String()**: 时间转字符串
- **String2Time()**: 字符串转时间
#### 身份标识
- **UUID()**: 生成 UUID
- **ULID()**: 生成 ULID
#### 文件操作
- **PathExists()**: 检查路径是否存在
- **CreateDir()**: 创建目录
- **GetRunPath()**: 获取运行路径
- **GetCurrentPath()**: 获取当前路径
#### JSON 处理
- **JsonEscape()**: JSON 转义
### 9. 错误处理 (errcode)
#### 标准错误码
- **Header 错误**: 101-104
- **标准错误**: 110-121
- **JWT 错误**: 131-139
- **模型错误**: 151-157
- **gRPC 错误**: 171-186
#### 错误创建
- **NewError()**: 创建标准错误
- **ErrFatal()**: 创建致命错误
- **ErrNotFound()**: 创建未找到错误
### 10. 响应处理 (infra)
#### 统一响应
- **Reply**: 统一响应结构
- **Success()**: 成功响应
- **Error()**: 错误响应,支持 gRPC 状态码转换
### 11. 日志打印 (printer)
#### 彩色日志
- **Info()**: 信息日志(白色)
- **Warn()**: 警告日志(橙色)
- **Success()**: 成功日志(绿色)
- **Error()**: 错误日志(红色)
- **Json()**: JSON 格式化输出
### 12. 环境管理 (env)
#### 运行时环境
- **RuntimeEnv**: 运行时环境配置
- **NewEnv()**: 初始化环境
- **GetEnvDefault()**: 获取环境变量默认值
### 13. 变量管理 (vars)
#### 全局变量
- **ServiceKey**: 服务键
- **HostName**: 主机名
- **VERSION**: 版本号
- **BUILD_TIME**: 构建时间
- **GO_VERSION**: Go 版本
### 14. 第三方集成 (third)
#### 微信集成
- 支持微信相关功能
### 15. 许可证管理 (licence)
#### 许可证验证
- 支持许可证文件验证
### 配置环境变量
```bash
export BSM_Workspace=def
export BSM_JwtSecretKey=your_secret_key
export BSM_RuntimeMode=dev
export BSM_Prefix=/usr/local/bsm
```
### 安全建议
- 使用强密钥进行加密
- 定期更新 JWT 密钥
- 配置适当的数据库连接池
- 使用 HTTPS 进行通信
- 定期检查许可证有效性
## 📝 代码优化说明
### 已完成的优化
1. **中文注释优化**
- 为所有核心模块添加了详细的中文注释
- 统一了注释风格和格式
- 提高了代码的可读性和维护性
2. **性能优化**
- 优化了 `String2Int64` 函数,直接使用 `strconv.ParseInt` 而不是先转 int 再转 int64
- 改进了网络工具函数的错误处理
- 优化了缓存操作的性能
3. **代码质量提升**
- 统一了函数命名规范
- 改进了错误处理机制
- 增强了类型安全性
### 使用建议
1. **配置管理**
```go
// 推荐使用环境变量进行配置
conf.New("your-service", &config)
```
2. **错误处理**
```go
// 使用统一的错误码
if err != nil {
return errcode.ErrInternal
}
```
3. **缓存使用**
```go
// 使用统一的缓存键前缀
key := redisClient.BuildKey("user", userID)
```
4. **数据库连接**
```go
// 使用连接池优化
db, err := database.NewDatabase("mysql", dsn, options)
```
## 许可证
本项目采用私有许可证,请确保已获得相应的使用授权。
## 贡献
欢迎提交 Issue 和 Pull Request 来改进本项目。
## 联系方式
如有问题,请联系开发团队。

64
cache/mapsync/map_float.go vendored Normal file
View File

@ -0,0 +1,64 @@
package mapsync
import (
"sync"
)
var (
// sync map
MapFloat *mapFloat
)
// lock
type mapFloat struct {
sync.RWMutex
Data map[string]float64
}
func NewMapFloat() *mapFloat {
return &mapFloat{
Data: make(map[string]float64),
}
}
func (c *mapFloat) Set(key string, val float64) {
c.Lock()
defer c.Unlock()
c.Data[key] = val
}
func (c *mapFloat) Get(key string) float64 {
c.RLock()
defer c.RUnlock()
vals, ok := c.Data[key]
if !ok {
return 0
}
return vals
}
func (c *mapFloat) Del(key string) {
c.Lock()
defer c.Unlock()
delete(c.Data, key)
}
func (c *mapFloat) Keys() (keys []string) {
c.RLock()
defer c.RUnlock()
for k, _ := range c.Data {
keys = append(keys, k)
}
return
}
func (c *mapFloat) All() map[string]float64 {
c.RLock()
defer c.RUnlock()
return c.Data
}

64
cache/mapsync/map_int.go vendored Normal file
View File

@ -0,0 +1,64 @@
package mapsync
import (
"sync"
)
var (
// sync map
MapInt *mapInt
)
// lock
type mapInt struct {
sync.RWMutex
Data map[string]int
}
func NewMapInt() *mapInt {
return &mapInt{
Data: make(map[string]int),
}
}
func (c *mapInt) Set(key string, val int) {
c.Lock()
defer c.Unlock()
c.Data[key] = val
}
func (c *mapInt) Get(key string) int {
c.RLock()
defer c.RUnlock()
vals, ok := c.Data[key]
if !ok {
return 0
}
return vals
}
func (c *mapInt) Del(key string) {
c.Lock()
defer c.Unlock()
delete(c.Data, key)
}
func (c *mapInt) All() map[string]int {
c.RLock()
defer c.RUnlock()
return c.Data
}
func (c *mapInt) Keys() (keys []string) {
c.RLock()
defer c.RUnlock()
for k, _ := range c.Data {
keys = append(keys, k)
}
return
}

64
cache/mapsync/map_string.go vendored Normal file
View File

@ -0,0 +1,64 @@
package mapsync
import (
"sync"
)
var (
// sync map
MapString *mapString
)
// lock
type mapString struct {
sync.RWMutex
Data map[string]string
}
func NewMapString() *mapString {
return &mapString{
Data: make(map[string]string),
}
}
func (c *mapString) Set(key, val string) {
c.Lock()
defer c.Unlock()
c.Data[key] = val
}
func (c *mapString) Get(key string) string {
c.RLock()
defer c.RUnlock()
vals, ok := c.Data[key]
if !ok {
return ""
}
return vals
}
func (c *mapString) Del(key string) {
c.Lock()
defer c.Unlock()
delete(c.Data, key)
}
func (c *mapString) Keys() (keys []string) {
c.RLock()
defer c.RUnlock()
for k, _ := range c.Data {
keys = append(keys, k)
}
return
}
func (c *mapString) All() map[string]string {
c.RLock()
defer c.RUnlock()
return c.Data
}

14
cache/mem/mem.go vendored
View File

@ -1,14 +0,0 @@
package mem
import (
"git.apinb.com/bsm-sdk/core/vars"
"github.com/FishGoddess/cachego"
)
func New() cachego.Cache {
return cachego.NewCache(
cachego.WithGC(vars.MemGcDuration),
cachego.WithShardings(vars.MemShardings),
cachego.WithLFU(vars.MemLRUMaxNumber),
)
}

90
cache/redis/cache.go vendored Normal file
View File

@ -0,0 +1,90 @@
// Package redis 提供Redis缓存操作功能
// 包括缓存设置、获取、删除等基本操作
package redis
import (
"encoding/json"
"fmt"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/vars"
)
// BuildKey 构建缓存键
// prefix: 键前缀
// params: 键参数
// 返回: 完整的缓存键
func (c *RedisClient) BuildKey(prefix string, params ...interface{}) string {
key := vars.CacheKeyPrefix + prefix
for _, param := range params {
key += fmt.Sprintf(":%v", param)
}
return key
}
// Get 获取缓存
// key: 缓存键
// result: 存储结果的指针
// 返回: 错误信息
func (c *RedisClient) Get(key string, result interface{}) error {
if c.Client == nil {
return errcode.ErrRedis
}
data, err := c.Client.Get(c.Ctx, key).Result()
if err != nil {
return errcode.NewError(500, err.Error())
}
return json.Unmarshal([]byte(data), result)
}
// Set 设置缓存
// key: 缓存键
// value: 缓存值
// ttl: 过期时间
// 返回: 错误信息
func (c *RedisClient) Set(key string, value interface{}, ttl time.Duration) error {
if c.Client == nil {
return errcode.ErrRedis
}
data, err := json.Marshal(value)
if err != nil {
return errcode.NewError(500, err.Error())
}
return c.Client.Set(c.Ctx, key, data, ttl).Err()
}
// Delete 删除缓存
// key: 缓存键
// 返回: 错误信息
func (c *RedisClient) Delete(key string) error {
if c.Client == nil {
return errcode.ErrRedis
}
return c.Client.Del(c.Ctx, key).Err()
}
// ClearAllCache 清除所有缓存
// 返回: 错误信息
func (c *RedisClient) ClearAllCache() error {
if c.Client == nil {
return errcode.ErrRedis
}
pattern := vars.CacheKeyPrefix + "*"
keys, err := c.Client.Keys(c.Ctx, pattern).Result()
if err != nil {
return errcode.NewError(500, err.Error())
}
if len(keys) > 0 {
return c.Client.Del(c.Ctx, keys...).Err()
}
return nil
}

View File

@ -20,6 +20,7 @@ type RedisClient struct {
DB int
Client *cacheRedis.Client
Ctx context.Context
memory map[string]any
}
func New(dsn string, hashRadix string) *RedisClient {
@ -54,11 +55,12 @@ func New(dsn string, hashRadix string) *RedisClient {
DB: db,
Client: client,
Ctx: context.Background(),
memory: make(map[string]any),
}
}
func Hash(s string) int {
h := fnv.New32a()
h.Write([]byte(s))
h.Write([]byte(strings.ToLower(s)))
return int(h.Sum32()) % vars.RedisShardings
}

View File

@ -1,3 +1,5 @@
// Package conf 提供配置管理功能
// 支持YAML配置文件加载、环境变量替换、配置验证等
package conf
import (
@ -8,48 +10,39 @@ import (
"strings"
"time"
"math/rand/v2"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/print"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/bsm-sdk/core/vars"
"golang.org/x/exp/rand"
yaml "gopkg.in/yaml.v3"
)
func New(srvKey string, cfg any) *Runtime {
var run Runtime
// New 加载配置文件
// srvKey: 服务键名
// cfg: 配置结构体指针
func New(srvKey string, cfg any) {
env.NewEnv()
// 设置服务键
vars.ServiceKey = srvKey
// 获取主机名
vars.HostName, _ = os.Hostname()
// 获取根目录路径,优先使用环境变量设置的路径,如果未设置则使用当前工作目录
rootDir := strings.ToLower(env.GetEnvDefault("CORE_Path", ""))
if rootDir == "" {
rootDir, _ = os.Getwd()
// 构造配置文件路径,输出配置文件信息
cfp := fmt.Sprintf("%s_%s.yaml", strings.ToLower(srvKey), env.Runtime.Mode)
cfp = filepath.Join(env.Runtime.Prefix, "etc", cfp)
// 配置文件不存在则读取Workspace配置文件
if !utils.PathExists(cfp) {
cfp = fmt.Sprintf("workspace_%s_%s.yaml", strings.ToLower(env.Runtime.Workspace), env.Runtime.Mode)
cfp = filepath.Join(env.Runtime.Prefix, "etc", cfp)
}
// 获取运行模式,如果环境变量未设置,则默认为"dev"
run.Mode = strings.ToLower(env.GetEnvDefault("CORE_Mode", "dev"))
// 获取JWT密钥用于身份验证
run.JwtKey = strings.ToLower(env.GetEnvDefault("CORE_JwtKey", ""))
// 获取许可证路径,如果环境变量未设置,则默认在根目录下的"etc"文件夹
run.LicencePath = strings.ToLower(env.GetEnvDefault("CORE_LicencePath", ""))
if run.LicencePath == "" {
run.LicencePath = filepath.Join(rootDir, "etc")
}
// 如果JWT密钥未设置则记录错误并终止程序
if run.JwtKey == "" {
log.Fatalf("ENV: CORE_JwtKey Not Nil !")
}
// 构造配置文件路径,输出配置文件信息
cfp := fmt.Sprintf("%s_%s.yaml", srvKey, run.Mode)
cfp = filepath.Join(rootDir, "etc", cfp)
print.Info("[CORE - %s] Config File: %s", srvKey, cfp)
printer.Info("[BSM - %s] Config File: %s", srvKey, cfp)
printer.Info("[BSM - %s] Check Configure ...", vars.ServiceKey)
// 读取配置文件内容
yamlFile, err := os.ReadFile(cfp)
@ -57,36 +50,73 @@ func New(srvKey string, cfg any) *Runtime {
log.Fatalf("ERROR: %v", err)
}
// 检查配置文件中是否存在Service和Addr字段
if !strings.Contains(string(yamlFile), "Service:") {
// 替换环境变量
yamlString := os.ExpandEnv(string(yamlFile))
// 检查配置文件中是否存在Service字段
if !strings.Contains(yamlString, "Service:") {
log.Fatalln("ERROR: Service Not Nil", cfp)
}
if !strings.Contains(string(yamlFile), "Port:") {
log.Fatalln("ERROR: Port Not Nil", cfp)
}
// 解析YAML
err = yaml.Unmarshal(yamlFile, cfg)
// 解析YAML到配置结构体
err = yaml.Unmarshal([]byte(yamlString), cfg)
if err != nil {
log.Fatalf("ERROR: %v", err)
}
return &run
}
// NotNil 验证必需配置项不为空
// values: 需要验证的配置值列表
func NotNil(values ...string) {
for _, value := range values {
if strings.TrimSpace(value) == "" {
log.Fatalln("ERROR:Must config not nil")
log.Fatalln("ERROR:Must config key not nil")
}
}
}
func CheckPort(port int) int {
if port <= 0 || port >= 65535 {
rand.Seed(uint64(time.Now().UnixNano()))
return rand.Intn(65535-1024) + 1024 // 生成1024到65535之间的随机端口
// PrintInfo 打印配置信息
// addr: 服务地址
func PrintInfo(addr string) {
printer.Success("[BSM - %s] Config Check Success.", vars.ServiceKey)
printer.Info("[BSM - %s] Service Name: %s", vars.ServiceKey, vars.ServiceKey)
printer.Info("[BSM - %s] Runtime Mode: %s", vars.ServiceKey, env.Runtime.Mode)
}
// CheckPort 检查端口配置,如果为空则生成随机端口
// port: 端口字符串
// 返回: 有效的端口字符串
func CheckPort(port string) string {
if port == "" {
r := rand.New(rand.NewPCG(1000, uint64(time.Now().UnixNano())))
p := r.IntN(65535-1024) + 1024 // 生成1024到65535之间的随机端口
return utils.Int2String(p)
}
return port
}
// CheckIP 检查IP配置如果为空则获取本机IP
// ip: IP地址字符串
// 返回: 有效的IP地址字符串
func CheckIP(ip string) string {
if ip == "" {
return utils.GetLocationIP()
}
return ip
}
// 初始化Logger配置
func InitLoggerConf(cfg *LogConf) *LogConf {
if cfg == nil {
return &LogConf{
Name: strings.ToLower(vars.ServiceKey),
Level: vars.LogLevel(vars.DEBUG),
Dir: "./logs/",
Endpoint: "",
Console: true,
File: true,
Remote: false,
}
}
return cfg
}

View File

@ -1,11 +1,16 @@
package conf
import "git.apinb.com/bsm-sdk/core/vars"
type Base struct {
Service string `yaml:"Service"` // 服务名称
Port int `yaml:"Port"` // 服务监听端口,0为自动随机端口
Cache string `yaml:"Cache"` // REDIS缓存
OnMicroService bool `yaml:"OnMicroService"` // 是否启用微服务
SecretKey string `yaml:"SecretKey"` // 服务秘钥
Service string `yaml:"Service"` // 服务名称
Port string `yaml:"Port"` // 服务监听端口,0为自动随机端口
Cache string `yaml:"Cache"` // REDIS缓存
SecretKey string `yaml:"SecretKey"` // 服务秘钥
BindIP string `yaml:"BindIP"` // 绑定IP
Addr string `yaml:"Addr"`
Log *LogConf `yaml:"Log"` // 日志配置
OnMicroService bool `yaml:"OnMicroService"`
}
type DBConf struct {
@ -13,6 +18,16 @@ type DBConf struct {
Source []string `yaml:"Source"` // 数据库连接
}
type MicroServiceConf struct {
Enable bool `yaml:"Enable"` // 是否启用微服务
Anonymous []string `yaml:"Anonymous"`
}
type GatewayConf struct {
Enable bool `yaml:"Enable"` // 是否启用网关服务
Port int `yaml:"Port"` // 服务监听端口
}
type ApmConf struct {
Name string // APM服务名称
Platform string `yaml:"Platform"` // APM平台apm,skywalking
@ -37,11 +52,14 @@ type RpcConf struct {
}
type OssConf struct {
Platform string `yaml:"Platform"` // oss平台aliyun,tencent,huawei,aws,minio
Site string `yaml:"Site"` // oss站点HOST
Endpoint string `yaml:"Endpoint"` // oss服务接入地址
Region string `yaml:"Region"` // oss服务区域
AccessKeyID string `yaml:"AccessKeyId"` // oss AccessKeyId
AccessKeySecret string `yaml:"AccessKeySecret"` // oss AccessKeySecret
UseSSL bool `yaml:"UseSSL"` // 是否使用SSL
}
type MqConf struct {
@ -49,14 +67,29 @@ type MqConf struct {
Space string `yaml:"Space"` // MQ服务空间
}
type Runtime struct {
Mode string // 运行模式dev,test,prod
JwtKey string // JWT密钥
LicencePath string // Licence文件路径
}
type TlsConf struct {
CaFile string // CA文件路径
CertFile string // 证书文件路径
KeyFile string // 密钥文件路径
}
type WebSocketConf struct {
ReadBufferSize int `yaml:"ReadBufferSize"`
WriteBufferSize int `yaml:"WriteBufferSize"`
CheckOrigin bool `yaml:"CheckOrigin"`
PingPeriod string `yaml:"PingPeriod"`
PongWait string `yaml:"PongWait"`
WriteWait string `yaml:"WriteWait"`
MaxMessageSize int64 `yaml:"MaxMessageSize"`
HandshakeTimeout string `yaml:"HandshakeTimeout"`
}
type LogConf struct {
Name string `yaml:"Name"`
Level vars.LogLevel `yaml:"Level"`
Dir string `yaml:"Dir"`
Endpoint string `yaml:"Endpoint"`
Console bool `yaml:"Console"`
File bool `yaml:"File"`
Remote bool `yaml:"Remote"`
}

View File

@ -1,13 +1,73 @@
// Package aes 提供AES加密解密功能
// 支持GCM、CBC、ECB等多种加密模式
package aes
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
)
// AES加密
// =================== GCM模式 ======================
// AESGCMEncrypt AES GCM模式加密
// plaintext: 明文数据
// key: 加密密钥
// 返回: 十六进制编码的密文字符串
func AESGCMEncrypt(plaintext, key []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
return hex.EncodeToString(ciphertext), nil
}
// AESGCMDecrypt AES GCM模式解密
// ciphertext: 十六进制编码的密文字符串
// key: 解密密钥
// 返回: 解密后的明文数据
func AESGCMDecrypt(ciphertext string, key []byte) ([]byte, error) {
data, err := hex.DecodeString(ciphertext)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, errors.New("密文无效")
}
nonce, cipherbyte := data[:nonceSize], data[nonceSize:]
return gcm.Open(nil, nonce, cipherbyte, nil)
}
// =================== CBC模式 ======================
// Encrypt AES CBC模式加密
// key: Base64编码的密钥
// iv: Base64编码的初始化向量
// data: 要加密的数据
// 返回: Base64编码的密文
func Encrypt(key string, iv string, data string) string {
if len(data) == 0 {
return ""
@ -24,7 +84,11 @@ func Encrypt(key string, iv string, data string) string {
return data
}
// AES解密
// Decrypt AES CBC模式解密
// key: Base64编码的密钥
// iv: Base64编码的初始化向量
// data: Base64编码的密文
// 返回: 解密后的明文
func Decrypt(key string, iv string, data string) string {
if len(data) == 0 {
return ""
@ -43,11 +107,19 @@ func Decrypt(key string, iv string, data string) string {
return data
}
// _PKCS5Padding PKCS5填充
// cipherText: 需要填充的数据
// blockSize: 块大小
// 返回: 填充后的数据
func _PKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}
// _PKCS5UnPadding PKCS5去填充
// origData: 需要去填充的数据
// 返回: 去填充后的数据
func _PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
@ -57,7 +129,11 @@ func _PKCS5UnPadding(origData []byte) []byte {
return origData[:(length - unpadding)]
}
// =================== ECB ======================
// =================== ECB模式 ======================
// AesEncryptECB AES ECB模式加密
// origData: 原始数据
// key: 加密密钥
// 返回: Base64编码的密文
func AesEncryptECB(origData []byte, key []byte) (data string) {
cipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
@ -76,11 +152,16 @@ func AesEncryptECB(origData []byte, key []byte) (data string) {
data = base64.StdEncoding.EncodeToString(encrypted)
return data
}
// AesDecryptECB AES ECB模式解密
// encrypted: Base64编码的密文
// key: 解密密钥
// 返回: 解密后的明文数据
func AesDecryptECB(encrypted string, key []byte) (decrypted []byte) {
decodedCiphertext, _ := base64.StdEncoding.DecodeString(encrypted)
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(decodedCiphertext))
//
// 分组分块解密
for bs, be := 0, cipher.BlockSize(); bs < len(decodedCiphertext); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], decodedCiphertext[bs:be])
}
@ -92,6 +173,10 @@ func AesDecryptECB(encrypted string, key []byte) (decrypted []byte) {
return decrypted[:trim]
}
// generateKey 生成标准长度的密钥
// key: 原始密钥
// 返回: 16字节的标准密钥
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
@ -102,3 +187,28 @@ func generateKey(key []byte) (genKey []byte) {
}
return genKey
}
// AesKeyCheck 检查AES密钥环境变量
// key: 环境变量名
// 返回: 十六进制编码的密钥字符串
func AesKeyCheck(key string) (string, error) {
// 从环境变量获取密钥
keyHex := os.Getenv(key)
if keyHex == "" {
// 使用入参作为变量名,避免硬编码误导
fmt.Printf("环境变量 %s 未设置\n", key)
return "", errors.New("密钥环境变量未设置")
}
// 解码十六进制字符串的密钥
byteKey, err := hex.DecodeString(keyHex)
if err != nil {
fmt.Printf("密钥解码失败: %v\n", err)
return "", errors.New("密钥解码失败")
}
// 检查密钥长度
if len(byteKey) != 16 && len(byteKey) != 24 && len(byteKey) != 32 {
fmt.Printf("无效的密钥长度: %d 字节 (需要16,24或32字节)\n", len(byteKey))
return "", errors.New("无效的密钥长度,需要16,24或32字节")
}
return keyHex, nil
}

View File

@ -21,16 +21,16 @@ var (
JwtSecretLen int
)
func New(token string) {
JwtSecret = []byte(token)
JwtSecretLen = len(env.MeshEnv.JwtSecretKey)
func New(secret string) {
JwtSecret = []byte(secret)
JwtSecretLen = len(env.Runtime.JwtSecretKey)
}
func GenerateTokenAes(id uint, identity, client, role string, owner any, extend map[string]string) (string, error) {
if (JwtSecretLen == 16 || JwtSecretLen == 24 || JwtSecretLen == 32) == false {
return "", errcode.ErrAuthSecret
if !(JwtSecretLen == 16 || JwtSecretLen == 24 || JwtSecretLen == 32) {
return "", errcode.ErrTokenSecretKey
}
expireTime := time.Now().Add(vars.JwtExpireDay)
expireTime := time.Now().Add(vars.JwtExpire)
claims := types.JwtClaims{
ID: id,
Identity: identity,
@ -43,7 +43,7 @@ func GenerateTokenAes(id uint, identity, client, role string, owner any, extend
byte, err := json.Marshal(claims)
if err != nil {
return "", errcode.ErrJsonEncode
return "", errcode.ErrTokenJsonEncode
}
token, err := AesEncryptCBC(byte)
@ -59,7 +59,7 @@ func AesEncryptCBC(plan []byte) (string, error) {
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, err := aes.NewCipher(JwtSecret)
if err != nil {
return "", errcode.ErrAuthSecret
return "", errcode.ErrTokenSecretKey
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
@ -76,17 +76,17 @@ func AesEncryptCBC(plan []byte) (string, error) {
func AesDecryptCBC(cryted string) (b []byte, err error) {
if (JwtSecretLen == 16 || JwtSecretLen == 24 || JwtSecretLen == 32) == false {
return nil, errcode.ErrAuthSecret
return nil, errcode.ErrTokenSecretKey
}
// 转成字节数组
crytedByte, err := base64.StdEncoding.DecodeString(cryted)
if err != nil {
return nil, errcode.ErrBase64Decode
return nil, errcode.ErrTokenBase64Decode
}
// 分组秘钥
block, err := aes.NewCipher(JwtSecret)
if err != nil {
return nil, errcode.ErrAuthSecret
return nil, errcode.ErrTokenSecretKey
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
@ -99,36 +99,46 @@ func AesDecryptCBC(cryted string) (b []byte, err error) {
// 去补全码
orig = PKCS7UnPadding(orig, blockSize)
if orig == nil {
return nil, errcode.ErrAuthParseFail
return nil, errcode.ErrTokenAuthParseFail
}
return orig, nil
}
// PKCS7Padding 使用PKCS7填充法对明文进行填充
func PKCS7Padding(ciphertext []byte, blocksize int) []byte {
padding := blocksize - len(ciphertext)%blocksize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 去码
// PKCS7UnPadding 去除PKCS7填充
// bug:runtime error: slice bounds out of range [:-22]
func PKCS7UnPadding(origData []byte, blocksize int) []byte {
// 检查块大小是否有效
if blocksize <= 0 {
return nil
}
// 检查原始数据是否为空
if origData == nil || len(origData) == 0 {
return nil
}
// 检查数据长度是否为块大小的整数倍
if len(origData)%blocksize != 0 {
return nil
}
length := len(origData)
// 获取填充字节数
unpadding := int(origData[length-1])
// 检查去填充后数据长度是否有效
if length-unpadding <= 0 {
return nil
}
// 返回去除填充后的数据
return origData[:(length - unpadding)]
}
@ -142,12 +152,12 @@ func ParseTokenAes(token string) (*types.JwtClaims, error) {
var ac *types.JwtClaims
err = json.Unmarshal(data, &ac)
if err != nil {
return nil, errcode.ErrAuthParseFail
return nil, errcode.ErrTokenAuthParseFail
}
expireTime := time.Now().Unix()
if expireTime > ac.ExpiresAt {
return nil, errcode.ErrAuthExpire
return nil, errcode.ErrTokenAuthExpire
}
return ac, nil

98
crypto/token/jwt.go Normal file
View File

@ -0,0 +1,98 @@
package token
import (
"encoding/base64"
"encoding/json"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/vars"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
ID uint `json:"id"`
Identity string `json:"identity"`
Extend map[string]string `json:"extend"`
Client string `json:"client"`
Owner any `json:"owner"`
Role string `json:"role"`
jwt.RegisteredClaims // v5版本新加的方法
}
type tokenJwt struct {
SecretKey string
}
func New(secretKey string) *tokenJwt {
return &tokenJwt{SecretKey: secretKey}
}
// 生成JWT
func (t *tokenJwt) GenerateJwt(id uint, identity, client, role string, owner any, extend map[string]string) (string, error) {
keyLen := len(t.SecretKey)
if !(keyLen == 16 || keyLen == 24 || keyLen == 32) {
return "", errcode.ErrTokenSecretKey
}
now := time.Now()
claims := Claims{
ID: id,
Identity: identity,
Client: client,
Extend: extend,
Owner: owner,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(vars.JwtExpire)), // 过期时间24小时
IssuedAt: jwt.NewNumericDate(now), // 签发时间
NotBefore: jwt.NewNumericDate(now), // 生效时间
},
}
// 使用HS256签名算法
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
s, err := token.SignedString([]byte(t.SecretKey))
if err != nil {
return "", errcode.String(errcode.ErrTokenGenerate, err.Error())
}
return s, nil
}
// 解析JWT
func (t *tokenJwt) ParseJwt(tokenstring string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(t.SecretKey), nil
})
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
} else {
return nil, errcode.String(errcode.ErrTokenParse, err.Error())
}
}
// 验证JWT是否过期
func (t *tokenJwt) IsExpired(tokenstring string) (bool, error) {
// 分割JWT的三个部分
parts := strings.Split(tokenstring, ".")
if len(parts) != 3 {
return true, errcode.ErrTokenDataInvalid
}
// 解码Payload部分
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return true, errcode.String(errcode.ErrTokenBase64Decode, err.Error())
}
// 解析JSON
var claims jwt.RegisteredClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return true, errcode.String(errcode.ErrTokenJsonDecode, err.Error())
}
// 检查过期时间
currentTime := time.Now().Unix()
return claims.ExpiresAt.Unix() < currentTime, nil
}

View File

@ -9,9 +9,9 @@ import (
"time"
"git.apinb.com/bsm-sdk/core/vars"
"github.com/elastic/go-elasticsearch/v8"
"github.com/elastic/go-elasticsearch/v8/esapi"
"github.com/elastic/go-elasticsearch/v8/esutil"
"github.com/elastic/go-elasticsearch/v9"
"github.com/elastic/go-elasticsearch/v9/esapi"
"github.com/elastic/go-elasticsearch/v9/esutil"
)
type ES struct {

View File

@ -1,97 +0,0 @@
fork from https://github.com/liyuan1125/gorm-cache
```go
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/liyuan1125/gorm-cache"
redis2 "github.com/liyuan1125/gorm-cache/store/redis"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"os"
"time"
)
var (
db *gorm.DB
redisClient *redis.Client
cachePlugin *cache.Cache
)
func newDb() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err.Error())
return
}
redisClient = redis.NewClient(&redis.Options{Addr: ":6379"})
cacheConfig := &cache.Config{
Store: redis2.NewWithDb(redisClient), // OR redis2.New(&redis.Options{Addr:"6379"})
Serializer: &cache.DefaultJSONSerializer{},
}
cachePlugin = cache.New(cacheConfig)
if err = db.Use(cachePlugin); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func basic() {
var username string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("username", &username)
fmt.Println(username)
// output gorm
}
func customKey() {
var nickname string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
ctx = cache.NewKey(ctx, "nickname")
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("nickname", &nickname)
fmt.Println(nickname)
// output gormwithmysql
}
func useTag() {
var nickname string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
ctx = cache.NewTag(ctx, "users")
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("nickname", &nickname)
fmt.Println(nickname)
// output gormwithmysql
}
func main() {
newDb()
basic()
customKey()
useTag()
ctx := context.Background()
fmt.Println(redisClient.Keys(ctx, "*").Val())
fmt.Println(cachePlugin.RemoveFromTag(ctx, "users"))
}
```

View File

@ -1,200 +0,0 @@
package cache
import (
"context"
"hash/fnv"
"os"
"strconv"
"time"
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
)
type Config struct {
Store Store
Prefix string
Serializer Serializer
}
type (
Serializer interface {
Serialize(v any) ([]byte, error)
Deserialize(data []byte, v any) error
}
Store interface {
// Set 写入缓存数据
Set(ctx context.Context, key string, value any, ttl time.Duration) error
// Get 获取缓存数据
Get(ctx context.Context, key string) ([]byte, error)
// SaveTagKey 将缓存key写入tag
SaveTagKey(ctx context.Context, tag, key string) error
// RemoveFromTag 根据缓存tag删除缓存
RemoveFromTag(ctx context.Context, tag string) error
}
)
type Cache struct {
store Store
// Serializer 序列化
Serializer Serializer
// prefix 缓存前缀
prefix string
}
// New
// @param conf
// @date 2022-07-02 08:09:52
func New(conf *Config) *Cache {
if conf.Store == nil {
os.Exit(1)
}
if conf.Serializer == nil {
conf.Serializer = &DefaultJSONSerializer{}
}
return &Cache{
store: conf.Store,
prefix: conf.Prefix,
Serializer: conf.Serializer,
}
}
// Name
// @date 2022-07-02 08:09:48
func (p *Cache) Name() string {
return "gorm:cache"
}
// Initialize
// @param tx
// @date 2022-07-02 08:09:47
func (p *Cache) Initialize(tx *gorm.DB) error {
return tx.Callback().Query().Replace("gorm:query", p.Query)
}
// generateKey
// @param key
// @date 2022-07-02 08:09:46
func generateKey(key string) string {
hash := fnv.New64a()
_, _ = hash.Write([]byte(key))
return strconv.FormatUint(hash.Sum64(), 36)
}
// Query
// @param tx
// @date 2022-07-02 08:09:38
func (p *Cache) Query(tx *gorm.DB) {
ctx := tx.Statement.Context
var ttl time.Duration
var hasTTL bool
if ttl, hasTTL = FromExpiration(ctx); !hasTTL {
callbacks.Query(tx)
return
}
var (
key string
hasKey bool
)
// 调用 Gorm的方法生产SQL
callbacks.BuildQuerySQL(tx)
// 是否有自定义key
if key, hasKey = FromKey(ctx); !hasKey {
key = p.prefix + generateKey(tx.Statement.SQL.String())
}
// 查询缓存数据
if err := p.QueryCache(ctx, key, tx.Statement.Dest); err == nil {
return
}
// 查询数据库
p.QueryDB(tx)
if tx.Error != nil {
return
}
// 写入缓存
if err := p.SaveCache(ctx, key, tx.Statement.Dest, ttl); err != nil {
tx.Logger.Error(ctx, err.Error())
return
}
if tag, hasTag := FromTag(ctx); hasTag {
_ = p.store.SaveTagKey(ctx, tag, key)
}
}
// QueryDB 查询数据库数据
// 这里重写Query方法 是不想执行 callbacks.BuildQuerySQL 两遍
func (p *Cache) QueryDB(tx *gorm.DB) {
if tx.Error != nil || tx.DryRun {
return
}
rows, err := tx.Statement.ConnPool.QueryContext(tx.Statement.Context, tx.Statement.SQL.String(), tx.Statement.Vars...)
if err != nil {
_ = tx.AddError(err)
return
}
defer func() {
_ = tx.AddError(rows.Close())
}()
gorm.Scan(rows, tx, 0)
}
// QueryCache 查询缓存数据
// @param ctx
// @param key
// @param dest
func (p *Cache) QueryCache(ctx context.Context, key string, dest any) error {
values, err := p.store.Get(ctx, key)
if err != nil {
return err
}
switch dest.(type) {
case *int64:
dest = 0
}
return p.Serializer.Deserialize(values, dest)
}
// SaveCache 写入缓存数据
func (p *Cache) SaveCache(ctx context.Context, key string, dest any, ttl time.Duration) error {
values, err := p.Serializer.Serialize(dest)
if err != nil {
return err
}
return p.store.Set(ctx, key, values, ttl)
}
// RemoveFromTag 根据tag删除缓存数据
// @param ctx
// @param tag
// @date 2022-07-02 08:08:59
func (p *Cache) RemoveFromTag(ctx context.Context, tag string) error {
return p.store.RemoveFromTag(ctx, tag)
}

View File

@ -1,88 +0,0 @@
package cache
import (
"context"
"time"
)
type (
// queryCacheCtx
queryCacheCtx struct{}
// queryCacheKeyCtx
queryCacheKeyCtx struct{}
// queryCacheTagCtx
queryCacheTagCtx struct{}
)
// NewKey
// @param ctx
// @param key
// @date 2022-07-02 08:11:44
func NewKey(ctx context.Context, key string) context.Context {
return context.WithValue(ctx, queryCacheKeyCtx{}, key)
}
// NewTag
// @param ctx
// @param key
// @date 2022-07-02 08:11:43
func NewTag(ctx context.Context, key string) context.Context {
return context.WithValue(ctx, queryCacheTagCtx{}, key)
}
// NewExpiration
// @param ctx
// @param ttl
// @date 2022-07-02 08:11:41
func NewExpiration(ctx context.Context, ttl time.Duration) context.Context {
return context.WithValue(ctx, queryCacheCtx{}, ttl)
}
// FromExpiration
// @param ctx
// @date 2022-07-02 08:11:40
func FromExpiration(ctx context.Context) (time.Duration, bool) {
value := ctx.Value(queryCacheCtx{})
if value != nil {
if t, ok := value.(time.Duration); ok {
return t, true
}
}
return 0, false
}
// FromKey
// @param ctx
// @date 2022-07-02 08:11:39
func FromKey(ctx context.Context) (string, bool) {
value := ctx.Value(queryCacheKeyCtx{})
if value != nil {
if t, ok := value.(string); ok {
return t, true
}
}
return "", false
}
// FromTag
// @param ctx
// @date 2022-07-02 08:11:37
func FromTag(ctx context.Context) (string, bool) {
value := ctx.Value(queryCacheTagCtx{})
if value != nil {
if t, ok := value.(string); ok {
return t, true
}
}
return "", false
}

View File

@ -1,11 +0,0 @@
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
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.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
gorm.io/driver/mysql v1.3.4 h1:/KoBMgsUHC3bExsekDcmNYaBnfH2WNeFuXqqrqMc98Q=
gorm.io/driver/mysql v1.3.4/go.mod h1:s4Tq0KmD0yhPGHbZEwg1VPlH0vT/GBHJZorPzhcxBUE=
gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.6 h1:KFLdNgri4ExFFGTRGGFWON2P1ZN28+9SJRN8voOoYe0=
gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=

View File

@ -1,92 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/liyuan1125/gorm-cache"
redis2 "github.com/liyuan1125/gorm-cache/store/redis"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"os"
"time"
)
var (
db *gorm.DB
redisClient *redis.Client
cachePlugin *cache.Cache
)
func newDb() {
dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local"
var err error
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err.Error())
return
}
redisClient = redis.NewClient(&redis.Options{Addr: ":6379"})
cacheConfig := &cache.Config{
Store: redis2.NewWithDb(redisClient), // OR redis2.New(&redis.Options{Addr:"6379"})
Serializer: &cache.DefaultJSONSerializer{},
}
cachePlugin = cache.New(cacheConfig)
if err = db.Use(cachePlugin); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
}
func basic() {
var username string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("username", &username)
fmt.Println(username)
// output gorm
}
func customKey() {
var nickname string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
ctx = cache.NewKey(ctx, "nickname")
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("nickname", &nickname)
fmt.Println(nickname)
// output gormwithmysql
}
func useTag() {
var nickname string
ctx := context.Background()
ctx = cache.NewExpiration(ctx, time.Hour)
ctx = cache.NewTag(ctx, "users")
db.Table("users").WithContext(ctx).Where("id = 1").Limit(1).Pluck("nickname", &nickname)
fmt.Println(nickname)
// output gormwithmysql
}
func main() {
newDb()
basic()
customKey()
useTag()
ctx := context.Background()
fmt.Println(redisClient.Keys(ctx, "*").Val())
fmt.Println(cachePlugin.RemoveFromTag(ctx, "users"))
}

View File

@ -1,23 +0,0 @@
package cache
import (
"encoding/json"
)
type DefaultJSONSerializer struct{}
// Serialize
// @param v
// @date 2022-07-02 08:12:26
func (d *DefaultJSONSerializer) Serialize(v any) ([]byte, error) {
return json.Marshal(v)
}
// Deserialize
// @param data
// @param v
// @date 2022-07-02 08:12:25
func (d *DefaultJSONSerializer) Deserialize(data []byte, v any) error {
return json.Unmarshal(data, v)
}

View File

@ -1,68 +0,0 @@
package redis
import (
"context"
"time"
"github.com/redis/go-redis/v9"
)
type Store struct {
store *redis.Client
}
// New
// @param conf
// @date 2022-07-02 08:12:14
func New(conf *redis.Options) *Store {
cli := redis.NewClient(conf)
return &Store{store: cli}
}
// NewWithDb
// @param tx
// @date 2022-07-02 08:12:12
func NewWithDb(tx *redis.Client) *Store {
return &Store{store: tx}
}
// Set
// @param ctx
// @param key
// @param value
// @param ttl
// @date 2022-07-02 08:12:11
func (r *Store) Set(ctx context.Context, key string, value any, ttl time.Duration) error {
return r.store.Set(ctx, key, value, ttl).Err()
}
// Get
// @param ctx
// @param key
// @date 2022-07-02 08:12:09
func (r *Store) Get(ctx context.Context, key string) ([]byte, error) {
return r.store.Get(ctx, key).Bytes()
}
// RemoveFromTag
// @param ctx
// @param tag
// @date 2022-07-02 08:12:08
func (r *Store) RemoveFromTag(ctx context.Context, tag string) error {
keys, err := r.store.SMembers(ctx, tag).Result()
if err != nil {
return err
}
return r.store.Del(ctx, keys...).Err()
}
// SaveTagKey
// @param ctx
// @param tag
// @param key
// @date 2022-07-02 08:12:05
func (r *Store) SaveTagKey(ctx context.Context, tag, key string) error {
return r.store.SAdd(ctx, tag, key).Err()
}

View File

@ -1,66 +0,0 @@
package kv
import (
"github.com/cockroachdb/pebble"
)
var Impl *KvImpl
type KvImpl struct {
PebbleDB *pebble.DB
}
func NewPebble(datadir string) *KvImpl {
db, err := pebble.Open(datadir, &pebble.Options{})
if err != nil {
panic(err)
}
return &KvImpl{
PebbleDB: db,
}
}
func (db *KvImpl) PebbleSet(key, val string) error {
return db.PebbleDB.Set([]byte(key), []byte(val), pebble.Sync)
}
func (db *KvImpl) PebbleGet(key string) ([]byte, error) {
value, _, err := db.PebbleDB.Get([]byte(key))
if err != nil {
return nil, err
}
return value, nil
}
func (db *KvImpl) PebbleFetch(prefixKey string) (result map[string]string, err error) {
keyUpperBound := func(b []byte) []byte {
end := make([]byte, len(b))
copy(end, b)
for i := len(end) - 1; i >= 0; i-- {
end[i] = end[i] + 1
if end[i] != 0 {
return end[:i+1]
}
}
return nil // no upper-bound
}
prefixIterOptions := func(prefix []byte) *pebble.IterOptions {
return &pebble.IterOptions{
LowerBound: prefix,
UpperBound: keyUpperBound(prefix),
}
}
iter, err := db.PebbleDB.NewIter(prefixIterOptions([]byte(prefixKey)))
if err != nil {
return nil, err
}
for iter.First(); iter.Valid(); iter.Next() {
result[string(iter.Key())] = string(iter.Value())
}
return
}

145
database/new.go Normal file
View File

@ -0,0 +1,145 @@
// Package database 提供数据库连接和管理功能
// 支持MySQL和PostgreSQL数据库包含连接池管理和自动迁移
package database
import (
"fmt"
"strings"
"git.apinb.com/bsm-sdk/core/database/sql"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/vars"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var (
// MigrateTables 存储需要在数据库初始化时自动迁移的表
MigrateTables []any
)
// NewDatabase 根据提供的驱动类型创建新的数据库连接
// 支持MySQL和PostgreSQL数据库
// driver: 数据库驱动类型 ("mysql" 或 "postgres")
// dsn: 数据源名称数组
// options: 数据库连接选项
// 返回: GORM数据库实例
func NewDatabase(driver string, dsn []string, options *types.SqlOptions) (db *gorm.DB, err error) {
driver = strings.ToLower(driver)
switch driver {
case "mysql":
db, err = NewMysql(dsn, options)
case "postgres":
db, err = NewPostgres(dsn, options)
default:
return nil, fmt.Errorf("unsupported database driver: %s", driver)
}
if err != nil {
return nil, err
}
// 自动迁移表结构
if len(MigrateTables) > 0 {
err = db.AutoMigrate(MigrateTables...)
if err != nil {
return nil, err
}
}
return db, nil
}
// NewMysql 创建MySQL数据库服务
// dsn: 数据源名称数组
// options: 数据库连接选项
// 返回: GORM数据库实例
func NewMysql(dsn []string, options *types.SqlOptions) (gormDb *gorm.DB, err error) {
// 设置连接默认值
if options == nil {
options = &types.SqlOptions{
MaxIdleConns: vars.SqlOptionMaxIdleConns,
MaxOpenConns: vars.SqlOptionMaxOpenConns,
ConnMaxLifetime: vars.SqlOptionConnMaxLifetime,
LogStdout: false,
Debug: true,
}
}
gormDb, err = gorm.Open(mysql.Open(dsn[0]), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return nil, err
}
if options.Debug {
gormDb = gormDb.Debug()
}
// 获取通用数据库对象 sql.DB然后使用其提供的功能
sqlDB, err := gormDb.DB()
if err != nil {
return nil, err
}
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量
sqlDB.SetMaxIdleConns(options.MaxIdleConns)
// SetMaxOpenConns 设置打开数据库连接的最大数量
sqlDB.SetMaxOpenConns(options.MaxOpenConns)
// SetConnMaxLifetime 设置了连接可复用的最大时间
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
return gormDb, nil
}
// NewPostgres 创建PostgreSQL数据库服务
// dsn: 数据源名称数组
// options: 数据库连接选项
// 返回: GORM数据库实例
func NewPostgres(dsn []string, options *types.SqlOptions) (gormDb *gorm.DB, err error) {
// 设置连接默认值
if options == nil {
options = &types.SqlOptions{
MaxIdleConns: vars.SqlOptionMaxIdleConns,
MaxOpenConns: vars.SqlOptionMaxOpenConns,
ConnMaxLifetime: vars.SqlOptionConnMaxLifetime,
LogStdout: false,
Debug: true,
}
}
gormDb, err = sql.NewPostgreSql(dsn[0], options)
if err != nil {
return nil, err
}
if options.Debug {
gormDb = gormDb.Debug()
}
// 获取通用数据库对象 sql.DB然后使用其提供的功能
sqlDB, err := gormDb.DB()
if err != nil {
return nil, err
}
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量
sqlDB.SetMaxIdleConns(options.MaxIdleConns)
// SetMaxOpenConns 设置打开数据库连接的最大数量
sqlDB.SetMaxOpenConns(options.MaxOpenConns)
// SetConnMaxLifetime 设置了连接可复用的最大时间
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
return gormDb, nil
}
// AppendMigrate 调用此函数后,会在数据库初始化时自动迁移表结构
//
// - table: 需要自动迁移的表
func AppendMigrate(table any) {
if MigrateTables == nil {
MigrateTables = make([]any, 0)
}
MigrateTables = append(MigrateTables, table)
}

View File

@ -8,6 +8,20 @@ import (
"gorm.io/gorm/schema"
)
func SetOptions(options *types.SqlOptions) *types.SqlOptions {
if options == nil {
options = &types.SqlOptions{
MaxIdleConns: vars.SqlOptionMaxIdleConns,
MaxOpenConns: vars.SqlOptionMaxOpenConns,
ConnMaxLifetime: vars.SqlOptionConnMaxLifetime,
LogStdout: false,
Debug: false,
}
}
return options
}
// new grom db.
func NewPostgreSql(dsn string, options *types.SqlOptions) (*gorm.DB, error) {
var err error
@ -16,7 +30,7 @@ func NewPostgreSql(dsn string, options *types.SqlOptions) (*gorm.DB, error) {
if options == nil {
options = &types.SqlOptions{
MaxIdleConns: vars.SqlOptionMaxIdleConns,
MaxOpenConns: vars.SqlOptionMaxIdleConns,
MaxOpenConns: vars.SqlOptionMaxOpenConns,
ConnMaxLifetime: vars.SqlOptionConnMaxLifetime,
LogStdout: false,
Debug: true,
@ -42,7 +56,11 @@ func NewPostgreSql(dsn string, options *types.SqlOptions) (*gorm.DB, error) {
}
// 获取通用数据库对象 sql.DB ,然后使用其提供的功能
sqlDB, _ := gormDb.DB()
sqlDB, err := gormDb.DB()
if err != nil {
return nil, err
}
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
sqlDB.SetMaxIdleConns(options.MaxIdleConns)
// SetMaxOpenConns 设置打开数据库连接的最大数量。
@ -51,4 +69,4 @@ func NewPostgreSql(dsn string, options *types.SqlOptions) (*gorm.DB, error) {
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
return gormDb, nil
}
}

62
env/env.go vendored
View File

@ -2,12 +2,38 @@ package env
import (
"os"
"path/filepath"
"strings"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/utils"
)
var Runtime *types.RuntimeEnv = nil
// get system env.
func NewEnv() *types.RuntimeEnv {
if Runtime == nil {
Runtime = &types.RuntimeEnv{
Workspace: GetEnvDefault("BSM_Workspace", "def"),
JwtSecretKey: GetEnvDefault("BSM_JwtSecretKey", "Cblocksmesh2022C"),
Mode: strings.ToLower(GetEnvDefault("BSM_RuntimeMode", "dev")),
LicencePath: strings.ToLower(GetEnvDefault("BSM_Licence", "")),
}
if Runtime.Mode == "dev" {
Runtime.Prefix = GetEnvDefault("BSM_Prefix", utils.GetCurrentPath())
} else {
Runtime.Prefix = GetEnvDefault("BSM_Prefix", "/usr/local/bsm")
}
if Runtime.LicencePath == "" {
Runtime.LicencePath = filepath.Join(Runtime.Prefix, "etc")
}
}
return Runtime
}
func GetEnvDefault(key string, def string) string {
value := os.Getenv(key)
if value == "" {
@ -15,39 +41,3 @@ func GetEnvDefault(key string, def string) string {
}
return value
}
var MeshEnv *types.MeshEnv = nil
// get system env.
func NewEnv() *types.MeshEnv {
if MeshEnv == nil {
MeshEnv = &types.MeshEnv{
Workspace: GetEnvDefault("BlocksMesh_Workspace", "def"),
JwtSecretKey: GetEnvDefault("BlocksMesh_JwtSecretKey", "Cblocksmesh2022C"),
RuntimeMode: strings.ToLower(GetEnvDefault("BlocksMesh_RuntimeMode", "dev")),
}
if MeshEnv.RuntimeMode == "dev" {
MeshEnv.Prefix = GetEnvDefault("BlocksMesh_Prefix", utils.GetCurrentPath())
} else {
MeshEnv.Prefix = GetEnvDefault("BlocksMesh_Prefix", "/usr/local/bsm")
}
}
return MeshEnv
}
// get system base env.
func NewBaseEnv() *types.MeshEnv {
if MeshEnv == nil {
MeshEnv = &types.MeshEnv{
RuntimeMode: strings.ToLower(GetEnvDefault("BlocksMesh_RuntimeMode", "dev")),
}
if MeshEnv.RuntimeMode == "dev" {
MeshEnv.Prefix = GetEnvDefault("BlocksMesh_Prefix", utils.GetCurrentPath())
} else {
MeshEnv.Prefix = GetEnvDefault("BlocksMesh_Prefix", "/usr/local/bsm")
}
}
return MeshEnv
}

View File

@ -1,101 +1,114 @@
// Package errcode 提供统一的错误码管理
// 定义了系统中所有可能的错误类型和对应的错误码
package errcode
import (
"github.com/gofiber/fiber/v2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// header error code ,start:100
// HTTP请求头相关错误码起始码:1000
var (
ErrHeaderRequestId = NewError(101, "Header Request-Id Not Found")
ErrHeaderAuthorization = NewError(102, "Header Authorization Not Found")
ErrHeaderSecretKey = NewError(103, "Header Secret-Key Not Found")
AllErrors = make(map[int]string)
ErrHeaderRequestId = NewError(1001, "Header Request-Id Not Found") // 请求ID头缺失
ErrHeaderAuthorization = NewError(1002, "Header Authorization Not Found") // 授权头缺失
ErrHeaderSecretKey = NewError(1003, "Header Secret-Key Not Found") // 密钥头缺失
ErrHeaderMustParams = NewError(1004, "Header Must Params") // 必需参数头缺失
)
// standard error code ,start:110
// 标准业务错误码,起始码:1100
var (
ErrRequestParse = NewError(111, "Request Parse Fail")
ErrRequestMust = NewError(112, "Request Params Required")
ErrPermission = NewError(113, "Permission Denied")
ErrJsonUnmarshal = NewError(114, "Json Unmarshal Fail")
ErrJsonMarshal = NewError(115, "Json Marshal Fail")
ErrInternal = NewError(116, "Internal Server Error")
ErrEmpty = NewError(1101, "Data Is Empty") // 数据为空
ErrRequestParse = NewError(1102, "Request Parse Fail") // 请求解析失败
ErrRequestMust = NewError(1103, "Request Params Required") // 请求参数必需
ErrPermission = NewError(1104, "Permission Denied") // 权限不足
ErrJsonUnmarshal = NewError(1105, "Json Unmarshal Fail") // JSON反序列化失败
ErrJsonMarshal = NewError(1106, "Json Marshal Fail") // JSON序列化失败
ErrInternal = NewError(1107, "Internal Server Error") // 内部服务器错误
ErrPassword = NewError(1108, "Password Incorrect") // 密码错误
ErrAccountDisabled = NewError(1110, "Account Disabled") // 账户已禁用
ErrDisabled = NewError(1111, "Status Disabled") // 状态已禁用
ErrRecordNotFound = NewError(1112, "Record Not Found") // 记录未找到
)
// jwt error code ,start:130
// Token认证相关错误码起始码:1300
var (
ErrJWTAuthNotFound = NewError(131, "JWT Authorization Not Found")
ErrJWTBase64Decode = NewError(132, "JWT Authorization Base64 Decode Error")
ErrJWTAuthParseFail = NewError(133, "JWT Authorization Fail")
ErrJWTAuthKeyId = NewError(134, "JWT Key:Id Incorrect")
ErrJWTAuthKeyIdentity = NewError(135, "JWT Key:Identity Incorrect")
ErrJWTAuthTokenChanged = NewError(136, "JWT Authorization Changed")
ErrJWTAuthExpire = NewError(137, "JWT Authorization Expire")
ErrJWTJsonDecode = NewError(138, "JWT Authorization JSON Decode Error")
ErrJWTJsonEncode = NewError(139, "JWT Authorization JSON Encode Error")
ErrTokenAuthNotFound = NewError(1301, "Token Authorization Not Found") // Token授权未找到
ErrTokenDataInvalid = NewError(1302, "Token Authorization Data Invalid") // Token授权数据无效
ErrTokenBase64Decode = NewError(1303, "Token Authorization Base64 Decode Error") // Token Base64解码错误
ErrTokenAuthParseFail = NewError(1304, "Token Authorization Fail") // Token授权解析失败
ErrTokenAuthKeyId = NewError(1305, "Token Key:Id Incorrect") // Token密钥ID错误
ErrTokenAuthKeyIdentity = NewError(1306, "Token Key:Identity Incorrect") // Token密钥身份错误
ErrTokenAuthTokenChanged = NewError(1307, "Token Authorization Changed") // Token授权已变更
ErrTokenAuthExpire = NewError(1308, "Token Authorization Expire") // Token授权已过期
ErrTokenJsonDecode = NewError(1309, "Token Authorization JSON Decode Error") // Token JSON解码错误
ErrTokenJsonEncode = NewError(1310, "Token Authorization JSON Encode Error") // Token JSON编码错误
ErrTokenSecretKey = NewError(1311, "Token SecretKey Error") // Token密钥错误
ErrTokenSecretKeyNotFound = NewError(1312, "Token SecretKey Not Found") // Token密钥未找到
ErrTokenGenerate = NewError(1313, "Generate Token Fail") // 生成令牌失败
ErrTokenParse = NewError(1314, "Parse Token Fail") // 解析令牌失败
)
// model error code ,start:150
// 基础设施相关错误码,起始码:1500
var (
ErrDB = NewError(151, "DB Fatal Error")
ErrRedis = NewError(152, "Redis Fatal Error")
ErrMq = NewError(153, "MQ Fatal Error")
ErrOss = NewError(154, "OSS Fatal Error")
ErrRpc = NewError(155, "RPC Fatal Error")
ErrApm = NewError(156, "APM Fatal Error")
ErrEtcd = NewError(157, "Etcd Fatal Error")
ErrDB = NewError(1501, "DB Fatal Error") // 数据库致命错误
ErrRedis = NewError(1502, "Redis Fatal Error") // Redis致命错误
ErrMq = NewError(1503, "MQ Fatal Error") // 消息队列致命错误
ErrOss = NewError(1504, "OSS Fatal Error") // 对象存储致命错误
ErrRpc = NewError(1505, "RPC Fatal Error") // RPC致命错误
ErrApm = NewError(1506, "APM Fatal Error") // 应用性能监控致命错误
ErrEtcd = NewError(1507, "Etcd Fatal Error") // Etcd致命错误
)
// google grpc error status.
// Google gRPC标准错误状态码起始码:1700
var (
OK = NewError(171, "OK")
ErrCanceled = NewError(172, "Canceled")
ErrUnknown = NewError(173, "Unknown")
ErrInvalidArgument = NewError(174, "Invalid Argument")
ErrDeadlineExceeded = NewError(175, "Deadline Exceeded")
ErrAlreadyExists = NewError(176, "Already Exists")
ErrPermissionDenied = NewError(177, "Permission Denied")
ErrResourceExhausted = NewError(178, "Resource Exhausted")
ErrFailedPrecondition = NewError(179, "Failed Precondition")
ErrAborted = NewError(181, "Aborted")
ErrOutOfRange = NewError(182, "Out Of Range")
ErrUnimplemented = NewError(183, "Unimplemented")
ErrUnavailable = NewError(184, "Unavailable")
ErrDataLoss = NewError(185, "Data Loss")
ErrUnauthenticated = NewError(186, "Unauthenticated")
ErrJSONMarshal = NewError(187, "Marshal JSON")
ErrJSONUnmarshal = NewError(188, "Unmarshal JSON")
ErrPasswd = NewError(189, "Password Error")
ErrSmsCode = NewError(191, "SMS Code Invalid")
ErrIdArgument = NewError(192, "ID Invalid Argument")
ErrIdentityArgument = NewError(193, "Identity Invalid Argument")
)
var (
ErrBase64Decode = NewError(201, "Auth Token Base64 Decode Error")
ErrAuthNotFound = NewError(202, "Auth Token Not Found")
ErrAuthParseFail = NewError(203, "Auth Parse Fail")
ErrAuthId = NewError(204, "Auth Id Not Passed")
ErrAuthIdentity = NewError(205, "Auth Identity Not Passed")
ErrAuthTokenChanged = NewError(206, "Auth Token Changed")
ErrAuthIdType = NewError(207, "Auth Id Type Error")
ErrAuthExpire = NewError(208, "Auth Token Expire")
ErrAuthClient = NewError(209, "Auth Token Client Not Passed")
ErrJsonDecode = NewError(210, "Auth JSON Decode Error")
ErrJsonEncode = NewError(211, "Auth JSON Encode Error")
ErrAuthSecret = NewError(212, "Auth JwtSecret Error")
ErrAccountNotFound = NewError(213, "Account Not Found")
OK = NewError(0, "OK") // 成功
ErrAccountNotFound = ErrNotFound(404, "Account") // 账户未找到
ErrCanceled = NewError(1702, "Canceled") // 操作已取消
ErrUnknown = NewError(1703, "Unknown") // 未知错误
ErrInvalidArgument = NewError(1704, "Invalid Argument") // 无效参数
ErrDeadlineExceeded = NewError(1705, "Deadline Exceeded") // 超时
ErrAlreadyExists = NewError(1706, "Already Exists") // 已存在
ErrPermissionDenied = NewError(1707, "Permission Denied") // 权限拒绝
ErrResourceExhausted = NewError(1708, "Resource Exhausted") // 资源耗尽
ErrFailedPrecondition = NewError(1709, "Failed Precondition") // 前置条件失败
ErrAborted = NewError(1710, "Aborted") // 操作中止
ErrOutOfRange = NewError(1711, "Out Of Range") // 超出范围
ErrUnimplemented = NewError(1712, "Unimplemented") // 未实现
ErrUnavailable = NewError(1713, "Unavailable") // 不可用
ErrDataLoss = NewError(1714, "Data Loss") // 数据丢失
ErrUnauthenticated = NewError(1715, "Unauthenticated") // 未认证
)
// NewError 创建新的错误实例
// code: 错误码
// msg: 错误消息
func NewError(code int, msg string) error {
return fiber.NewError(code, msg)
AllErrors[code] = msg
return status.New(codes.Code(code), msg).Err()
}
// custom error,status code:500
func ErrFatal(msg string) error {
return fiber.NewError(500, msg)
// ErrFatal 创建致命错误,状态码:500
// code: 错误码
// msg: 错误消息
func ErrFatal(code int, msg string) error {
AllErrors[code] = msg
return status.New(codes.Code(code), msg).Err()
}
func ErrNotFound(msg string) error {
return fiber.NewError(404, msg+" Not Found")
// ErrNotFound 创建未找到错误
// code: 错误码
// msg: 错误消息,会自动转换为大写
func ErrNotFound(code int, msg string) error {
AllErrors[code] = msg
return status.New(codes.Code(code), msg).Err()
}
// IsErr 检查错误是否与指定的错误匹配
func IsErr(err, target error) bool {
return status.Code(err) == status.Code(target)
}
func String(err error, msg string) error {
return status.New(status.Code(err), err.Error()+", "+msg).Err()
}

91
go.mod
View File

@ -1,92 +1,3 @@
module git.apinb.com/bsm-sdk/core
go 1.23.6
require (
github.com/FishGoddess/cachego v0.6.1
github.com/cockroachdb/pebble v1.1.4
github.com/elastic/go-elasticsearch/v8 v8.17.0
github.com/gofiber/fiber/v2 v2.52.6
github.com/gofiber/jwt/v4 v4.0.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/nats-io/nats.go v1.39.0
github.com/oklog/ulid/v2 v2.1.0
github.com/redis/go-redis/v9 v9.7.0
github.com/shirou/gopsutil v3.21.11+incompatible
go.etcd.io/etcd/client/v3 v3.5.18
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.25.12
)
require (
github.com/DataDog/zstd v1.4.5 // indirect
github.com/MicahParks/keyfunc/v2 v2.0.3 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/nats-io/nkeys v0.4.9 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.12.0 // indirect
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/etcd/api/v3 v3.5.18 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.18 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)
go 1.25.1

653
go.sum
View File

@ -1,653 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/FishGoddess/cachego v0.6.1 h1:mbytec3loqw5dcO177LyRGyStKG0AngP5g2GR3SzSh0=
github.com/FishGoddess/cachego v0.6.1/go.mod h1:VLSMwWRlRPazjGYer8pq+4aDrIe1Otj3Yy9HAb0eo3c=
github.com/MicahParks/keyfunc/v2 v2.0.3 h1:uKUEOc+knRO0UoucONisgNPiT85V2s/W5c0FQYsd9kc=
github.com/MicahParks/keyfunc/v2 v2.0.3/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.4 h1:5II1uEP4MyHLDnsrbv/EZ36arcb9Mxg3n+owhZ3GrG8=
github.com/cockroachdb/pebble v1.1.4/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA=
github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v8 v8.17.0 h1:e9cWksE/Fr7urDRmGPGp47Nsp4/mvNOrU8As1l2HQQ0=
github.com/elastic/go-elasticsearch/v8 v8.17.0/go.mod h1:lGMlgKIbYoRvay3xWBeKahAiJOgmFDsjZC39nmO3H64=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/jwt/v4 v4.0.0 h1:XlZ4H1omUZxX0AIE/esDBMNGiyI0kfaaHVAj8+7izAk=
github.com/gofiber/jwt/v4 v4.0.0/go.mod h1:vcEMSaWmPyP06Emfy5aBtqss8+t6gYkdX0pGytzdNbM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/nats.go v1.39.0 h1:2/yg2JQjiYYKLwDuBzV0FbB2sIV+eFNkEevlRi4n9lI=
github.com/nats-io/nats.go v1.39.0/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM=
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE=
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.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg=
github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y=
github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/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.5.18 h1:Q4oDAKnmwqTo5lafvB+afbgCDF7E35E4EYV2g+FNGhs=
go.etcd.io/etcd/api/v3 v3.5.18/go.mod h1:uY03Ob2H50077J7Qq0DeehjM/A9S8PhVfbQ1mSaMopU=
go.etcd.io/etcd/client/pkg/v3 v3.5.18 h1:mZPOYw4h8rTk7TeJ5+3udUkfVGBqc+GCjOJYd68QgNM=
go.etcd.io/etcd/client/pkg/v3 v3.5.18/go.mod h1:BxVf2o5wXG9ZJV+/Cu7QNUiJYk4A29sAhoI5tIRsCu4=
go.etcd.io/etcd/client/v3 v3.5.18 h1:nvvYmNHGumkDjZhTHgVU36A9pykGa2K4lAJ0yY7hcXA=
go.etcd.io/etcd/client/v3 v3.5.18/go.mod h1:kmemwOsPU9broExyhYsBxX4spCTDX3yLgPMWtpBXG6E=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34=
golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

44
infra/fts.go Normal file
View File

@ -0,0 +1,44 @@
package infra
import (
"bytes"
"io"
"net/http"
"git.apinb.com/bsm-sdk/core/errcode"
)
var FTS fts
type fts struct {
Endpoint string
}
func (o *fts) SaveTo(buf *bytes.Buffer, haeder map[string]string) (string, error) {
// 创建一个 POST 请求
req, err := http.NewRequest("POST", o.Endpoint, buf)
if err != nil {
return "", errcode.NewError(110, "Request to the URL")
}
// 设置请求头为二进制流
req.Header.Set("Content-Type", "application/octet-stream")
for key, val := range haeder {
req.Header.Set(key, val)
}
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", errcode.NewError(111, "Client Do")
}
defer resp.Body.Close()
// 读取响应体
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", errcode.NewError(112, "Read response body")
}
return string(respBody), nil
}

9
infra/health.go Normal file
View File

@ -0,0 +1,9 @@
package infra
import (
"github.com/gin-gonic/gin"
)
func Health(ctx *gin.Context) {
ctx.String(200, "ok", nil)
}

37
infra/logs.go Normal file
View File

@ -0,0 +1,37 @@
package infra
import (
"encoding/json"
"git.apinb.com/bsm-sdk/core/utils"
)
type LogItem struct {
OpID uint `json:"op_id"`
OpName string `json:"op_name"`
OpType string `json:"op_type"`
Text string `json:"text"`
Code string `json:"code"`
Level uint `json:"level"`
Ip string `json:"ip"`
Module string `json:"module"`
Encry bool `json:"encry"`
}
var (
Type_Login string = "login"
Type_Logout string = "logout"
Type_Register string = "register"
Type_Update string = "update"
Type_Delete string = "delete"
Type_Query string = "query"
Type_Other string = "other"
Type_Create string = "create"
)
func PushLog(endpoint string, data []*LogItem) {
jsonBytes, _ := json.Marshal(data)
go utils.HttpPost(endpoint, nil, jsonBytes)
}

53
infra/response.go Normal file
View File

@ -0,0 +1,53 @@
// Package infra 提供基础设施功能
// 包括统一响应处理、健康检查、日志等
package infra
import (
"time"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/status"
)
var Response Reply
// Reply 统一响应结构体
type Reply struct {
Code int32 `json:"code"` // 响应码
Message string `json:"message"` // 响应消息
Details any `json:"details"` // 响应数据
Timeseq int64 `json:"timeseq"` // 时间戳序列
}
// Success 返回成功响应
// ctx: Gin上下文
// data: 响应数据
func (reply *Reply) Success(ctx *gin.Context, data any) {
reply.Code = 0
reply.Details = data
reply.Message = ""
reply.Timeseq = time.Now().UnixMilli()
if data == nil {
reply.Details = ""
}
ctx.JSON(200, reply)
}
// Error 返回错误响应
// ctx: Gin上下文
// err: 错误对象
func (reply *Reply) Error(ctx *gin.Context, err error) {
reply.Code = 500
reply.Details = ""
// 默认状态码为500
e, ok := status.FromError(err)
if ok {
reply.Code = int32(e.Code())
reply.Message = e.Message()
} else {
reply.Message = err.Error()
}
// 发送错误响应
ctx.JSON(200, reply)
}

54
infra/service.go Normal file
View File

@ -0,0 +1,54 @@
package infra
import (
"context"
"log"
"time"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/utils"
clientv3 "go.etcd.io/etcd/client/v3"
)
var (
Service service
RootPrefix string = "/bsm_services/"
)
type service struct{}
func (s *service) Register(cli *clientv3.Client, serviceName string, port string) error {
lease := clientv3.NewLease(cli)
grantResp, err := lease.Grant(context.TODO(), 5)
if err != nil {
return err
}
serviceAddr := utils.GetLocationIP() + ":" + port
key := RootPrefix + serviceName + "/" + utils.Int642String(time.Now().UnixNano())
_, err = cli.KV.Put(context.TODO(), key, serviceAddr, clientv3.WithLease(grantResp.ID))
if err != nil {
return err
}
keepAliveChan, err := lease.KeepAlive(context.TODO(), grantResp.ID)
if err != nil {
return err
}
printer.Info("[BSM Register] Service Key: %s", key)
printer.Info("[BSM Register] Service Val: %s", serviceAddr)
printer.Success("[BSM Register] Service Register Complete.")
go func() {
for keepAliveResp := range keepAliveChan {
if keepAliveResp == nil {
log.Println("LeaseKeepAlive", "Failed")
return
}
}
}()
return nil
}

296
licence/licence.go Normal file
View File

@ -0,0 +1,296 @@
// 校验授权文件
package licence
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"os"
"path"
"sort"
"strconv"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/crypto/aes"
"git.apinb.com/bsm-sdk/core/utils"
"github.com/shirou/gopsutil/cpu"
)
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// CPU 信息
type CpuInfo struct {
Cpu int32 `json:"cpu"`
Cores int32 `json:"cores"`
ModelName string `json:"modelName"`
VendorId string `json:"vendorId"`
PhysicalId string `json:"physicalId"`
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
type MachineInfo struct {
MacAddrs []string `json:"macAddrs"`
Cpus []*CpuInfo `json:"cpus"`
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 授权信息
type Licence struct {
CompanyName string `json:"licence_to"` // 授权公司
CreateDate int `json:"create"` // 生效日期
ExpireDate int `json:"expire"` // 有效期
MachineCodes []string `json:"machine_codes"` // 机器码列表
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
var (
des_key string
des_iv string
Check_Licence_File bool = true // 是否检查部署授权文件
)
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
const (
signKey = "1F36659EC27CFFF849E068EA80B1A4CA"
LICENCE_KEY = "BLOCKS_KEY"
)
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
func init() {
des_key = base64.StdEncoding.EncodeToString([]byte(signKey))
des_iv = base64.StdEncoding.EncodeToString([]byte(signKey[:16]))
}
func WatchCheckLicence(licPath, licName string) {
utils.SetInterval(func() {
if CheckLicence(licPath, licName) == false {
log.Println("授权文件失效,请重新部署授权文件:", licPath)
os.Exit(99)
}
}, time.Hour*1)
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
func CheckLicence(licPath, licName string) bool {
// 加载授权文件
content, err := LoadLicenceFromFile(licPath)
if err != nil {
return false
}
// 解密解码授权文件
var l Licence
if err := DecodeLicence(content, &l); err != nil {
return false
}
return l.VerifyLicence(licName)
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Licence 校验
func (l *Licence) VerifyLicence(licName string) bool {
// 用于开发环境,为授权公司时跳过验证
if l.CompanyName == licName {
return true
}
today := StrToInt(time.Now().Format("20060102"))
// 机器日期不在授权文件有限期之内 (早于生效日期,或超过有效期)
if (today < l.CreateDate) || (today > l.ExpireDate) {
return false
}
// 机器码不在授权列表中
machine_code := GetMachineCode()
if (len(machine_code) == 0) || !l.ValidMachineCode(machine_code) {
return false
}
return true
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 检查机器码是否存在授权列表中
func (l *Licence) ValidMachineCode(code string) bool {
result := false
for _, c := range l.MachineCodes {
if c == code {
result = true
break
}
}
return result
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 加载授权文件
func LoadLicenceFromFile(licPath string) (string, error) {
key_path := path.Join(licPath, "licence.key")
if utils.PathExists(key_path) {
file, err := os.Open(key_path)
if err != nil {
return "", err
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
return "", err
}
return string(content), nil
}
return "", errors.New("授权文件不存在")
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 解密授权文件内容,并析构至 Licence
func DecodeLicence(content string, licence *Licence) error {
if len(content) == 0 {
return errors.New("授权文件无效")
}
if txt := DecryptStr(content); len(txt) > 0 {
if err := json.Unmarshal([]byte(txt), licence); err != nil {
return err
}
}
return nil
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 字符串解密
func DecryptStr(txt string) string {
result := ""
if len(txt) > 0 {
result = aes.Decrypt(des_key, des_iv, txt)
}
return result
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 加密字符串
func EncryptStr(txt string) string {
result := ""
if len(txt) > 0 {
result = aes.Encrypt(des_key, des_iv, txt)
}
return result
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 生成授权文件
func BuildLicence(company_name string, Create_date int, expire_date int, machine_codes []string) (string, error) {
// 构造licence
licence := &Licence{
CompanyName: company_name,
CreateDate: Create_date,
ExpireDate: expire_date,
MachineCodes: machine_codes,
}
content, err := json.Marshal(licence)
if err != nil {
return "", err
}
// 对json加密
result := EncryptStr(string(content))
return result, nil
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 生成激活码
func GetLicenceCode(machinceCode string) string {
return hash256(machinceCode + "&" + signKey)
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 获取CPU信息
func getCpuInfo() []*CpuInfo {
cpus, _ := cpu.Info()
cinfos := make([]*CpuInfo, len(cpus))
for k, v := range cpus {
cinfos[k] = &CpuInfo{
Cpu: v.CPU,
ModelName: v.ModelName,
Cores: v.Cores,
VendorId: v.VendorID,
PhysicalId: v.PhysicalID,
}
}
sort.SliceStable(cinfos, func(i, j int) bool {
if cinfos[i].Cores != cinfos[j].Cores {
return cinfos[i].Cores > cinfos[j].Cores
}
return cinfos[i].Cpu < cinfos[j].Cpu
})
return cinfos
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 获取MAC地址
func getMacAddrs() []string {
var macs []string
netfaces, err := net.Interfaces()
if err != nil {
return macs
}
for i := 0; i < len(netfaces); i++ {
if (netfaces[i].Flags&net.FlagUp) != 0 && (netfaces[i].Flags&net.FlagLoopback) == 0 {
addrs, _ := netfaces[i].Addrs()
for _, address := range addrs {
ipnet, ok := address.(*net.IPNet)
if ok && ipnet.IP.IsGlobalUnicast() {
macs = append(macs, netfaces[i].HardwareAddr.String())
}
}
}
}
sort.Slice(macs, func(i, j int) bool {
return macs[i] > macs[j]
})
return macs
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 获取机器码
func GetMachineCode() string {
info := MachineInfo{MacAddrs: getMacAddrs(), Cpus: getCpuInfo()}
json, _ := json.Marshal(info)
machineCode := hash256(string(json))
licenceCode := GetLicenceCode(machineCode)
return licenceCode
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// 加密
func hash256(d string) string {
h := sha256.New()
h.Write([]byte(d))
bs := fmt.Sprintf("%x", h.Sum(nil))
return strings.ToUpper(bs)
}
// --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
func StrToInt(valstr string) int {
val, err := strconv.Atoi(valstr)
if err != nil {
val = 0
}
return val
}

422
logger/logger.go Normal file
View File

@ -0,0 +1,422 @@
package logger
import (
"encoding/json"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
"git.apinb.com/bsm-sdk/core/conf"
"git.apinb.com/bsm-sdk/core/utils"
"git.apinb.com/bsm-sdk/core/vars"
)
// Logger 日志器结构
type Logger struct {
level vars.LogLevel
infoLogger *log.Logger
warnLogger *log.Logger
errorLogger *log.Logger
fatalLogger *log.Logger
debugLogger *log.Logger
fileWriter io.Writer
consoleWriter io.Writer
mu sync.RWMutex
name string
logDir string
currentDate string
onRemote bool
endpoint string
}
var (
globalLogger *Logger
once sync.Once
)
// 初始化Logger配置
func New(cfg *conf.LogConf) {
if cfg == nil {
cfg = &conf.LogConf{
Name: strings.ToLower(vars.ServiceKey),
Level: vars.LogLevel(vars.DEBUG),
Dir: "./logs/",
Endpoint: "",
Console: true,
File: true,
Remote: false,
}
}
InitLogger(cfg)
}
// InitLogger 初始化全局日志器
func InitLogger(cfg *conf.LogConf) error {
var err error
once.Do(func() {
globalLogger, err = NewLogger(cfg)
})
return err
}
// NewLogger 创建新的日志器
func NewLogger(cfg *conf.LogConf) (*Logger, error) {
// 确保日志目录存在
if err := os.MkdirAll(cfg.Dir, 0755); err != nil {
return nil, fmt.Errorf("创建日志目录失败: %v", err)
}
// 控制台输出
consoleWriter := os.Stdout
// 文件输出
fileWriter, err := createLogFile(cfg.Dir, cfg.Name)
if err != nil {
return nil, fmt.Errorf("创建日志文件失败: %v", err)
}
// 创建多输出写入器
multiWriter := io.MultiWriter(consoleWriter, fileWriter)
logger := &Logger{
level: cfg.Level,
fileWriter: fileWriter,
consoleWriter: consoleWriter,
logDir: cfg.Dir,
name: strings.ToLower(cfg.Name),
currentDate: time.Now().Format("2006-01-02"),
onRemote: cfg.Remote,
endpoint: cfg.Endpoint,
}
// 创建不同级别的日志器
logger.infoLogger = log.New(multiWriter, "[INFO] ", log.LstdFlags)
logger.warnLogger = log.New(multiWriter, "[WARN] ", log.LstdFlags)
logger.errorLogger = log.New(multiWriter, "[ERROR] ", log.LstdFlags)
logger.fatalLogger = log.New(multiWriter, "[FATAL] ", log.LstdFlags)
logger.debugLogger = log.New(multiWriter, "[DEBUG] ", log.LstdFlags)
return logger, nil
}
// createLogFile 创建日志文件
func createLogFile(logDir, name string) (io.Writer, error) {
filename := fmt.Sprintf("%s_%s.log", name, time.Now().Format("2006-01-02"))
filepath := filepath.Join(logDir, filename)
file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return nil, err
}
return file, nil
}
// checkAndRotateLog 检查并轮转日志文件
func (l *Logger) checkAndRotateLog() error {
today := time.Now().Format("2006-01-02")
if l.currentDate != today {
l.mu.Lock()
defer l.mu.Unlock()
if l.currentDate != today {
// 关闭旧文件
if closer, ok := l.fileWriter.(io.Closer); ok {
closer.Close()
}
// 创建新文件
newFileWriter, err := createLogFile(l.logDir, l.name)
if err != nil {
return err
}
l.fileWriter = newFileWriter
l.currentDate = today
// 重新创建多输出写入器
multiWriter := io.MultiWriter(l.consoleWriter, l.fileWriter)
l.infoLogger = log.New(multiWriter, "[INFO] ", log.LstdFlags)
l.warnLogger = log.New(multiWriter, "[WARN] ", log.LstdFlags)
l.errorLogger = log.New(multiWriter, "[ERROR] ", log.LstdFlags)
l.fatalLogger = log.New(multiWriter, "[FATAL] ", log.LstdFlags)
l.debugLogger = log.New(multiWriter, "[DEBUG] ", log.LstdFlags)
}
}
return nil
}
func (l *Logger) sendToRemote(level, name, out string) {
if l.endpoint == "" {
return
}
data := map[string]interface{}{
"level": level,
"name": name,
"out": out,
}
jsonBytes, _ := json.Marshal(data)
utils.HttpPost(l.endpoint, nil, jsonBytes)
}
// Debug 输出调试信息
func (l *Logger) Debug(v ...interface{}) {
if l.level <= vars.DEBUG {
l.checkAndRotateLog()
out := fmt.Sprint(v...)
if l.onRemote {
go l.sendToRemote("DEBUG", l.name, out)
}
l.debugLogger.Output(2, out)
}
}
// Debugf 格式化输出调试信息
func (l *Logger) Debugf(format string, v ...interface{}) {
if l.level <= vars.DEBUG {
l.checkAndRotateLog()
out := fmt.Sprintf(format, v...)
if l.onRemote {
go l.sendToRemote("DEBUG", l.name, out)
}
l.debugLogger.Output(2, out)
}
}
// Info 输出信息
func (l *Logger) Info(v ...interface{}) {
if l.level <= vars.INFO {
l.checkAndRotateLog()
out := fmt.Sprint(v...)
if l.onRemote {
go l.sendToRemote("INFO", l.name, out)
}
l.infoLogger.Output(2, out)
}
}
// Infof 格式化输出信息
func (l *Logger) Infof(format string, v ...interface{}) {
if l.level <= vars.INFO {
l.checkAndRotateLog()
out := fmt.Sprintf(format, v...)
if l.onRemote {
go l.sendToRemote("INFO", l.name, out)
}
l.infoLogger.Output(2, out)
}
}
// Warn 输出警告
func (l *Logger) Warn(v ...interface{}) {
if l.level <= vars.WARN {
l.checkAndRotateLog()
out := fmt.Sprint(v...)
if l.onRemote {
go l.sendToRemote("WARN", l.name, out)
}
l.warnLogger.Output(2, out)
}
}
// Warnf 格式化输出警告
func (l *Logger) Warnf(format string, v ...interface{}) {
if l.level <= vars.WARN {
l.checkAndRotateLog()
out := fmt.Sprintf(format, v...)
if l.onRemote {
go l.sendToRemote("WARN", l.name, out)
}
l.warnLogger.Output(2, out)
}
}
// Error 输出错误
func (l *Logger) Error(v ...interface{}) {
if l.level <= vars.ERROR {
l.checkAndRotateLog()
out := fmt.Sprint(v...)
if l.onRemote {
go l.sendToRemote("ERROR", l.name, out)
}
l.errorLogger.Output(2, out)
}
}
// Errorf 格式化输出错误
func (l *Logger) Errorf(format string, v ...interface{}) {
if l.level <= vars.ERROR {
l.checkAndRotateLog()
out := fmt.Sprintf(format, v...)
if l.onRemote {
go l.sendToRemote("ERROR", l.name, out)
}
l.errorLogger.Output(2, out)
}
}
// Fatal 输出致命错误并退出程序
func (l *Logger) Fatal(v ...interface{}) {
l.checkAndRotateLog()
out := fmt.Sprint(v...)
if l.onRemote {
go l.sendToRemote("FATAL", l.name, out)
}
l.fatalLogger.Output(2, out)
os.Exit(1)
}
// Fatalf 格式化输出致命错误并退出程序
func (l *Logger) Fatalf(format string, v ...interface{}) {
l.checkAndRotateLog()
out := fmt.Sprintf(format, v...)
if l.onRemote {
go l.sendToRemote("FATAL", l.name, out)
}
l.fatalLogger.Output(2, out)
os.Exit(1)
}
// Print 输出信息兼容标准log包
func (l *Logger) Print(v ...interface{}) {
l.Info(v...)
}
// Printf 格式化输出信息兼容标准log包
func (l *Logger) Printf(format string, v ...interface{}) {
l.Infof(format, v...)
}
// Println 输出信息并换行兼容标准log包
func (l *Logger) Println(v ...interface{}) {
l.Info(v...)
}
// SetLevel 设置日志级别
func (l *Logger) SetLevel(level vars.LogLevel) {
l.mu.Lock()
defer l.mu.Unlock()
l.level = level
}
// GetLevel 获取日志级别
func (l *Logger) GetLevel() vars.LogLevel {
l.mu.RLock()
defer l.mu.RUnlock()
return l.level
}
// Close 关闭日志器
func (l *Logger) Close() error {
if closer, ok := l.fileWriter.(io.Closer); ok {
return closer.Close()
}
return nil
}
// 全局日志函数兼容标准log包
// Debug 全局调试日志
func Debug(v ...interface{}) {
if globalLogger != nil {
globalLogger.Debug(v...)
}
}
// Debugf 全局调试日志
func Debugf(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Debugf(format, v...)
}
}
// Info 全局信息日志
func Info(v ...interface{}) {
if globalLogger != nil {
globalLogger.Info(v...)
}
}
// Infof 全局信息日志
func Infof(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Infof(format, v...)
}
}
// Warn 全局警告日志
func Warn(v ...interface{}) {
if globalLogger != nil {
globalLogger.Warn(v...)
}
}
// Warnf 全局警告日志
func Warnf(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Warnf(format, v...)
}
}
// Error 全局错误日志
func Error(v ...interface{}) {
if globalLogger != nil {
globalLogger.Error(v...)
}
}
// Errorf 全局错误日志
func Errorf(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Errorf(format, v...)
}
}
// Fatal 全局致命错误日志
func Fatal(v ...interface{}) {
if globalLogger != nil {
globalLogger.Fatal(v...)
}
}
// Fatalf 全局致命错误日志
func Fatalf(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Fatalf(format, v...)
}
}
// Print 全局打印日志兼容标准log包
func Print(v ...interface{}) {
if globalLogger != nil {
globalLogger.Print(v...)
}
}
// Printf 全局打印日志兼容标准log包
func Printf(format string, v ...interface{}) {
if globalLogger != nil {
globalLogger.Printf(format, v...)
}
}
// Println 全局打印日志兼容标准log包
func Println(v ...interface{}) {
if globalLogger != nil {
globalLogger.Println(v...)
}
}
// GetLogger 获取全局日志器实例
func GetLogger() *Logger {
return globalLogger
}

18
middleware/cors.go Normal file
View File

@ -0,0 +1,18 @@
package middleware
import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func Cors() gin.HandlerFunc {
return cors.New(cors.Config{
AllowAllOrigins: true,
AllowHeaders: []string{
"Origin", "Content-Length", "Content-Type", "Workspace", "Request-Id", "Authorization", "Token",
},
AllowMethods: []string{
"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
},
})
}

88
middleware/jwt.go Normal file
View File

@ -0,0 +1,88 @@
// Package middleware 提供HTTP中间件功能
// 包括JWT认证、CORS、运行模式等中间件
package middleware
import (
"encoding/json"
"log"
"net/http"
"git.apinb.com/bsm-sdk/core/crypto/token"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"github.com/gin-gonic/gin"
)
// JwtAuth JWT认证中间件
// time_verify: 是否验证token过期时间
// 返回: Gin中间件函数
func JwtAuth(time_verify bool) gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头中获取 Authorization
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
log.Printf("获取token异常:%v\n", "Authorization header is required")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
c.Abort()
return
}
// 检测是否需要验证token时间
if time_verify {
// 判断时间claims.ExpiresAt
isExpire, err := token.New(env.Runtime.JwtSecretKey).IsExpired(authHeader)
if err != nil {
log.Println("token解析异常:", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
if isExpire {
log.Println("token过期请重新获取:", "Token has expired")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token has expired"})
c.Abort()
return
}
}
// 提取Token
claims, err := token.New(env.Runtime.JwtSecretKey).ParseJwt(authHeader)
if err != nil || claims == nil {
log.Printf("提取token异常:%v\n", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
// 将解析后的 Token 存储到上下文中
c.Set("Auth", claims)
// 如果 Token 有效,继续处理请求
c.Next()
}
}
// ParseAuth 获取上下文用户登录信息
// c: Gin上下文
// 返回: JWT声明信息
func ParseAuth(c *gin.Context) (*types.JwtClaims, error) {
claims, ok := c.Get("Auth")
if !ok {
log.Printf("获取登录信息异常: %v", errcode.ErrTokenAuthNotFound)
return nil, errcode.ErrTokenAuthNotFound
}
json_claims, err := json.Marshal(claims)
if err != nil {
log.Printf("解析json异常: %v", err)
return nil, errcode.ErrJsonMarshal
}
var auth *types.JwtClaims
if err := json.Unmarshal(json_claims, &auth); err != nil {
log.Printf("解析json异常: %v", err)
return nil, errcode.ErrJsonUnmarshal
}
return auth, nil
}

16
middleware/mode.go Normal file
View File

@ -0,0 +1,16 @@
package middleware
import (
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/vars"
"github.com/gin-gonic/gin"
)
func Mode(app *gin.Engine) {
// 设置gin模式
if env.Runtime.Mode == vars.RUN_MODE_PROD {
gin.SetMode(gin.ReleaseMode)
} else {
gin.SetMode(gin.DebugMode)
}
}

View File

@ -1,72 +0,0 @@
package nats
import (
"errors"
"strings"
"git.apinb.com/bsm-sdk/core/vars"
natsgo "github.com/nats-io/nats.go"
)
type Nats struct {
Client natsgo.JetStreamContext
}
func NewNats(endpoints []string, space string) (*Nats, error) {
if len(endpoints) == 0 {
return nil, errors.New("endpoints is empty")
}
if space == "" {
space = vars.MQSpaceName
}
jetStream, err := NatsNew(endpoints, space)
if err != nil {
return nil, err
}
return &Nats{
Client: jetStream,
}, nil
}
func NatsNew(endpoints []string, space string) (natsgo.JetStreamContext, error) {
var serverUrl string
if len(endpoints) > 1 {
serverUrl = strings.Join(endpoints, ",")
} else {
serverUrl = endpoints[0]
}
conn, err := natsgo.Connect(serverUrl, natsgo.DontRandomize())
if err != nil {
return nil, err
}
defer conn.Close()
js, err := conn.JetStream(natsgo.PublishAsyncMaxPending(256))
if err != nil {
return nil, err
}
js.AddStream(&natsgo.StreamConfig{
Name: space,
Subjects: []string{space}, //jetstream不支持通配符
Retention: natsgo.WorkQueuePolicy,
MaxBytes: 8,
})
return js, nil
}
func (mq *Nats) Subscribe(topic string, do func([]byte)) (err error) {
_, err = mq.Client.Subscribe(topic, func(m *natsgo.Msg) {
do(m.Data)
m.Ack()
})
return
}
func (mq *Nats) Producer(topic string, data []byte) (err error) {
_, err = mq.Client.Publish(topic, data)
return
}

View File

@ -1,30 +1,48 @@
package print
package printer
import (
"bytes"
"encoding/json"
"fmt"
"log"
"os"
)
var logger *log.Logger
func init() {
logger = log.New(os.Stdout, "", 0) // 创建一个新的 Logger 实例,不带任何标志
logger.SetFlags(log.Ldate | log.Ltime)
}
// record INFO message. Color White
func Info(format string, a ...interface{}) {
message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...)
log.Print(message)
logger.Print(message)
}
// record Warn message. Color Orange
func Warn(format string, a ...interface{}) {
message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...)
log.Print(message)
logger.Print(message)
}
// record Success message. Color Green
func Success(format string, a ...interface{}) {
message := fmt.Sprintf("\033[32m[Success] "+format+"\033[0m\n", a...)
log.Print(message)
message := fmt.Sprintf("\033[32m[Succ] "+format+"\033[0m\n", a...)
logger.Print(message)
}
// record ERROR message. Color Red
func Error(format string, a ...interface{}) {
message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...)
log.Print(message)
logger.Print(message)
}
func Json(v any) {
jsonBy, _ := json.Marshal(v)
var out bytes.Buffer
json.Indent(&out, jsonBy, "", "\t")
out.WriteTo(os.Stdout)
fmt.Printf("\n")
}

View File

@ -3,7 +3,8 @@ package nats
import (
"strings"
"git.apinb.com/bsm-sdk/engine/vars"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/vars"
natsgo "github.com/nats-io/nats.go"
)
@ -11,8 +12,14 @@ type Nats struct {
Client natsgo.JetStreamContext
}
func NewNats(endpoints []string) (*Nats, error) {
jetStream, err := NatsNew(endpoints)
func NewNats(endpoints []string, space string) (*Nats, error) {
if len(endpoints) == 0 {
return nil, errcode.ErrMq
}
if space == "" {
space = vars.MQSpaceName
}
jetStream, err := NatsNew(endpoints, space)
if err != nil {
return nil, err
}
@ -22,7 +29,7 @@ func NewNats(endpoints []string) (*Nats, error) {
}, nil
}
func NatsNew(endpoints []string) (natsgo.JetStreamContext, error) {
func NatsNew(endpoints []string, space string) (natsgo.JetStreamContext, error) {
var serverUrl string
if len(endpoints) > 1 {
serverUrl = strings.Join(endpoints, ",")
@ -42,8 +49,8 @@ func NatsNew(endpoints []string) (natsgo.JetStreamContext, error) {
}
js.AddStream(&natsgo.StreamConfig{
Name: vars.MQSpaceName,
Subjects: []string{vars.MQSpaceName}, //jetstream不支持通配符
Name: space,
Subjects: []string{space}, //jetstream不支持通配符
Retention: natsgo.WorkQueuePolicy,
MaxBytes: 8,
})

View File

@ -1,88 +0,0 @@
package pulsar
import (
"context"
"errors"
"git.apinb.com/bsm-sdk/engine/types"
"git.apinb.com/bsm-sdk/engine/vars"
pulsargo "github.com/apache/pulsar-client-go/pulsar"
)
type Pulsar struct {
Client pulsargo.Client
}
func NewPulsar(cfg *types.PulsarConf) (*Pulsar, error) {
client, err := pulsargo.NewClient(pulsargo.ClientOptions{
URL: cfg.Endpoints, //TODO: 更换为接入点地址(控制台集群管理页完整复制)
Authentication: pulsargo.NewAuthenticationToken(cfg.Token),
OperationTimeout: vars.OperationTimeout,
ConnectionTimeout: vars.ConnectionTimeout,
})
if err != nil {
return nil, err
}
return &Pulsar{
Client: client,
}, nil
}
// push to pulsar server, return messageid.
func (mq *Pulsar) Producer(topic, msg string) (MessageID string, err error) {
if msg == "" {
return "", errors.New("Message is nil.")
}
producer, err := mq.Client.CreateProducer(pulsargo.ProducerOptions{
Topic: topic,
CompressionType: pulsargo.ZSTD,
})
if err != nil {
return "", err
}
msgID, err := producer.Send(context.Background(), &pulsargo.ProducerMessage{
Payload: []byte(msg),
})
if err != nil {
return "", err
}
return msgID.String(), nil
}
func (mq *Pulsar) Subscribe(topic, subscription string, subType pulsargo.SubscriptionType, do func([]byte)) error {
// we can listen this channel
channel := make(chan pulsargo.ConsumerMessage, 100)
options := pulsargo.ConsumerOptions{
Topic: topic,
SubscriptionName: subscription,
Type: subType,
// fill `MessageChannel` field will create a listener
MessageChannel: channel,
}
consumer, err := mq.Client.Subscribe(options)
if err != nil {
return err
}
defer consumer.Close()
// Receive messages from channel. The channel returns a struct `ConsumerMessage` which contains message and the consumer from where
// the message was received. It's not necessary here since we have 1 single consumer, but the channel could be
// shared across multiple consumers as well
for cm := range channel {
consumer := cm.Consumer
msg := cm.Message
do(msg.Payload())
consumer.Ack(msg)
}
return nil
}

204
service/address.go Normal file
View File

@ -0,0 +1,204 @@
package service
import (
"fmt"
"net"
"net/url"
"strings"
"git.apinb.com/bsm-sdk/core/utils"
)
type NetworkAddress struct {
Protocol string // tcp, tcp4, tcp6, unix, unixpacket
Host string // IP 地址或主机名
Port string // 端口号
Path string // Unix socket 路径
Raw string // 原始字符串
}
// 解析网络地址字符串
// "tcp://0.0.0.0:1212",
//
// "tcp4://127.0.0.1:8080",
// "tcp6://[::1]:8080",
// "unix:///data/app/passport.sock",
// "unixpacket:///tmp/mysql.sock",
// ":8080", // 传统格式
// "/tmp/server.sock", // 传统Unix格式
// "invalid://address", // 错误格式
func ParseNetworkAddress(addr string) (*NetworkAddress, error) {
// 如果包含 ://,按 URL 解析
if strings.Contains(addr, "://") {
return parseURLStyle(addr)
}
// 否则按传统格式解析
return parseTraditionalStyle(addr)
}
// 解析 tcp://0.0.0.0:1212 或 unix:///path/to/socket 格式
func parseURLStyle(addr string) (*NetworkAddress, error) {
u, err := url.Parse(addr)
if err != nil {
return nil, fmt.Errorf("解析URL失败: %w", err)
}
result := &NetworkAddress{
Protocol: u.Scheme,
Raw: addr,
}
switch u.Scheme {
case "tcp", "tcp4", "tcp6":
return parseTCPURL(u, result)
case "unix", "unixpacket":
return parseUnixURL(u, result)
default:
return nil, fmt.Errorf("不支持的协议: %s", u.Scheme)
}
}
// 解析 TCP 类型的 URL
func parseTCPURL(u *url.URL, result *NetworkAddress) (*NetworkAddress, error) {
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// 如果没有端口,尝试添加默认端口
if strings.Contains(err.Error(), "missing port") {
host = u.Host
port = "0" // 默认端口
} else {
return nil, fmt.Errorf("解析TCP地址失败: %w", err)
}
}
result.Host = host
result.Port = port
// 根据主机地址确定具体的协议类型
if result.Protocol == "tcp" {
result.Protocol = determineTCPProtocol(host)
}
return result, nil
}
// 解析 Unix socket 类型的 URL
func parseUnixURL(u *url.URL, result *NetworkAddress) (*NetworkAddress, error) {
// Unix socket 路径在 URL 的 Path 字段
if u.Path == "" {
return nil, fmt.Errorf("Unix socket 路径不能为空")
}
result.Path = u.Path
// 如果协议是 unix但路径表明需要数据包传输可以自动升级
if result.Protocol == "unix" && strings.Contains(u.Path, "packet") {
result.Protocol = "unixpacket"
}
return result, nil
}
// 根据主机地址确定 TCP 协议类型
func determineTCPProtocol(host string) string {
if host == "" {
return "tcp" // 默认
}
// 解析 IP 地址
ip := net.ParseIP(host)
if ip != nil {
if ip.To4() != nil {
return "tcp4"
}
return "tcp6"
}
// 如果是特殊地址
switch host {
case "0.0.0.0", "127.0.0.1", "localhost":
return "tcp4"
case "::", "::1":
return "tcp6"
default:
return "tcp" // 默认支持双栈
}
}
// 解析传统格式如 ":8080", "127.0.0.1:8080", "/tmp/socket"
func parseTraditionalStyle(addr string) (*NetworkAddress, error) {
// 检查是否是 Unix socket包含路径分隔符
if strings.Contains(addr, "/") || strings.HasPrefix(addr, "@/") {
return &NetworkAddress{Protocol: "unix", Path: addr}, nil
}
// 否则按 TCP 地址解析
host, port, err := net.SplitHostPort(addr)
if err == nil {
return &NetworkAddress{Protocol: "tcp", Host: host, Port: port}, nil
}
// 检查是否是端口号
if ok := utils.IsNumber(addr); ok {
return &NetworkAddress{Protocol: "tcp", Host: "0.0.0.0", Port: addr}, nil
}
return nil, fmt.Errorf("解析地址失败: %w", err)
}
// 获取网络类型用于 net.Dial 或 net.Listen
func (na *NetworkAddress) Network() string {
return na.Protocol
}
// 获取地址字符串用于 net.Dial 或 net.Listen
func (na *NetworkAddress) Address() string {
switch na.Protocol {
case "tcp", "tcp4", "tcp6":
if na.Port == "" {
return na.Host
}
return net.JoinHostPort(na.Host, na.Port)
case "unix", "unixpacket":
return na.Path
default:
return na.Raw
}
}
// 格式化输出
func (na *NetworkAddress) String() string {
switch na.Protocol {
case "tcp", "tcp4", "tcp6":
return fmt.Sprintf("%s://%s", na.Protocol, net.JoinHostPort(na.Host, na.Port))
case "unix", "unixpacket":
return fmt.Sprintf("%s://%s", na.Protocol, na.Path)
default:
return na.Raw
}
}
// 验证地址是否有效
func (na *NetworkAddress) Validate() error {
switch na.Protocol {
case "tcp", "tcp4", "tcp6":
if na.Host == "" && na.Port == "" {
return fmt.Errorf("TCP地址需要主机和端口")
}
// 验证端口
if na.Port != "" {
if _, err := net.LookupPort("tcp", na.Port); err != nil {
return fmt.Errorf("无效的端口: %s", na.Port)
}
}
case "unix", "unixpacket":
if na.Path == "" {
return fmt.Errorf("Unix socket路径不能为空")
}
default:
return fmt.Errorf("不支持的协议: %s", na.Protocol)
}
return nil
}

View File

@ -1,4 +1,4 @@
package cmd
package service
import (
"fmt"

60
service/meta.go Normal file
View File

@ -0,0 +1,60 @@
package service
import (
"context"
"git.apinb.com/bsm-sdk/core/crypto/token"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/utils"
"google.golang.org/grpc/metadata"
)
// 解析Context中MetaData的数据
type ParseOptions struct {
RoleValue string // 判断角色的值
MustPrivateAllow bool // 是否只允许私有IP访问
}
func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*token.Claims, error) {
// 解析metada中的信息并验证
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errcode.ErrTokenAuthNotFound
}
var Authorizations []string = md.Get("authorization")
if len(Authorizations) == 0 || Authorizations[0] == "" {
return nil, errcode.ErrTokenAuthNotFound
}
claims, err := token.New(env.Runtime.JwtSecretKey).ParseJwt(Authorizations[0])
if err != nil {
return nil, err
}
if opts != nil {
if !checkRole(claims, "role", opts.RoleValue) {
return nil, errcode.ErrPermissionDenied
}
if opts.MustPrivateAllow {
if utils.IsPublicIP(claims.Client) {
return nil, errcode.ErrPermissionDenied
}
}
}
return claims, nil
}
func checkRole(claims *token.Claims, roleKey, roleValue string) bool {
if roleValue == "" {
return true
}
if role, exists := claims.Extend[roleKey]; !exists || role != roleValue {
return false
} else {
return true
}
}

132
service/register.go Normal file
View File

@ -0,0 +1,132 @@
package service
import (
"context"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/vars"
clientv3 "go.etcd.io/etcd/client/v3"
)
// ServiceRegister 创建租约注册服务
type ServiceRegister struct {
cli *clientv3.Client //etcd client
leaseID clientv3.LeaseID //租约ID
//租约keepalieve相应chan
keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse
key string //key
val string //value
}
// NewRegister 注册服务至路由表.
//
// ec:EtcdConfig;
//
// routeKey: ServiceRouteRootPrefix + ServiceKey + "/" + utils.GetLocationIP() + addr;
//
// gs:grpc.Server;
func RegisterService(cli *clientv3.Client, routeKey string, methods []string, lease int64) (*ServiceRegister, error) {
ser := &ServiceRegister{
cli: cli,
key: routeKey,
val: strings.Join(methods, ","),
}
//申请租约设置时间keepalive
if err := ser.putKeyWithLease(lease); err != nil {
return nil, err
}
return ser, nil
}
// 设置租约
func (s *ServiceRegister) putKeyWithLease(lease int64) error {
//设置租约时间
resp, err := s.cli.Grant(context.Background(), lease)
if err != nil {
return err
}
//注册服务并绑定租约
_, err = s.cli.Put(context.Background(), s.key, s.val, clientv3.WithLease(resp.ID))
if err != nil {
return err
}
//设置续租 定期发送需求请求
leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID)
if err != nil {
return err
}
s.leaseID = resp.ID
s.keepAliveChan = leaseRespChan
return nil
}
// ListenLeaseRespChan 监听 续租情况
func (s *ServiceRegister) ListenLeaseRespChan() {
for {
select {
case leaseKeepResp := <-s.keepAliveChan:
if leaseKeepResp == nil {
//log.Println("close lease.")
return
} else {
goto END
}
}
END:
time.Sleep(500 * time.Millisecond)
}
}
// Close 注销服务
func (s *ServiceRegister) Close() error {
//撤销租约
if _, err := s.cli.Revoke(context.Background(), s.leaseID); err != nil {
return err
}
return s.cli.Close()
}
func (s *ServiceRegister) SetAnonymous(key string, urls []string) {
// remove reppeat, clear service all anonymous uri.
anonymous, err := s.cli.Get(context.Background(), key)
if err != nil {
printer.Error("[BSM Register] Get Anonymous Fail: %v", err)
return
}
var as []string
if len(anonymous.Kvs) > 0 {
val := string(anonymous.Kvs[0].Value)
as = strings.Split(val, ",")
}
as = append(clearService(as), urls...)
newAnonymous := strings.Join(as, ",")
// put anonymous to etcd
_, err = s.cli.Put(context.Background(), key, newAnonymous)
if err != nil {
printer.Error("[BSM Register] Anonymous Fail.")
} else {
printer.Info("[BSM Register] Anonymous: %s", newAnonymous)
}
}
func clearService(as []string) (out []string) {
for _, v := range as {
if len(v) >= len(vars.ServiceKey) {
if v[0:len(vars.ServiceKey)] != vars.ServiceKey {
out = append(out, v)
}
}
}
return
}

158
service/service.go Normal file
View File

@ -0,0 +1,158 @@
// Package service 提供微服务管理功能
// 包括服务启动、注册、网关代理等核心功能
package service
import (
"context"
"log"
"net"
"os"
"strconv"
"strings"
"net/http"
gwRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"git.apinb.com/bsm-sdk/core/conf"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/vars"
clientv3 "go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
)
type (
// RegisterFn 定义服务注册函数类型
RegisterFn func(*grpc.Server)
// Service 微服务实例
Service struct {
GrpcSrv *grpc.Server // gRPC服务器实例
Opts *Options // 服务配置选项
}
// Options 服务配置选项
Options struct {
Addr string // 服务监听地址
EtcdClient *clientv3.Client // Etcd客户端
MsConf *conf.MicroServiceConf // 微服务配置
GatewayConf *conf.GatewayConf // 网关配置
GatewayCtx context.Context // 网关上下文
GatewayMux *gwRuntime.ServeMux // 网关多路复用器
}
)
// New 创建新的服务实例
// srv: gRPC服务器实例
// opts: 服务配置选项
func New(srv *grpc.Server, opts *Options) *Service {
return &Service{GrpcSrv: srv, Opts: opts}
}
// Addr 将IP和端口格式化为host:port格式
// ip: IP地址
// port: 端口号
func Addr(ip string, port int) string {
return net.JoinHostPort(ip, strconv.Itoa(port))
}
// Start 启动服务
// 包括etcd注册、gRPC服务启动、HTTP网关启动
func (s *Service) Start() {
printer.Info("[BSM - %s] Service Starting ...", vars.ServiceKey)
// 注册到etcd
if s.Opts.MsConf != nil && s.Opts.MsConf.Enable {
if s.Opts.EtcdClient == nil {
printer.Error("[BSM Register] Etcd Client is nil.")
os.Exit(1)
}
printer.Info("[BSM - %s] Registering Service to Etcd ...", vars.ServiceKey)
// 获取gRPC方法用于网关/路由发现
methods := FoundGrpcMethods(s.GrpcSrv)
// 设置路由键
routerKey := vars.ServiceRootPrefix + "Router/" + env.Runtime.Workspace + "/" + strings.ToLower(vars.ServiceKey) + "/" + s.Opts.Addr
// 使用租约注册到etcd
register, err := RegisterService(s.Opts.EtcdClient, routerKey, methods, vars.ServiceLease)
if err != nil {
log.Panicf("[ERROR] %s Service Register:%s \n", vars.ServiceKey, err.Error())
}
anonKey := vars.ServiceRootPrefix + "Router/" + env.Runtime.Workspace + "/"
register.SetAnonymous(anonKey, s.Opts.MsConf.Anonymous)
// 服务注册租约监听
go register.ListenLeaseRespChan()
}
// 启动gRPC服务
tcpListen, err := net.Listen("tcp", s.Opts.Addr)
if err != nil {
panic(err)
}
go func() {
if err := s.GrpcSrv.Serve(tcpListen); err != nil {
panic(err)
}
}()
printer.Success("[BSM - %s] Grpc %s Runing Success !", vars.ServiceKey, s.Opts.Addr)
// 启动HTTP网关
if s.Opts.GatewayConf != nil && s.Opts.GatewayConf.Enable {
addr := Addr("0.0.0.0", s.Opts.GatewayConf.Port)
go s.Gateway(s.Opts.Addr, addr)
printer.Success("[BSM - %s] Http %s Runing Success!", vars.ServiceKey, addr)
}
// 阻塞主线程
select {}
}
// Gateway 启动HTTP网关服务
// grpcAddr: gRPC服务地址
// httpAddr: HTTP服务地址
func (s *Service) Gateway(grpcAddr string, httpAddr string) {
// 定义上下文
_, cancel := context.WithCancel(s.Opts.GatewayCtx)
defer cancel()
// 启动HTTP服务不因HTTP启动失败而导致panic
if err := http.ListenAndServe(httpAddr, s.Opts.GatewayMux); err != nil {
printer.Error("[BSM - %s] Http Serve Error: %v", vars.ServiceKey, err)
}
}
// Use 执行初始化函数
// initFunc: 初始化函数
func (s *Service) Use(initFunc func() error) {
err := (initFunc)()
if err != nil {
printer.Error(err.Error())
panic(err)
}
}
// Stop 优雅停止服务
func (s *Service) Stop() {
s.GrpcSrv.GracefulStop()
}
// FoundGrpcMethods 发现gRPC服务中的所有方法
// s: gRPC服务器实例
// 返回: 方法名列表
func FoundGrpcMethods(s *grpc.Server) []string {
var mothods []string
for key, srv := range s.GetServiceInfo() {
srvName := strings.Split(key, ".")[1]
for _, mn := range srv.Methods {
mothods = append(mothods, srvName+"."+mn.Name)
}
}
return mothods
}

65
third/wechat.go Normal file
View File

@ -0,0 +1,65 @@
package third
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
)
func WeChat_Decrypt(sessionKey, encryptedData, iv string) (string, error) {
aesKey, err := base64.StdEncoding.DecodeString(sessionKey)
if err != nil {
return "", err
}
cipherText, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
ivBytes, err := base64.StdEncoding.DecodeString(iv)
if err != nil {
return "", err
}
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
mode := cipher.NewCBCDecrypter(block, ivBytes)
mode.CryptBlocks(cipherText, cipherText)
cipherText, err = WeChat_Pkcs7Unpad(cipherText, block.BlockSize())
if err != nil {
return "", err
}
return string(cipherText), nil
}
// pkcs7Unpad returns slice of the original data without padding
func WeChat_Pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
var (
// ErrInvalidBlockSize block size不合法
ErrInvalidBlockSize = errors.New("invalid block size")
// ErrInvalidPKCS7Data PKCS7数据不合法
ErrInvalidPKCS7Data = errors.New("invalid PKCS7 data")
// ErrInvalidPKCS7Padding 输入padding失败
ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
)
if blockSize <= 0 {
return nil, ErrInvalidBlockSize
}
if len(data)%blockSize != 0 || len(data) == 0 {
return nil, ErrInvalidPKCS7Data
}
c := data[len(data)-1]
n := int(c)
if n == 0 || n > len(data) {
return nil, ErrInvalidPKCS7Padding
}
for i := 0; i < n; i++ {
if data[len(data)-n+i] != c {
return nil, ErrInvalidPKCS7Padding
}
}
return data[:len(data)-n], nil
}

19
types/config.go Normal file
View File

@ -0,0 +1,19 @@
package types
type AnonymousConf struct {
Key string
Urls []string
}
type PulsarConf struct {
Endpoints string
Token string
Namespaces string
}
func (p *PulsarConf) IsHas() bool {
if p != nil && p.Endpoints != "" {
return true
}
return false
}

View File

@ -10,62 +10,68 @@ type (
// sql options
SqlOptions struct {
MaxIdleConns int
MaxOpenConns int
MaxIdleConns int `gorm:"column:max_idle_conns;" json:"max_idle_conns"`
MaxOpenConns int `gorm:"column:max_open_conns;" json:"max_open_conns"`
ConnMaxLifetime time.Duration
LogStdout bool
Debug bool
LogStdout bool `gorm:"column:log_stdout;" json:"log_stdout"`
Debug bool `gorm:"column:debug;" json:"debug"`
}
// standard ID,Identity definition.
Std_IDIdentity struct {
ID uint `gorm:"primarykey;" json:"id"`
ID uint `gorm:"column:id;primarykey;" json:"id"`
Identity string `gorm:"column:identity;type:varchar(36);uniqueIndex;" json:"identity"` // 唯一标识24位NanoID,36位为ULID
}
// standard ID,Created,Updated,Deleted definition.
Std_IICUDS struct {
ID uint `gorm:"primarykey;" json:"id"`
ID uint `gorm:"column:id;primarykey;" json:"id"`
Identity string `gorm:"column:identity;type:varchar(36);uniqueIndex;" json:"identity"` // 唯一标识24位NanoID,36位为ULID
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index;" json:"deleted_at"`
Status int8 `gorm:"default:0;index;" json:"status"` // 状态默认为0-1禁止1为正常
CreatedAt time.Time `gorm:"column:created_at;type:TIMESTAMP;" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:TIMESTAMP;" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:TIMESTAMP;index;" json:"deleted_at"`
Status int8 `gorm:"column:status;default:0;index;" json:"status"` // 状态默认为0-1禁止1为正常
}
// standard ID,Identity,Created,Updated,Deleted,Status definition.
Std_ICUD struct {
ID uint `gorm:"primarykey;" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index;" json:"deleted_at"`
ID uint `gorm:"column:id;primarykey;" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:TIMESTAMP;" json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:TIMESTAMP;index;" json:"deleted_at"`
}
// standard ID,Created definition.
Std_IdCreated struct {
ID uint `gorm:"primarykey;" json:"id"`
CreatedAt time.Time `json:"created_at"`
ID uint `gorm:"column:id;primarykey;" json:"id"`
CreatedAt time.Time `gorm:"column:created_at;type:TIMESTAMP;" json:"created_at"`
}
// standard PassportID,PassportIdentity definition.
Std_Passport struct {
PassportID uint `gorm:"column:passport_id;Index;" json:"passport_id"`
PassportIdentity string `gorm:"column:passport_identity;type:varchar(36);Index;" json:"passport_identity"` // 用户唯一标识24位NanoID,36位为ULID
PassportIdentity string `gorm:"column:passport_identity;type:varchar(36);Index;" json:"passport_identity"` // 用户唯一标识24位NanoID,36位为UUID
}
// standard OwnerID,OwnerIdentity definition.
Std_Owner struct {
OwnerID uint `gorm:"column:owner_id;Index;" json:"owner_id"`
OwnerIdentity string `gorm:"column:owner_identity;type:varchar(36);Index;" json:"owner_identity"` // 用户唯一标识24位NanoID,36位为UUID
}
// standard ID definition.
Std_ID struct {
ID uint `gorm:"primarykey;" json:"id"`
ID uint `gorm:"column:id;primarykey;" json:"id"`
}
// standard Identity definition.
Std_Identity struct {
Identity string `gorm:"column:identity;type:varchar(36);uniqueIndex;" json:"identity"` // 唯一标识24位NanoID,36位为ULID
Identity string `gorm:"column:identity;type:varchar(36);uniqueIndex;" json:"identity"` // 唯一标识24位NanoID,36位为UUID
}
// standard Status definition.
Std_Status struct {
Status int8 `gorm:"default:0;index;" json:"status"` // 状态默认为0-1禁止1为正常
Status int64 `gorm:"column:status;default:0;index;" json:"status"` // 状态默认为0-1禁止1为正常
}
)

View File

@ -13,4 +13,5 @@ type JwtClaims struct {
Owner any `json:"owner"`
Role string `json:"role"`
ExpiresAt int64 `json:"exp"`
SsoToken string `json:"sso_token"`
}

View File

@ -7,9 +7,10 @@ const (
Run_Time_Mode_Product
)
type MeshEnv struct {
type RuntimeEnv struct {
Workspace string // MESH workspacedefault:bsm
Prefix string // MESH prefixdefault:/usr/local/mesh/
JwtSecretKey string // jwt Secret Keydefault:
RuntimeMode string // Runtime Mode String: dev/test/pre/product
Mode string // Runtime Mode String: dev/test/pre/product
LicencePath string
}

View File

@ -1,8 +0,0 @@
package types
type Message struct {
Identity string
Action string
Args string
Payload string
}

View File

@ -1,36 +0,0 @@
package types
type Empty struct {
}
type Base struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
type Paginate struct {
Offset int `form:"offset,default=0,range=[0:)"`
Size int `form:"size,default=10,range=[1:50]"`
}
type ID struct {
ID uint `path:"id"`
}
type Identity struct {
Identity string `path:"identity"`
}
type GormModel struct {
ID uint `json:"id,optional"`
CreatedAt string `json:"created_at,optional"`
UpdatedAt string `json:"updated_at,optional"`
DeletedAt string `json:"deleted_at,optional"`
}
type FetchRequest struct {
Params map[string]string `json:"params,optional"`
TimeRange string `json:"time_range,optional"`
Paginate
}

36
types/types.go Normal file
View File

@ -0,0 +1,36 @@
// Package types 提供通用数据类型定义
// 包括分页、ID、身份标识等常用结构体
package types
// Empty 空结构体
type Empty struct {
}
// Paginate 分页参数
type Paginate struct {
Offset int `form:"offset,default=0,range=[0:)"` // 偏移量
Size int `form:"size,default=10,range=[1:50]"` // 每页大小
}
// ID ID结构体
type ID struct {
ID uint `json:"id"` // ID字段
}
// Identity 身份标识结构体
type Identity struct {
Identity string `json:"identity"` // 身份标识
}
// Ident ID和身份标识组合结构体
type Ident struct {
ID uint `json:"id"` // ID字段
Identity string `json:"identity"` // 身份标识
}
// FetchRequest 获取请求结构体
type FetchRequest struct {
Params map[string]string `json:"params,optional"` // 查询参数
TimeRange string `json:"time_range,optional"` // 时间范围
Paginate // 分页参数
}

View File

@ -1,3 +1,5 @@
// Package utils 提供通用工具函数
// 包括数据类型转换、时间处理、网络工具等
package utils
import (
@ -6,61 +8,72 @@ import (
"strings"
)
// 字符串转Int
//
// intStr数字的字符串
// String2Int 字符串转Int
// intStr: 数字的字符串
// 返回: 转换后的整数转换失败返回0
func String2Int(intStr string) (intNum int) {
intNum, _ = strconv.Atoi(intStr)
intNum, err := strconv.Atoi(intStr)
if err != nil {
return 0
}
return
}
// 字符串转Int64
//
// intStr数字的字符串
// String2Int64 字符串转Int64
// intStr: 数字的字符串
// 返回: 转换后的64位整数转换失败返回0
func String2Int64(intStr string) (int64Num int64) {
intNum, _ := strconv.Atoi(intStr)
int64Num = int64(intNum)
return
intNum, err := strconv.ParseInt(intStr, 10, 64)
if err != nil {
return 0
}
return intNum
}
// 字符串转Float64
//
// floatStr小数点数字的字符串
// String2Float64 字符串转Float64
// floatStr: 小数点数字的字符串
// 返回: 转换后的64位浮点数转换失败返回0
func String2Float64(floatStr string) (floatNum float64) {
floatNum, _ = strconv.ParseFloat(floatStr, 64)
floatNum, err := strconv.ParseFloat(floatStr, 64)
if err != nil {
return 0
}
return
}
// 字符串转Float32
//
// floatStr小数点数字的字符串
// String2Float32 字符串转Float32
// floatStr: 小数点数字的字符串
// 返回: 转换后的32位浮点数转换失败返回0
func String2Float32(floatStr string) (floatNum float32) {
floatNum64, _ := strconv.ParseFloat(floatStr, 32)
floatNum64, err := strconv.ParseFloat(floatStr, 32)
if err != nil {
return 0
}
floatNum = float32(floatNum64)
return
}
// Int转字符串
//
// intNum数字字符串
// Int2String Int转字符串
// intNum: 整数
// 返回: 转换后的字符串
func Int2String(intNum int) (intStr string) {
intStr = strconv.Itoa(intNum)
return
}
// Int64转字符串
//
// intNum数字字符串
// Int642String Int64转字符串
// intNum: 64位整数
// 返回: 转换后的字符串
func Int642String(intNum int64) (int64Str string) {
//10, 代表10进制
// 10代表10进制
int64Str = strconv.FormatInt(intNum, 10)
return
}
// Float64转字符串
//
// floatNumfloat64数字
// prec精度位数不传则默认float数字精度
// Float64ToString Float64转字符串
// floatNum: float64数字
// prec: 精度位数不传则默认float数字精度
// 返回: 转换后的字符串
func Float64ToString(floatNum float64, prec ...int) (floatStr string) {
if len(prec) > 0 {
floatStr = strconv.FormatFloat(floatNum, 'f', prec[0], 64)
@ -70,10 +83,10 @@ func Float64ToString(floatNum float64, prec ...int) (floatStr string) {
return
}
// Float32转字符串
//
// floatNumfloat32数字
// prec精度位数不传则默认float数字精度
// Float32ToString Float32转字符串
// floatNum: float32数字
// prec: 精度位数不传则默认float数字精度
// 返回: 转换后的字符串
func Float32ToString(floatNum float32, prec ...int) (floatStr string) {
if len(prec) > 0 {
floatStr = strconv.FormatFloat(float64(floatNum), 'f', prec[0], 32)
@ -83,7 +96,9 @@ func Float32ToString(floatNum float32, prec ...int) (floatStr string) {
return
}
// 二进制转10进制
// BinaryToDecimal 二进制转10进制
// bit: 二进制字符串
// 返回: 转换后的十进制数
func BinaryToDecimal(bit string) (num int) {
fields := strings.Split(bit, "")
lens := len(fields)
@ -96,7 +111,9 @@ func BinaryToDecimal(bit string) (num int) {
return
}
// interface to string
// AnyToString 任意类型转字符串
// in: 输入值
// 返回: 转换后的字符串
func AnyToString(in any) (s string) {
if in == nil {
return ""

View File

@ -25,11 +25,17 @@ func CreateDir(dirName string) bool {
}
func GetRunPath() string {
path, _ := os.Executable()
path, err := os.Executable()
if err != nil {
return ""
}
return path
}
func GetCurrentPath() string {
path, _ := os.Getwd()
path, err := os.Getwd()
if err != nil {
return ""
}
return path
}
}

View File

@ -12,7 +12,7 @@ func If(condition bool, trueValue, falseValue interface{}) interface{} {
return falseValue
}
//如果首字母是小写字母, 则变换为大写字母
// 如果首字母是小写字母, 则变换为大写字母
func FirstToUpper(str string) string {
if str == "" {
return ""

View File

@ -2,10 +2,21 @@ package utils
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
)
const (
B = 1
KB = 1024 * B
MB = 1024 * KB
GB = 1024 * MB
)
// 将字符串写入文件
func StringToFile(path, content string) error {
startF, err := os.Create(path)
if err != nil {
@ -18,3 +29,132 @@ func StringToFile(path, content string) error {
}
return nil
}
// 文件复制
func CopyFile(src, dst string) (int64, error) {
sourceFileStat, err := os.Stat(src)
if err != nil {
return 0, err
}
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
source, err := os.Open(src)
if err != nil {
return 0, err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
return nBytes, err
}
func FileSize(fp string) string {
fileInfo, err := os.Stat(fp)
if err != nil {
return "0 B"
}
bytes := fileInfo.Size()
switch {
case bytes >= GB:
return fmt.Sprintf("%.2f GB", float64(bytes)/float64(GB))
case bytes >= MB:
return fmt.Sprintf("%.2f MB", float64(bytes)/float64(MB))
case bytes >= KB:
return fmt.Sprintf("%.2f KB", float64(bytes)/float64(KB))
default:
return fmt.Sprintf("%d B", bytes)
}
}
// 递归遍历文件夹
// rootDir: 文件夹根目录
// s: 存储文件名的切片
// filter: 过滤条件:".git", ".idea", ".vscode", ".gitignore", ".gitea", ".github", ".golangci.yml", "*.pyc"
func FileTreeByFilter(rootDir string, s []string, filter []string) ([]string, error) {
rd, err := os.ReadDir(rootDir)
if err != nil {
return s, err
}
for _, fi := range rd {
// 检查文件名是否匹配任何一个过滤模式
matched := false
for _, item := range filter {
exists, err := filepath.Match(item, fi.Name())
if err != nil {
continue
}
if exists {
matched = true
break
}
}
if matched {
continue
}
if fi.IsDir() {
fullDir := rootDir + "/" + fi.Name()
s, err = FileTreeByFilter(fullDir, s, filter)
if err != nil {
return s, err
}
} else {
fullName := rootDir + "/" + fi.Name()
s = append(s, fullName)
}
}
return s, nil
}
// 递归遍历文件夹
// rootDir: 文件夹根目录
// s: 存储文件名的切片
// allow: 允许条件:".zip", ".check"
func FileTreeBySelect(rootDir string, s []string, allow []string) ([]string, error) {
rd, err := os.ReadDir(rootDir)
if err != nil {
return s, err
}
for _, fi := range rd {
// 检查文件名是否匹配任何一个过滤模式
matched := false
for _, item := range allow {
exists, err := filepath.Match(item, fi.Name())
if err != nil {
continue
}
if exists {
matched = true
break
}
}
if !matched {
continue
}
if fi.IsDir() {
fullDir := rootDir + "/" + fi.Name()
s, err = FileTreeBySelect(fullDir, s, allow)
if err != nil {
return s, err
}
} else {
fullName := rootDir + "/" + fi.Name()
s = append(s, fullName)
}
}
return s, nil
}

34
utils/fmt.go Normal file
View File

@ -0,0 +1,34 @@
package utils
import (
"strconv"
"strings"
)
func FloatRoundString(in float64, place int) string {
return Float64ToString(FloatRound(in, place))
}
func FloatRound(in float64, place int) float64 {
// 限制 place 范围在合理区间
if place < 0 {
place = 0
}
// 使用 strconv.FormatFloat 直接格式化,避免多次条件判断
str := strconv.FormatFloat(in, 'f', place, 64)
num, _ := strconv.ParseFloat(str, 64)
return num
}
func RateToFloat64(s string) float64 {
// 去掉百分号
s = strings.TrimSuffix(s, "%")
// 转换为 float64
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0
}
return f / 100
}

View File

@ -1,9 +1,14 @@
package utils
import (
"github.com/google/uuid"
ulid "github.com/oklog/ulid/v2"
)
func UUID() string {
return uuid.Must(uuid.NewV7()).String()
}
// remove nanoid,uuid,replace to ulid
func ULID() string {
return ulid.Make().String()

View File

@ -1,7 +1,10 @@
// Package utils 提供通用工具函数
// 包括数据类型转换、时间处理、网络工具等
package utils
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@ -9,8 +12,12 @@ import (
"net/http"
"os"
"strconv"
"strings"
)
// IsPublicIP 判断是否为公网IP
// ipString: IP地址字符串
// 返回: 是否为公网IP
func IsPublicIP(ipString string) bool {
ip := net.ParseIP(ipString)
if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() {
@ -18,11 +25,11 @@ func IsPublicIP(ipString string) bool {
}
if ip4 := ip.To4(); ip4 != nil {
switch true {
case ip4[0] == 10:
case ip4[0] == 10: // 10.0.0.0/8
return false
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31:
case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: // 172.16.0.0/12
return false
case ip4[0] == 192 && ip4[1] == 168:
case ip4[0] == 192 && ip4[1] == 168: // 192.168.0.0/16
return false
default:
return true
@ -31,25 +38,57 @@ func IsPublicIP(ipString string) bool {
return false
}
// Get Location IP .
func GetLocationIP() string {
addrs, err := net.InterfaceAddrs()
// GetLocationIP 获取本机IP地址
// 返回: 本机IP地址
func GetLocationIP() (localIp string) {
localIp = "127.0.0.1"
// Get all network interfaces
interfaces, err := net.Interfaces()
if err != nil {
return ""
return
}
ip := ""
for _, a := range addrs {
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
ip = ipnet.IP.String()
break
for _, iface := range interfaces {
// Skip the loopback interface
if iface.Flags&net.FlagLoopback != 0 {
continue
}
// Get addresses associated with the interface
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
// Check if the address is an IPNet
ipnet, ok := addr.(*net.IPNet)
if !ok || ipnet.IP.IsLoopback() {
continue
}
// Get the IP address
ip := ipnet.IP.To4()
if ip == nil {
continue
}
// Skip IP addresses in the 169.254.x.x range
if strings.HasPrefix(ip.String(), "169.254") {
continue
}
// Skip IP addresses in the 169.254.x.x range
if strings.HasPrefix(ip.String(), "26.26") {
continue
}
// Return the first valid IP address found
return ip.String()
}
}
if ip == "" {
return ""
}
return ip
return
}
func LocalIPv4s() ([]string, error) {
@ -89,6 +128,15 @@ func HttpGet(url string) ([]byte, error) {
return body, err
}
func HttpPostJSON(url string, header map[string]string, data map[string]any) ([]byte, error) {
bytes, err := json.Marshal(data)
if err != nil {
return nil, err
}
return HttpPost(url, header, bytes)
}
func HttpPost(url string, header map[string]string, data []byte) ([]byte, error) {
var err error
reader := bytes.NewBuffer(data)
@ -153,7 +201,7 @@ func DownloadFile(url, saveTo string, fb func(length, downLen int64)) {
resp, err := client.Get(url)
if err != nil {
fmt.Printf("download %s error:%s\n", url, err)
os.Exit(0)
return
}
//读取服务器返回的文件大小
fsize, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32)
@ -165,12 +213,12 @@ func DownloadFile(url, saveTo string, fb func(length, downLen int64)) {
file.Chmod(0777)
if err != nil {
fmt.Printf("Create %s error:%s\n", saveTo, err)
os.Exit(0)
return
}
defer file.Close()
if resp.Body == nil {
fmt.Printf("resp %s error:%s\n", url, err)
os.Exit(0)
return
}
defer resp.Body.Close()
//下面是 io.copyBuffer() 的简化版本
@ -208,6 +256,6 @@ func DownloadFile(url, saveTo string, fb func(length, downLen int64)) {
if err != nil {
fmt.Printf("callback error:%s\n", err)
os.Exit(0)
return
}
}

24
utils/time.go Normal file
View File

@ -0,0 +1,24 @@
// Package utils 提供通用工具函数
// 包括时间处理、数据类型转换、网络工具等
package utils
import (
"time"
)
// Time2String 将时间转换为字符串
// layout: 时间格式
// t: 时间对象
// 返回: 格式化的时间字符串
func Time2String(layout string, t time.Time) string {
return t.Format(layout)
}
// String2Time 将字符串转换为时间
// layout: 时间格式
// in: 时间字符串
// 返回: 时间对象
func String2Time(layout, in string) time.Time {
t, _ := time.ParseInLocation(layout, in, time.Local)
return t
}

129
utils/validator.go Normal file
View File

@ -0,0 +1,129 @@
package utils
import (
"regexp"
"strings"
"unicode"
)
// IsEmail 验证是否是邮箱格式
func IsEmail(email string) bool {
if strings.TrimSpace(email) == "" {
return false
}
// 邮箱正则表达式
pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
matched, err := regexp.MatchString(pattern, email)
if err != nil {
return false
}
return matched
}
// IsMobile 验证是否是手机号(中国手机号格式)
func IsMobile(mobile string) bool {
if strings.TrimSpace(mobile) == "" {
return false
}
// 中国手机号正则表达式1开头第二位3-9后面9位数字
pattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(pattern, mobile)
if err != nil {
return false
}
return matched
}
// IsNumber 验证是否是纯数字
func IsNumber(str string) bool {
if strings.TrimSpace(str) == "" {
return false
}
for _, char := range str {
if !unicode.IsDigit(char) {
return false
}
}
return true
}
// IsEnglish 验证是否是英文字符(不限大小写)
func IsEnglish(str string) bool {
if strings.TrimSpace(str) == "" {
return false
}
for _, char := range str {
if !unicode.IsLetter(char) {
return false
}
if !isEnglishLetter(char) {
return false
}
}
return true
}
// IsEnglishWithSpace 验证是否是英文字符(允许空格)
func IsEnglishWithSpace(str string) bool {
if strings.TrimSpace(str) == "" {
return false
}
for _, char := range str {
if unicode.IsSpace(char) {
continue
}
if !unicode.IsLetter(char) {
return false
}
if !isEnglishLetter(char) {
return false
}
}
return true
}
// IsAlphanumeric 验证是否是字母和数字组合
func IsAlphanumeric(str string) bool {
if strings.TrimSpace(str) == "" {
return false
}
for _, char := range str {
if !unicode.IsLetter(char) && !unicode.IsDigit(char) {
return false
}
}
return true
}
// IsStrongPassword 验证强密码至少8位包含大小写字母和数字
func IsStrongPassword(password string) bool {
if len(password) < 8 {
return false
}
var hasUpper, hasLower, hasDigit bool
for _, char := range password {
switch {
case unicode.IsUpper(char):
hasUpper = true
case unicode.IsLower(char):
hasLower = true
case unicode.IsDigit(char):
hasDigit = true
}
}
return hasUpper && hasLower && hasDigit
}
// 辅助函数:判断是否是英文字母
func isEnglishLetter(char rune) bool {
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')
}

View File

@ -8,4 +8,10 @@ var (
MemLRUMaxNumber int = 1024
MemShardings int = 64
RedisShardings int = 256
// CacheKeyPrefix 缓存键前缀
CacheKeyPrefix = "bsm:"
// DefaultTTL 默认缓存过期时间
DefaultTTL = 30 * time.Minute // 30分钟
)

View File

@ -4,5 +4,5 @@ import "time"
var (
// cache def value
JwtExpireDay time.Duration = 1 * 24 * time.Hour
JwtExpire time.Duration = 1 * 24 * time.Hour
)

12
vars/logger.go Normal file
View File

@ -0,0 +1,12 @@
package vars
// LogLevel 日志级别
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
FATAL
)

View File

@ -3,7 +3,7 @@ package vars
import "time"
var (
MQSpaceName = "Traingo"
MQSpaceName = "Default"
OperationTimeout time.Duration = 30 * time.Second
ConnectionTimeout time.Duration = 30 * time.Second
)

View File

@ -4,4 +4,7 @@ var (
RUN_MODE_DEV = "dev"
RUN_MODE_TEST = "test"
RUN_MODE_PROD = "prod"
ServiceLease int64 = 60
ServiceRootPrefix string = "/"
)

View File

@ -5,4 +5,6 @@ const (
NormalStatus = 1
// DisabledStatus .
DisabledStatus = -1
OK string = "OK"
)

27
with/databases.go Normal file
View File

@ -0,0 +1,27 @@
package with
import (
"git.apinb.com/bsm-sdk/core/conf"
"git.apinb.com/bsm-sdk/core/database"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/vars"
"gorm.io/gorm"
)
func Databases(cfg *conf.DBConf, opts *types.SqlOptions) *gorm.DB {
if cfg == nil || len(cfg.Source) == 0 {
panic("No Database Source Found !")
}
// print inform.
printer.Info("[BSM - %s] Databases: %v", vars.ServiceKey, cfg)
var err error
db, err := database.NewDatabase(cfg.Driver, cfg.Source, opts)
if err != nil {
printer.Error("Database Init Failed !")
panic(err)
}
return db
}

54
with/etcd.go Normal file
View File

@ -0,0 +1,54 @@
package with
import (
"time"
"git.apinb.com/bsm-sdk/core/conf"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/vars"
"go.etcd.io/etcd/client/pkg/v3/transport"
clientv3 "go.etcd.io/etcd/client/v3"
)
func Etcd(cfg *conf.EtcdConf) (cli *clientv3.Client) {
if cfg == nil || len(cfg.Endpoints) == 0 {
return
}
etcdCfg := clientv3.Config{
Endpoints: cfg.Endpoints,
DialTimeout: 5 * time.Second,
}
if cfg.Passwd != nil {
etcdCfg.Username = cfg.Passwd.Account
etcdCfg.Password = cfg.Passwd.Password
}
if cfg.TLS != nil {
tlsInfo := transport.TLSInfo{
TrustedCAFile: cfg.TLS.CaFile,
CertFile: cfg.TLS.CertFile,
KeyFile: cfg.TLS.KeyFile,
}
tlsConfig, err := tlsInfo.ClientConfig()
if err != nil {
printer.Error(errcode.ErrEtcd.Error())
panic(err)
}
etcdCfg.TLS = tlsConfig
}
var err error
cli, err = clientv3.New(etcdCfg)
if err != nil {
printer.Error(errcode.ErrEtcd.Error())
panic(err)
}
// print inform.
printer.Info("[BSM - %s] Service Center: %v", vars.ServiceKey, cfg.Endpoints)
return
}

36
with/memory.go Normal file
View File

@ -0,0 +1,36 @@
package with
import (
"context"
"time"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/vars"
"github.com/allegro/bigcache/v3"
)
func Memory(opts *bigcache.Config) (cli *bigcache.BigCache) {
if opts == nil {
opts = &bigcache.Config{
Shards: 1024,
LifeWindow: 10 * time.Minute,
CleanWindow: 5 * time.Minute,
MaxEntriesInWindow: 1000 * 10 * 60,
MaxEntrySize: 500,
Verbose: true,
HardMaxCacheSize: 8192,
OnRemove: nil,
OnRemoveWithReason: nil,
}
}
var err error
cli, err = bigcache.New(context.Background(), *opts)
if err != nil {
printer.Error("Memory Cache Fatal Error")
panic(err)
}
printer.Success("[BSM - %s] Memory Cache: Shards=%d, MaxEntrySize=%d", vars.ServiceKey, opts.Shards, opts.MaxEntrySize)
return
}

18
with/redis.go Normal file
View File

@ -0,0 +1,18 @@
package with
import (
"git.apinb.com/bsm-sdk/core/cache/redis"
"git.apinb.com/bsm-sdk/core/printer"
"git.apinb.com/bsm-sdk/core/vars"
)
func RedisCache(cfg string) (cli *redis.RedisClient) {
if cfg != "" {
cli = redis.New(cfg, vars.ServiceKey)
// print inform.
printer.Info("[BSM - %s] Cache: %s, DBIndex: %d", vars.ServiceKey, cfg, cli.DB)
}
return
}