Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21716c4340 | |||
| 518c237061 | |||
| 139983134b | |||
| 75aa6ae647 | |||
| f681f0bb17 | |||
| 44319d03b9 | |||
| 2f57edd277 | |||
| cf0ee224f7 |
@@ -54,7 +54,7 @@ func New(srvKey string, cfg any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 解析YAML
|
// 解析YAML
|
||||||
err = yaml.Unmarshal(yamlFile, cfg)
|
err = yaml.Unmarshal([]byte(yamlString), cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ERROR: %v", err)
|
log.Fatalf("ERROR: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
// MigrateTables holds the tables that need to be auto-migrated on database initialization
|
// MigrateTables holds the tables that need to be auto-migrated on database initialization
|
||||||
MigrateTables []any
|
MigrateTables []any
|
||||||
// Init is an optional initialization function that can be executed after database connection is established
|
|
||||||
Init *func() = nil
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDatabase creates a new database connection based on the provided driver type
|
// NewDatabase creates a new database connection based on the provided driver type
|
||||||
@@ -36,9 +34,12 @@ func NewDatabase(driver string, dsn []string, options *types.SqlOptions) (db *go
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the Init function if it's not nil
|
// auto migrate table.
|
||||||
if Init != nil {
|
if len(MigrateTables) > 0 {
|
||||||
(*Init)()
|
err = db.AutoMigrate(MigrateTables...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
@@ -81,13 +82,6 @@ func NewMysql(dsn []string, options *types.SqlOptions) (gormDb *gorm.DB, err err
|
|||||||
// SetConnMaxLifetime 设置了连接可复用的最大时间。
|
// SetConnMaxLifetime 设置了连接可复用的最大时间。
|
||||||
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
|
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
|
||||||
|
|
||||||
if len(MigrateTables) > 0 {
|
|
||||||
err = gormDb.AutoMigrate(MigrateTables...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return gormDb, nil
|
return gormDb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,12 +120,5 @@ func NewPostgres(dsn []string, options *types.SqlOptions) (gormDb *gorm.DB, err
|
|||||||
// SetConnMaxLifetime 设置了连接可复用的最大时间。
|
// SetConnMaxLifetime 设置了连接可复用的最大时间。
|
||||||
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
|
sqlDB.SetConnMaxLifetime(options.ConnMaxLifetime)
|
||||||
|
|
||||||
if len(MigrateTables) > 0 {
|
|
||||||
err = gormDb.AutoMigrate(MigrateTables...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
204
service/address.go
Normal file
204
service/address.go
Normal 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
|
||||||
|
}
|
||||||
@@ -108,6 +108,14 @@ func (s *Service) Gateway(grpcAddr string, httpAddr string) {
|
|||||||
http.ListenAndServe(httpAddr, s.Opts.GatewayMux)
|
http.ListenAndServe(httpAddr, s.Opts.GatewayMux)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) Use(initFunc func() error) {
|
||||||
|
err := (initFunc)()
|
||||||
|
if err != nil {
|
||||||
|
print.Error(err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) Stop() {
|
func (s *Service) Stop() {
|
||||||
s.GrpcSrv.GracefulStop()
|
s.GrpcSrv.GracefulStop()
|
||||||
}
|
}
|
||||||
|
|||||||
129
utils/validator.go
Normal file
129
utils/validator.go
Normal 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')
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Databases(cfg *conf.DBConf, db *gorm.DB, opts *types.SqlOptions) {
|
func Databases(cfg *conf.DBConf, opts *types.SqlOptions) *gorm.DB {
|
||||||
if cfg == nil || len(cfg.Source) == 0 {
|
if cfg == nil || len(cfg.Source) == 0 {
|
||||||
panic("No Database Source Found !")
|
panic("No Database Source Found !")
|
||||||
}
|
}
|
||||||
@@ -18,10 +18,10 @@ func Databases(cfg *conf.DBConf, db *gorm.DB, opts *types.SqlOptions) {
|
|||||||
print.Info("[BSM - %s] Databases: %v", vars.ServiceKey, cfg)
|
print.Info("[BSM - %s] Databases: %v", vars.ServiceKey, cfg)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
db, err = database.NewDatabase(cfg.Driver, cfg.Source, opts)
|
db, err := database.NewDatabase(cfg.Driver, cfg.Source, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
print.Error("Database Init Failed !")
|
print.Error("Database Init Failed !")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return
|
return db
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
clientv3 "go.etcd.io/etcd/client/v3"
|
clientv3 "go.etcd.io/etcd/client/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Etcd(cfg *conf.EtcdConf, cli *clientv3.Client) {
|
func Etcd(cfg *conf.EtcdConf) (cli *clientv3.Client){
|
||||||
if cfg == nil || len(cfg.Endpoints) == 0 {
|
if cfg == nil || len(cfg.Endpoints) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/allegro/bigcache/v3"
|
"github.com/allegro/bigcache/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Memory(cli *bigcache.BigCache, opts *bigcache.Config) {
|
func Memory(opts *bigcache.Config) (cli *bigcache.BigCache) {
|
||||||
if opts == nil {
|
if opts == nil {
|
||||||
opts = &bigcache.Config{
|
opts = &bigcache.Config{
|
||||||
Shards: 1024,
|
Shards: 1024,
|
||||||
@@ -32,4 +32,5 @@ func Memory(cli *bigcache.BigCache, opts *bigcache.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
print.Success("[BSM - %s] Memory Cache: Shards=%d, MaxEntrySize=%d", vars.ServiceKey, opts.Shards, opts.MaxEntrySize)
|
print.Success("[BSM - %s] Memory Cache: Shards=%d, MaxEntrySize=%d", vars.ServiceKey, opts.Shards, opts.MaxEntrySize)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import (
|
|||||||
"git.apinb.com/bsm-sdk/core/vars"
|
"git.apinb.com/bsm-sdk/core/vars"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RedisCache(cfg string, cli *redis.RedisClient) {
|
func RedisCache(cfg string) (cli *redis.RedisClient) {
|
||||||
if cfg != "" {
|
if cfg != "" {
|
||||||
cli = redis.New(cfg, vars.ServiceKey)
|
cli = redis.New(cfg, vars.ServiceKey)
|
||||||
|
|
||||||
// print inform.
|
// print inform.
|
||||||
print.Info("[BSM - %s] Cache: %s, DBIndex: %d", vars.ServiceKey, cfg, cli.DB)
|
print.Info("[BSM - %s] Cache: %s, DBIndex: %d", vars.ServiceKey, cfg, cli.DB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user