Compare commits

...

27 Commits

Author SHA1 Message Date
ffb706df32 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-08-11 14:42:38 +08:00
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
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
bfccf4d468 fix licence 2025-05-26 22:11:47 +08:00
cd72620e49 add HttpPostJSON 2025-05-23 10:45:28 +08:00
zhaoxiaorong
5bb23deb3b fix 2025-05-21 20:13:55 +08:00
2c713adc16 add sync map data. 2025-05-03 15:49:16 +08:00
zhaoxiaorong
21f09ea41e fix 2025-04-24 17:52:29 +08:00
4d06ad3e8b add err code 2025-04-19 20:14:37 +08:00
52a81a404e fix infra response 2025-04-18 19:11:50 +08:00
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
14 changed files with 363 additions and 63 deletions

View File

@@ -5,3 +5,23 @@ go env -w GONOPROXY=git.apinb.com/*
go env -w GOINSECURE=git.apinb.com/* go env -w GOINSECURE=git.apinb.com/*
go env -w GONOSUMDB=git.apinb.com/* go env -w GONOSUMDB=git.apinb.com/*
``` ```
# crypto 加密与解密
## GCM加密
```
AESGCMEncrypt GCM 加密
AESGCMDecrypt GCM 解密
```
## CBC加密
```
Encrypt CBC加密
Decrypt CBC解密
```
## ECB加密
```
AesEncryptECB ECB加密
AesDecryptECB ECB解密
```
## 环境变量检测
```
AesKeyCheck 秘钥环境变量检测
```

View File

@@ -1,12 +1,13 @@
package conf package conf
type Base struct { type Base struct {
Service string `yaml:"Service"` // 服务名称 Service string `yaml:"Service"` // 服务名称
Port string `yaml:"Port"` // 服务监听端口,0为自动随机端口 Port string `yaml:"Port"` // 服务监听端口,0为自动随机端口
Cache string `yaml:"Cache"` // REDIS缓存 Cache string `yaml:"Cache"` // REDIS缓存
SecretKey string `yaml:"SecretKey"` // 服务秘钥 SecretKey string `yaml:"SecretKey"` // 服务秘钥
BindIP string `yaml:"BindIP"` // 绑定IP BindIP string `yaml:"BindIP"` // 绑定IP
Addr string `yaml:"Addr"` Addr string `yaml:"Addr"`
OnMicroService bool `yaml:"OnMicroService"`
} }
type DBConf struct { type DBConf struct {
@@ -48,11 +49,14 @@ type RpcConf struct {
} }
type OssConf struct { type OssConf struct {
Platform string `yaml:"Platform"` // oss平台aliyun,tencent,huawei,aws,minio
Site string `yaml:"Site"` // oss站点HOST Site string `yaml:"Site"` // oss站点HOST
Endpoint string `yaml:"Endpoint"` // oss服务接入地址 Endpoint string `yaml:"Endpoint"` // oss服务接入地址
Region string `yaml:"Region"` // oss服务区域 Region string `yaml:"Region"` // oss服务区域
AccessKeyID string `yaml:"AccessKeyId"` // oss AccessKeyId AccessKeyID string `yaml:"AccessKeyId"` // oss AccessKeyId
AccessKeySecret string `yaml:"AccessKeySecret"` // oss AccessKeySecret AccessKeySecret string `yaml:"AccessKeySecret"` // oss AccessKeySecret
UseSSL bool `yaml:"UseSSL"` // 是否使用SSL
} }
type MqConf struct { type MqConf struct {

View File

@@ -4,10 +4,58 @@ import (
"bytes" "bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
) )
// AES加密 // =================== GCM ======================
// AEC GCM 加密
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
}
// AEC GCM 解密
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 ======================
// AES CBC加密
func Encrypt(key string, iv string, data string) string { func Encrypt(key string, iv string, data string) string {
if len(data) == 0 { if len(data) == 0 {
return "" return ""
@@ -24,7 +72,7 @@ func Encrypt(key string, iv string, data string) string {
return data return data
} }
// AES解密 // AES CBC解密
func Decrypt(key string, iv string, data string) string { func Decrypt(key string, iv string, data string) string {
if len(data) == 0 { if len(data) == 0 {
return "" return ""
@@ -102,3 +150,24 @@ func generateKey(key []byte) (genKey []byte) {
} }
return genKey return genKey
} }
func AesKeyCheck(key string) (string, error) {
// 从环境变量获取密钥
keyHex := os.Getenv(key)
if keyHex == "" {
fmt.Println("环境变量 RST_KEY 未设置")
return "", errors.New("环境变量 RST_KEY 未设置")
}
// 解码十六进制字符串的密钥
byteKey, err := hex.DecodeString(keyHex)
if err != nil {
fmt.Printf("密钥解码失败: %v\n", err)
return "", errors.New("密钥解码失败")
}
// 检查密钥长度
if len(byteKey) != 16 && len(key) != 24 && len(key) != 32 {
fmt.Printf("无效的密钥长度: %d 字节 (需要16,24或32字节)\n", len(key))
return "", errors.New("无效的密钥长度,需要16,24或32字节")
}
return keyHex, nil
}

58
data/map_float.go Normal file
View File

@@ -0,0 +1,58 @@
package data
import (
"sync"
)
var (
// Cache
CacheMapFloat *MapFloat
)
// lock
type MapFloat struct {
sync.RWMutex
Data map[string]float64
}
func NewMapFloat() *MapFloat {
return &MapFloat{
Data: make(map[string]float64),
}
}
func (c *MapFloat) All() map[string]float64 {
c.RLock()
defer c.RUnlock()
return c.Data
}
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) Set(key string, val float64) {
c.Lock()
defer c.Unlock()
c.Data[key] = val
}
func (c *MapFloat) Keys() (keys []string) {
c.RLock()
defer c.RUnlock()
for k, _ := range c.Data {
keys = append(keys, k)
}
return
}

51
data/map_string.go Normal file
View File

@@ -0,0 +1,51 @@
package data
import (
"sync"
)
var (
// Cache
CacheMapString *MapString
)
// lock
type MapString struct {
sync.RWMutex
Data map[string]string
}
func NewMapString() *MapString {
return &MapString{
Data: make(map[string]string),
}
}
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) Set(key, val string) {
c.Lock()
defer c.Unlock()
c.Data[key] = val
}
func (c *MapString) Keys() (keys []string) {
c.RLock()
defer c.RUnlock()
for k, _ := range c.Data {
keys = append(keys, k)
}
return
}

View File

@@ -15,13 +15,18 @@ var (
// standard error code ,start:110 // standard error code ,start:110
var ( var (
ErrEmpty = NewError(110, "Data Is Empty") ErrEmpty = NewError(110, "Data Is Empty")
ErrRequestParse = NewError(111, "Request Parse Fail") ErrRequestParse = NewError(111, "Request Parse Fail")
ErrRequestMust = NewError(112, "Request Params Required") ErrRequestMust = NewError(112, "Request Params Required")
ErrPermission = NewError(113, "Permission Denied") ErrPermission = NewError(113, "Permission Denied")
ErrJsonUnmarshal = NewError(114, "Json Unmarshal Fail") ErrJsonUnmarshal = NewError(114, "Json Unmarshal Fail")
ErrJsonMarshal = NewError(115, "Json Marshal Fail") ErrJsonMarshal = NewError(115, "Json Marshal Fail")
ErrInternal = NewError(116, "Internal Server Error") ErrInternal = NewError(116, "Internal Server Error")
ErrPassword = NewError(117, "Password Incorrect")
ErrAccountNotFound = NewError(118, "Account Not Found")
ErrAccountDisabled = NewError(119, "Account Disabled")
ErrDisabled = NewError(120, "Status Disabled")
ErrRecordNotFound = NewError(121, "Record Not Found")
) )
// jwt error code ,start:130 // jwt error code ,start:130

View File

@@ -8,29 +8,29 @@ import (
var Response Reply var Response Reply
type Reply struct { type Reply struct {
Code int `json:"code"` Code int32 `json:"code"`
Msg string `json:"msg"` Message string `json:"message"`
Data any `json:"data"` Result any `json:"result"`
} }
func (reply *Reply) Success(ctx *gin.Context, data any) { func (reply *Reply) Success(ctx *gin.Context, data any) {
reply.Code = 200 reply.Code = 0
reply.Data = data reply.Result = data
reply.Msg = "" reply.Message = ""
if data == nil { if data == nil {
reply.Data = "" reply.Result = ""
} }
ctx.JSON(200, reply) ctx.JSON(200, reply)
} }
func (reply *Reply) Error(ctx *gin.Context, err error) { func (reply *Reply) Error(ctx *gin.Context, err error) {
reply.Code = 500 reply.Code = 500
reply.Data = "" reply.Result = ""
// Status code defaults to 500 // Status code defaults to 500
e, ok := status.FromError(err) e, ok := status.FromError(err)
if ok { if ok {
reply.Code = int(e.Code()) reply.Code = int32(e.Code())
} }
reply.Msg = e.Message() reply.Message = e.Message()
// Send error // Send error
ctx.JSON(200, reply) ctx.JSON(200, reply)

View File

@@ -58,8 +58,8 @@ var (
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
const ( const (
signKey = "8E853B589944FF7A56BEF02AAA51D6F4" signKey = "1F36659EC27CFFF849E068EA80B1A4CA"
LICENCE_KEY = "TRAIN_LICENCE_KEY" LICENCE_KEY = "BLOCKS_KEY"
) )
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -69,13 +69,13 @@ func init() {
} }
func WatchCheckLicence(licPath, licName string) { func WatchCheckLicence(licPath, licName string) {
for { utils.SetInterval(func() {
if CheckLicence(licPath, licName) == false { if CheckLicence(licPath, licName) == false {
log.Println("授权文件失效,请重新部署授权文件:", licPath) log.Println("授权文件失效,请重新部署授权文件:", licPath)
os.Exit(99) os.Exit(99)
} }
time.Sleep(time.Hour * 1)
} }, time.Hour*1)
} }
// -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -2,7 +2,6 @@ package middleware
import ( import (
"encoding/json" "encoding/json"
"fmt"
"log" "log"
"net/http" "net/http"
@@ -33,14 +32,14 @@ func JwtAuth(redis *redis.RedisClient) gin.HandlerFunc {
} }
// 从redis 获取token,判断当前redis 是否为空 // 从redis 获取token,判断当前redis 是否为空
tokenKey := fmt.Sprintf("%d-%s-%s", claims.ID, claims.Role, "token") // tokenKey := fmt.Sprintf("%d-%s-%s", claims.ID, claims.Role, "token")
redisToken := redis.Client.Get(redis.Ctx, tokenKey) // redisToken := redis.Client.Get(redis.Ctx, tokenKey)
if redisToken.Val() == "" { // if redisToken.Val() == "" {
log.Println("redis异常", "Token status unauthorized") // log.Println("redis异常", "Token status unauthorized")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token status unauthorized"}) // c.JSON(http.StatusUnauthorized, gin.H{"error": "Token status unauthorized"})
c.Abort() // c.Abort()
return // return
} // }
// 将解析后的 Token 存储到上下文中 // 将解析后的 Token 存储到上下文中
c.Set("Auth", claims) c.Set("Auth", claims)

14
oplog/new.go Normal file
View File

@@ -0,0 +1,14 @@
package oplog
import (
"encoding/json"
"git.apinb.com/bsm-sdk/core/utils"
)
func New(endpoint string, data []*LogItem) {
jsonBytes, _ := json.Marshal(data)
go utils.HttpPost(endpoint, nil, jsonBytes)
}

23
oplog/types.go Normal file
View File

@@ -0,0 +1,23 @@
package oplog
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"`
}
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"
)

View File

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

View File

@@ -25,7 +25,7 @@ func StringToFile(path, content string) error {
// rootDir: 文件夹根目录 // rootDir: 文件夹根目录
// s: 存储文件名的切片 // s: 存储文件名的切片
// filter: 过滤条件:".git", ".idea", ".vscode", ".gitignore", ".gitea", ".github", ".golangci.yml", "*.pyc" // filter: 过滤条件:".git", ".idea", ".vscode", ".gitignore", ".gitea", ".github", ".golangci.yml", "*.pyc"
func FileTree(rootDir string, s []string, filter []string) ([]string, error) { func FileTreeByFilter(rootDir string, s []string, filter []string) ([]string, error) {
rd, err := os.ReadDir(rootDir) rd, err := os.ReadDir(rootDir)
if err != nil { if err != nil {
return s, err return s, err
@@ -50,7 +50,48 @@ func FileTree(rootDir string, s []string, filter []string) ([]string, error) {
if fi.IsDir() { if fi.IsDir() {
fullDir := rootDir + "/" + fi.Name() fullDir := rootDir + "/" + fi.Name()
s, err = FileTree(fullDir, s, filter) 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 { if err != nil {
return s, err return s, err
} }

View File

@@ -2,6 +2,7 @@ package utils
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -121,6 +122,15 @@ func HttpGet(url string) ([]byte, error) {
return body, err 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) { func HttpPost(url string, header map[string]string, data []byte) ([]byte, error) {
var err error var err error
reader := bytes.NewBuffer(data) reader := bytes.NewBuffer(data)