refactor: reorganize modules and add Linux build tooling

This commit is contained in:
2026-08-09 12:41:08 +08:00
parent 86024fa81d
commit 5cc5fd92ed
1461 changed files with 4054 additions and 4724 deletions

View File

@@ -0,0 +1,42 @@
package config
import (
"net"
"git.apinb.com/bsm-sdk/core/conf"
"git.apinb.com/bsm-sdk/core/crypto/encipher"
"git.apinb.com/bsm-sdk/core/env"
)
var (
Spec SrvConfig
)
type SrvConfig struct {
conf.Base `yaml:",inline"`
Databases *conf.DBConf `yaml:"Databases"`
MicroService *conf.MicroServiceConf `yaml:"MicroService"`
Rpc map[string]conf.RpcConf `yaml:"Rpc"`
Gateway *conf.GatewayConf `yaml:"Gateway"`
Apm *conf.ApmConf `yaml:"APM"`
Etcd *conf.EtcdConf `yaml:"Etcd"`
}
func New(srvKey string) {
// 初始化配置 创建一个新的配置实例,用于服务配置
conf.New(srvKey, &Spec)
// 配置校验 服务IP,端口; 端口如果不合规,则随机分配端口
Spec.Port = conf.CheckPort(Spec.Port)
Spec.BindIP = conf.CheckIP(Spec.BindIP)
Spec.Addr = net.JoinHostPort(Spec.BindIP, Spec.Port)
Spec.Log = conf.InitLoggerConf(Spec.Log)
// 配置校验 服务名称地址及监听地址不能为空
conf.NotNil(Spec.Service, Spec.Cache)
// 初始化
encipher.New(env.Runtime.JwtSecretKey)
conf.PrintInfo(Spec.Addr)
}

View File

@@ -0,0 +1,12 @@
package excode
import "git.apinb.com/bsm-sdk/core/errcode"
var (
// 参数错误
ErrInvalidApp = errcode.ErrNotFound(404, "app")
ErrInvalidOS = errcode.ErrNotFound(404, "os")
ErrInvalidArch = errcode.ErrNotFound(404, "arch")
ErrInvalidVersion = errcode.ErrNotFound(404, "version")
ErrInvalidCountry = errcode.ErrNotFound(404, "country")
)

View File

@@ -0,0 +1,29 @@
package impl
import (
"bsm/full/module/base/initial/internal/config"
"git.apinb.com/bsm-sdk/core/cache/redis"
"git.apinb.com/bsm-sdk/core/with"
cache "github.com/patrickmn/go-cache"
clientv3 "go.etcd.io/etcd/client/v3"
"gorm.io/gorm"
)
var (
RedisService *redis.RedisClient // Redis 客户端服务
EtcdService *clientv3.Client // Etcd 客户端服务
DBService *gorm.DB // 数据库服务
MemorySerice *cache.Cache // 内存缓存服务
)
// NewImpl 初始化各类服务实例
func NewImpl() {
// 初始化内存缓存
MemorySerice = with.Memory(nil)
// 初始化 Redis 缓存
RedisService = with.RedisCache(config.Spec.Cache)
// 初始化数据库服务
DBService = with.Databases(config.Spec.Databases, nil)
// 初始化 Etcd 服务
EtcdService = with.Etcd(config.Spec.Etcd)
}

View File

@@ -0,0 +1,55 @@
package check
import (
"context"
"bsm/full/module/base/initial/internal/models"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/printer"
)
// 获取应用的相关配置信息
func Config(ctx context.Context, in *pb.ConfigRequest) (reply *pb.ConfigReply, err error) {
// 输入验证
if in == nil || in.App == "" || in.Os == "" {
return nil, errcode.ErrInvalidArgument
}
var (
configs []*models.InitialConfig
data []*pb.ConfigItem
)
// 查询配置优先返回特定OS的配置然后是通用配置
configs, err = GetConfigByCache(in.App, in.Os)
if err != nil {
printer.Error("GetConfigByCache", err)
return nil, errcode.ErrDB
}
// 去重处理同一个key只保留优先级最高的配置
configMap := make(map[string]*models.InitialConfig)
for _, item := range configs {
if existing, exists := configMap[item.Key]; !exists || item.Os != "ALL" {
configMap[item.Key] = item
} else if existing.Os == "ALL" && item.Os != "ALL" {
configMap[item.Key] = item
}
}
// 转换为响应格式
for _, item := range configMap {
data = append(data, &pb.ConfigItem{
Identity: item.Identity,
Key: item.Key,
Value: item.Value,
Version: item.Version,
})
}
return &pb.ConfigReply{
Data: data,
}, nil
}

View File

@@ -0,0 +1,29 @@
package check
import (
"bsm/full/module/base/initial/internal/impl"
"bsm/full/module/base/initial/internal/models"
"git.apinb.com/bsm-sdk/core/vars"
)
// GetConfigCache 获取配置缓存
func GetConfigByCache(app, os string) ([]*models.InitialConfig, error) {
var configs []*models.InitialConfig
key := impl.RedisService.BuildKey("config", app, os)
err := impl.RedisService.Get(key, &configs)
if err == nil {
return configs, nil
}
err = impl.DBService.Where("app = ? AND (os = ? OR os = ?)", app, os, "ALL").
Order("CASE WHEN os = '" + os + "' THEN 0 ELSE 1 END, id DESC").
Find(&configs).Error
if err != nil {
return nil, err
}
err = impl.RedisService.Set(key, configs, vars.DefaultTTL)
return configs, err
}

View File

@@ -0,0 +1,32 @@
package check
import (
"context"
"time"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/vars"
)
// HELLO - 健康检查接口
func Hello(ctx context.Context, in *pb.Crc) (reply *pb.StatusReply, err error) {
// 输入验证
if in.Code != "" {
return &pb.StatusReply{
Code: 0,
Message: vars.OK,
Details: "Hello " + in.Code,
Timeseq: time.Now().Unix(),
}, nil
}
// 可以在这里添加更多的健康检查逻辑
// 例如检查数据库连接、Redis连接等
return &pb.StatusReply{
Code: 0,
Message: vars.OK,
Details: "",
Timeseq: time.Now().Unix(),
}, nil
}

View File

@@ -0,0 +1,55 @@
package check
import (
"context"
"bsm/full/module/base/initial/internal/impl"
"bsm/full/module/base/initial/internal/models"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"gorm.io/gorm"
)
// 检查更新
func Updates(ctx context.Context, in *pb.CheckForUpdatesRequest) (reply *pb.CheckForUpdatesReply, err error) {
// 输入验证
if in == nil || in.App == "" || in.Os == "" || in.Arch == "" || in.Version == "" {
return nil, errcode.ErrInvalidArgument
}
var data models.InitialApps
// 查询最新版本
err = impl.DBService.Where("app = ? AND os = ? AND arch = ?", in.App, in.Os, in.Arch).
Order("pubdate DESC, id DESC").
First(&data).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
// 没有找到对应的应用版本
return &pb.CheckForUpdatesReply{
Identity: "0",
Version: "0",
}, nil
}
return nil, errcode.ErrDB
}
// 比较版本号
if in.Version != data.Version {
// 有新版本可用
return &pb.CheckForUpdatesReply{
Identity: data.Identity,
Version: data.Version,
Summary: data.Summary,
Files: data.Files,
Pubdate: data.Pubdate.Format("2006-01-02 15:04:05"),
}, nil
}
// 当前版本是最新的
return &pb.CheckForUpdatesReply{
Identity: data.Identity,
Version: data.Version,
}, nil
}

View File

@@ -0,0 +1,44 @@
package data
import (
"context"
"fmt"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/printer"
)
// 区域数据,默认级别:市
func Areas(ctx context.Context, in *pb.AreasRequest) (reply *pb.AreasReply, err error) {
// 参数检查 - 如果都没有提供,默认使用中国(ID=1)
if in.GetCountryCode() == "" && in.GetCountryId() == 0 {
in.CountryId = 1 // 默认中国
}
// 查询条件:国家代码优先
areas, err := GetAreasByCache(in)
if err != nil {
printer.Error("GetAreasByCache", err)
return nil, errcode.ErrDB
}
// 转换为响应格式
var data []*pb.AreasItem
for _, area := range areas {
data = append(data, &pb.AreasItem{
Id: fmt.Sprintf("%d", area.Id),
Pid: fmt.Sprintf("%d", area.Pid),
Deep: area.Deep,
Name: area.Name,
PinyinPrefix: area.PinyinPrefix,
Pinyin: area.Pinyin,
ExtId: area.ExtId,
ExtName: area.ExtName,
})
}
return &pb.AreasReply{
Areas: data,
}, nil
}

View File

@@ -0,0 +1,39 @@
package data
import (
"bsm/full/module/base/initial/internal/impl"
"bsm/full/module/base/initial/internal/models"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/vars"
)
func GetAreasByCache(in *pb.AreasRequest) ([]*models.InitialAreas, error) {
var areas []*models.InitialAreas
var (
key string
val any
)
if in.GetCountryCode() != "" {
key = "country_code = ?"
val = in.GetCountryCode()
} else if in.GetCountryId() != 0 {
key = "country_id = ?"
val = in.GetCountryId()
}
cacheKey := impl.RedisService.BuildKey("areas", key, val, in.ShowTown)
err := impl.RedisService.Get(cacheKey, &areas)
if err == nil {
return areas, nil
}
err = impl.DBService.Where("enabled = ?", true).Where(key, val).Where("show_town = ?", in.ShowTown).
Order("sort_order ASC, name ASC").
Find(&areas).Error
err = impl.RedisService.Set(cacheKey, areas, vars.DefaultTTL)
return areas, err
}

View File

@@ -0,0 +1,43 @@
package data
import (
"context"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/printer"
)
func Country(ctx context.Context, in *pb.Empty) (reply *pb.CountryReply, err error) {
// 查询启用的国家,按排序字段和名称排序
countries, err := GetCountryByCache()
if err != nil {
printer.Error("GetCountryByCache", err)
return nil, errcode.ErrDB
}
// 转换为响应格式
var data []*pb.CountryItem
for _, country := range countries {
data = append(data, &pb.CountryItem{
Id: uint32(country.Id),
Iso2: country.Iso2,
Iso3: country.Iso3,
NumCode: country.NumCode,
PhoneCode: country.PhoneCode,
Currency: country.Currency,
CurrencySymbol: country.CurrencySymbol,
Region: country.Region,
Timezones: country.Timezones,
Translations: country.Translations,
Name: country.Name,
LocalName: country.LocalName,
Enabled: country.Enabled,
SortOrder: country.SortOrder,
})
}
return &pb.CountryReply{
Countries: data,
}, nil
}

View File

@@ -0,0 +1,25 @@
package data
import (
"bsm/full/module/base/initial/internal/impl"
"bsm/full/module/base/initial/internal/models"
"git.apinb.com/bsm-sdk/core/vars"
)
func GetCountryByCache() ([]*models.InitialCountry, error) {
var country []*models.InitialCountry
cacheKey := impl.RedisService.BuildKey("country")
err := impl.RedisService.Get(cacheKey, &country)
if err == nil {
return country, nil
}
err = impl.DBService.Where("enabled = ?", true).
Order("sort_order ASC, name ASC").
Find(&country).Error
err = impl.RedisService.Set(cacheKey, country, vars.DefaultTTL)
return country, err
}

View File

@@ -0,0 +1,48 @@
package data
import (
"context"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/printer"
)
// 系统标签数据
func Datas(ctx context.Context, in *pb.Empty) (reply *pb.DatasReply, err error) {
// 查询系统数据按数据类型和ID排序
datas, err := GetDatasByCache()
if err != nil {
printer.Error("GetDatasByCache", err)
return nil, errcode.ErrDB
}
// 转换为响应格式
var data []*pb.DatasItem
// 添加默认的"全部"选项
data = append(data, &pb.DatasItem{
Id: 0,
DataType: "default",
Key: "all",
Title: "全部",
Remark: "默认全部选项",
Icon: "",
})
// 添加数据库中的数据
for _, item := range datas {
data = append(data, &pb.DatasItem{
Id: int64(item.ID),
DataType: item.DataType,
Key: item.Key,
Title: item.Title,
Remark: item.Remark,
Icon: item.Icon,
})
}
return &pb.DatasReply{
Datas: data,
}, nil
}

View File

@@ -0,0 +1,29 @@
package data
import (
"bsm/full/module/base/initial/internal/impl"
"bsm/full/module/base/initial/internal/models"
"gorm.io/gorm"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/vars"
)
func GetDatasByCache() ([]*models.InitialDatas, error) {
var datas []*models.InitialDatas
cacheKey := impl.RedisService.BuildKey("datas")
err := impl.RedisService.Get(cacheKey, &datas)
if err == nil {
return datas, nil
}
err = impl.DBService.Order("data_type ASC, id ASC").Find(&datas).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, errcode.ErrDB
}
err = impl.RedisService.Set(cacheKey, datas, vars.DefaultTTL)
return datas, err
}

View File

@@ -0,0 +1,28 @@
package models
import (
"time"
"git.apinb.com/bsm-sdk/core/database"
"git.apinb.com/bsm-sdk/core/types"
)
// 应用版本更新表
type InitialApps struct {
types.Std_IICUDS
Version string `gorm:"type:varchar(255);default:'';"` // 版本号
App string `gorm:"type:varchar(255);default:'';"` // 应用名称
Os string `gorm:"type:varchar(255);default:'';"` // 操作系统
Arch string `gorm:"type:varchar(255);default:'';"` // 平台
Summary string `gorm:"type:text;default:'';"` // 简介
Files string `gorm:"type:text;default:'';"` // 更新文件以及文件hash
Pubdate time.Time // 发布时间
}
func init() {
database.AppendMigrate(&InitialApps{})
}
func (table *InitialApps) TableName() string {
return "initial_apps"
}

View File

@@ -0,0 +1,27 @@
// Models generated by mesh dev cli,@Author: David Yan(david.yan@qq.com).
package models
import "git.apinb.com/bsm-sdk/core/database"
// 地区表
type InitialAreas struct {
Id uint `gorm:"column:id;" json:"id"` //
CountryID uint `gorm:"index" json:"country_id"` //
CountryCode string `gorm:"column:country_code;index;type:varchar(255);index;" json:"country_code"` // 国家代码
Pid uint `gorm:"column:pid;index;" json:"pid"` //
Deep int32 `gorm:"column:deep;" json:"deep"` //
Name string `gorm:"column:name;type:varchar(255);" json:"name"` //
PinyinPrefix string `gorm:"column:pinyin_prefix;type:varchar(255);index;" json:"pinyin_prefix"` //
Pinyin string `gorm:"column:pinyin;type:varchar(255);" json:"pinyin"` //
ExtId string `gorm:"column:ext_id;type:varchar(255);" json:"ext_id"` //
ExtName string `gorm:"column:ext_name;" json:"ext_name"` //
}
func init() {
database.AppendMigrate(&InitialAreas{})
}
// TableName .
func (table *InitialAreas) TableName() string {
return "initial_areas" //对应数据库表名
}

View File

@@ -0,0 +1,24 @@
package models
import (
"git.apinb.com/bsm-sdk/core/database"
"git.apinb.com/bsm-sdk/core/types"
)
// 系统配置表
type InitialConfig struct {
types.Std_IICUDS
App string `gorm:"type:varchar(255);default:'';"` // 应用名称
Os string `gorm:"type:varchar(255);default:'';"` // 操作系统,ALL代表全平台
Key string `gorm:"type:text;default:'';"` // 配置主键名称
Value string `gorm:"type:text;default:'';"` // 配置参数
Version int64 `gorm:"default:0;"` // 版本号
}
func init() {
database.AppendMigrate(&InitialConfig{})
}
func (table *InitialConfig) TableName() string {
return "initial_config"
}

View File

@@ -0,0 +1,30 @@
package models
import "git.apinb.com/bsm-sdk/core/database"
// 国家表
type InitialCountry struct {
Id uint `gorm:"column:id;" json:"id"` //
Iso2 string `gorm:"type:varchar(255);uniqueIndex" json:"iso2"` // ISO 3166-1 alpha-2
Iso3 string `gorm:"type:varchar(255);uniqueIndex" json:"iso3"` // ISO 3166-1 alpha-3
NumCode string `gorm:"type:varchar(255)" json:"num_code"` // ISO 3166-1 numeric
PhoneCode string `gorm:"type:varchar(255)" json:"phone_code"` // 国际电话区号
Currency string `gorm:"type:varchar(24);not null" json:"currency"` // 货币简称
CurrencySymbol string `gorm:"type:varchar(24);not null" json:"currency_symbol"` // 货币符号
Region string `gorm:"type:varchar(24);not null" json:"region"` // 所属地区
Name string `gorm:"type:varchar(255);not null" json:"name"` // 英文名称
LocalName string `gorm:"type:varchar(255);not null" json:"local_name"` // 本地语言名称
Timezones string // 时区配置
Translations string // 翻译
Enabled bool `gorm:"default:true" json:"enabled"` // 是否启用
SortOrder int32 `gorm:"default:0" json:"sort_order"` // 排序
}
func init() {
database.AppendMigrate(&InitialCountry{})
}
// TableName .
func (table *InitialCountry) TableName() string {
return "initial_country" //对应数据库表名
}

View File

@@ -0,0 +1,26 @@
// Models generated by mesh dev cli,@Author: David Yan(david.yan@qq.com).
package models
import (
"git.apinb.com/bsm-sdk/core/database"
"git.apinb.com/bsm-sdk/core/types"
)
// 系统数据表
type InitialDatas struct {
types.Std_IICUDS
DataType string `gorm:"column:data_type;type:varchar(255);index;" json:"data_type"` // 数据类型
Key string `gorm:"column:key;type:varchar(255);uniqueIndex;" json:"key"`
Title string `gorm:"column:title;type:varchar(255);default:'';" json:"title"`
Remark string `gorm:"column:remark;type:varchar(255);default:'';" json:"remark"`
Icon string `gorm:"column:icon;default:'';" json:"icon"`
}
func init() {
database.AppendMigrate(&InitialDatas{})
}
// TableName .
func (table *InitialDatas) TableName() string {
return "initial_datas" //对应数据库表名
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,31 @@
// Code generated by protoc-gen-slc. DO NOT EDIT.
package server
import (
"context"
"bsm/full/module/base/initial/internal/logic/check"
pb "bsm/full/module/base/initial/pb"
)
type CheckServer struct {
pb.UnimplementedCheckServer
}
func NewCheckServer() *CheckServer {
return &CheckServer{}
}
// HELLO
func (s *CheckServer) Hello(ctx context.Context, in *pb.Crc) (*pb.StatusReply, error) {
return check.Hello(ctx, in)
}
// 获取应用的相关配置信息
func (s *CheckServer) Config(ctx context.Context, in *pb.ConfigRequest) (*pb.ConfigReply, error) {
return check.Config(ctx, in)
}
// 检查更新
func (s *CheckServer) Updates(ctx context.Context, in *pb.CheckForUpdatesRequest) (*pb.CheckForUpdatesReply, error) {
return check.Updates(ctx, in)
}

View File

@@ -0,0 +1,30 @@
// Code generated by protoc-gen-slc. DO NOT EDIT.
package server
import (
"context"
"bsm/full/module/base/initial/internal/logic/data"
pb "bsm/full/module/base/initial/pb"
)
type DataServer struct {
pb.UnimplementedDataServer
}
func NewDataServer() *DataServer {
return &DataServer{}
}
func (s *DataServer) Country(ctx context.Context, in *pb.Empty) (*pb.CountryReply, error) {
return data.Country(ctx, in)
}
// 系统区域数据,默认级别:市
func (s *DataServer) Areas(ctx context.Context, in *pb.AreasRequest) (*pb.AreasReply, error) {
return data.Areas(ctx, in)
}
// 系统数据
func (s *DataServer) Datas(ctx context.Context, in *pb.Empty) (*pb.DatasReply, error) {
return data.Datas(ctx, in)
}

View File

@@ -0,0 +1,96 @@
// Code generated by protoc-gen-slc. DO NOT EDIT.
package server
import (
"context"
pb "bsm/full/module/base/initial/pb"
"git.apinb.com/bsm-sdk/core/vars"
gwRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection"
"google.golang.org/protobuf/proto"
"net/http"
"os"
"strings"
"time"
)
type Server struct {
Grpc *grpc.Server
Ctx context.Context
Mux *gwRuntime.ServeMux
grpcConns map[string]*grpc.ClientConn // 连接池
}
func New(addr string) *Server {
srv := &Server{
Ctx: context.Background(),
Grpc: grpc.NewServer(),
Mux: gwRuntime.NewServeMux(gwRuntime.WithForwardResponseRewriter(responseEnvelope)),
grpcConns: make(map[string]*grpc.ClientConn),
}
// register service to grpc.Server
pb.RegisterCheckServer(srv.Grpc, NewCheckServer())
pb.RegisterDataServer(srv.Grpc, NewDataServer())
reflection.Register(srv.Grpc)
// 连接池: 只创建一次连接并复用
conn, ok := srv.grpcConns[addr]
if !ok {
var err error
conn, err = grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic("failed to dial grpc server: " + err.Error())
}
srv.grpcConns[addr] = conn
}
// 将服务注册到Gateway
if err := pb.RegisterCheckHandler(srv.Ctx, srv.Mux, conn); err != nil {
panic("Failed to register Check handler: " + err.Error())
}
if err := pb.RegisterDataHandler(srv.Ctx, srv.Mux, conn); err != nil {
panic("Failed to register Data handler: " + err.Error())
}
// Register services swagger
srv.RegisterSwagger()
return srv
}
// RegisterSwagger 注册swagger
func (s *Server) RegisterSwagger() {
srvKey := strings.ToLower(vars.ServiceKey)
s.Mux.HandlePath("GET", "/"+srvKey+".swagger.json", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
w.Header().Set("Content-Type", "application/json")
bytes, err := os.ReadFile("./swagger/" + srvKey + ".swagger.json")
if err != nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(err.Error()))
return
}
w.Write(bytes)
return
})
}
// response envelope
func responseEnvelope(_ context.Context, response proto.Message) (interface{}, error) {
name := string(response.ProtoReflect().Descriptor().Name())
if name == "Status" || name == "Error" || name == "StatusReply" {
return response, nil
}
return map[string]any{
"code": 0,
"message": vars.OK,
"details": response,
"timeseq": time.Now().Unix(),
}, nil
}