Compare commits

..

38 Commits

Author SHA1 Message Date
63a4653eb2 add CopyFile 2025-08-21 10:59:18 +08:00
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
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
f7948263c5 Merge branch 'main' of https://git.apinb.com/bsm-sdk/core 2025-04-08 15:20:34 +08:00
fc42bc92ff fix parse meta ctx 2025-04-08 15:20:22 +08:00
b8f693ef82 fix net getLocationIP 2025-04-07 11:14:07 +08:00
6ec06c2813 add third 2025-04-06 13:42:44 +08:00
7983651fcd add time <=> string 2025-04-02 14:53:45 +08:00
d7fb72b5e0 add types.db identity default:uuid_generate_v4() 2025-04-01 23:34:24 +08:00
18 changed files with 591 additions and 104 deletions

View File

@@ -5,3 +5,23 @@ go env -w GONOPROXY=git.apinb.com/*
go env -w GOINSECURE=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
type Base struct {
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"`
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"`
OnMicroService bool `yaml:"OnMicroService"`
}
type DBConf struct {
@@ -48,11 +49,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 {

View File

@@ -4,10 +4,58 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"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 {
if len(data) == 0 {
return ""
@@ -24,7 +72,7 @@ func Encrypt(key string, iv string, data string) string {
return data
}
// AES解密
// AES CBC解密
func Decrypt(key string, iv string, data string) string {
if len(data) == 0 {
return ""
@@ -102,3 +150,24 @@ func generateKey(key []byte) (genKey []byte) {
}
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
var (
ErrEmpty = NewError(110, "Data Is Empty")
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(110, "Data Is Empty")
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")
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

View File

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

View File

@@ -17,14 +17,14 @@ var (
type service struct{}
func (s *service) Register(cli *clientv3.Client, serviceName string, port int) error {
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() + ":" + utils.Int2String(port)
serviceAddr := utils.GetLocationIP() + ":" + port
key := RootPrefix + serviceName + "/" + utils.Int642String(time.Now().UnixNano())
_, err = cli.KV.Put(context.TODO(), key, serviceAddr, clientv3.WithLease(grantResp.ID))

View File

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

72
middleware/jwt.go Normal file
View File

@@ -0,0 +1,72 @@
package middleware
import (
"encoding/json"
"log"
"net/http"
"git.apinb.com/bsm-sdk/core/cache/redis"
"git.apinb.com/bsm-sdk/core/crypto/encipher"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"github.com/gin-gonic/gin"
)
func JwtAuth(redis *redis.RedisClient) gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头中获取 Authorization
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
log.Println("获取token异常:", "Authorization header is required")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"})
c.Abort()
return
}
// 提取Token
claims, err := encipher.ParseTokenAes(authHeader)
if err != nil || claims == nil {
log.Println("提取token异常:", "Token is required")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
// 从redis 获取token,判断当前redis 是否为空
// tokenKey := fmt.Sprintf("%d-%s-%s", claims.ID, claims.Role, "token")
// redisToken := redis.Client.Get(redis.Ctx, tokenKey)
// if redisToken.Val() == "" {
// log.Println("redis异常", "Token status unauthorized")
// c.JSON(http.StatusUnauthorized, gin.H{"error": "Token status unauthorized"})
// c.Abort()
// return
// }
// 将解析后的 Token 存储到上下文中
c.Set("Auth", claims)
// 如果 Token 有效,继续处理请求
c.Next()
}
}
// 获取上下文用户登录信息
func ParseAuth(c *gin.Context) (*types.JwtClaims, error) {
claims, ok := c.Get("Auth")
if !ok {
log.Printf("获取登录信息异常: %v", errcode.ErrJWTAuthNotFound)
return nil, errcode.ErrJWTAuthNotFound
}
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
}

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

@@ -2,81 +2,57 @@ package service
import (
"context"
"encoding/json"
"strconv"
"git.apinb.com/bsm-sdk/core/crypto/encipher"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/utils"
"google.golang.org/grpc/metadata"
)
type Meta struct {
ID uint `json:"id"`
IDENTITY string `json:"identity"`
EXTEND map[string]string `json:"extend"`
CLIENT string `json:"client"`
}
// 解析Context中MetaData的数据
type ParseOptions struct {
RoleValue string // 判断角色的值
MustPrivateAllow bool // 是否只允许私有IP访问
}
func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*Meta, error) {
func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*types.JwtClaims, error) {
// 解析metada中的信息并验证
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errcode.ErrJWTAuthNotFound
}
// 安全获取 metadata 中的值
identityValues := md.Get("authorization_identity")
clientValues := md.Get("client")
if len(identityValues) == 0 {
return nil, errcode.ErrJWTAuthNotFound
}
if len(clientValues) == 0 {
var Authorizations []string = md.Get("authorization")
if len(Authorizations) == 0 || Authorizations[0] == "" {
return nil, errcode.ErrJWTAuthNotFound
}
meta := &Meta{
IDENTITY: md["authorization_identity"][0],
CLIENT: md["client"][0],
}
if id, err := strconv.Atoi(md["authorization_id"][0]); err != nil {
return nil, errcode.ErrJWTAuthKeyId
} else {
meta.ID = uint(id)
}
data := make(map[string]string)
if err := json.Unmarshal([]byte(md["authorization_extend"][0]), &data); err == nil {
meta.EXTEND = data
claims, err := encipher.ParseTokenAes(Authorizations[0])
if err != nil {
return nil, err
}
if opts != nil {
if !meta.CheckRole("role", opts.RoleValue) {
if !checkRole(claims, "role", opts.RoleValue) {
return nil, errcode.ErrPermissionDenied
}
if opts.MustPrivateAllow {
if utils.IsPublicIP(meta.CLIENT) {
if utils.IsPublicIP(claims.Client) {
return nil, errcode.ErrPermissionDenied
}
}
}
return meta, nil
return claims, nil
}
func (m *Meta) CheckRole(roleKey, roleValue string) bool {
func checkRole(claims *types.JwtClaims, roleKey, roleValue string) bool {
if roleValue == "" {
return true
}
if role, exists := m.EXTEND[roleKey]; !exists || role != roleValue {
if role, exists := claims.Extend[roleKey]; !exists || role != roleValue {
return false
} else {
return true

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
}

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 int64 `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 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

@@ -2,6 +2,7 @@ package utils
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
@@ -21,11 +22,37 @@ 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
}
// 递归遍历文件夹
// rootDir: 文件夹根目录
// s: 存储文件名的切片
// 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)
if err != nil {
return s, err
@@ -50,7 +77,48 @@ func FileTree(rootDir string, s []string, filter []string) ([]string, error) {
if fi.IsDir() {
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 {
return s, err
}

View File

@@ -2,6 +2,7 @@ package utils
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@@ -9,6 +10,7 @@ import (
"net/http"
"os"
"strconv"
"strings"
)
func IsPublicIP(ipString string) bool {
@@ -32,24 +34,55 @@ func IsPublicIP(ipString string) bool {
}
// Get Location IP .
func GetLocationIP() string {
addrs, err := net.InterfaceAddrs()
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 +122,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)

14
utils/time.go Normal file
View File

@@ -0,0 +1,14 @@
package utils
import (
"time"
)
func Time2String(layout string, t time.Time) string {
return t.Format(layout)
}
func String2Time(layout, in string) time.Time {
t, _ := time.ParseInLocation(layout, in, time.Local)
return t
}