Browse Source

first commit

shao 1 week ago
parent
commit
09b57c5394
26 changed files with 1272 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 4 0
      .gitmodules
  3. 17 0
      Makefile
  4. 32 0
      README.md
  5. 37 0
      cmd/cobra.go
  6. 49 0
      cmd/crypto/crypto.go
  7. 103 0
      cmd/server/server.go
  8. 35 0
      cmd/version/version.go
  9. 217 0
      common/config.go
  10. 95 0
      common/enum.go
  11. 16 0
      common/global.go
  12. 31 0
      common/translator.go
  13. 84 0
      common/utils.go
  14. 43 0
      config/config.toml.template
  15. 41 0
      go.mod
  16. 112 0
      go.sum
  17. 103 0
      logger/gorm.go
  18. 86 0
      logger/logger.go
  19. 9 0
      main.go
  20. 14 0
      models/model.go
  21. 34 0
      models/scope.go
  22. 57 0
      models/setup.go
  23. 1 0
      rpc_idl
  24. 25 0
      service/basic.go
  25. 12 0
      static/locale/common.en.toml
  26. 12 0
      static/locale/common.zh.toml

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea
+logs/*
+config/config.toml

+ 4 - 0
.gitmodules

@@ -0,0 +1,4 @@
+[submodule "rpc_idl"]
+	path = rpc_idl
+	url = http://code.pacsonline.cn/DR/rpc_idl.git
+	branch = v1

+ 17 - 0
Makefile

@@ -0,0 +1,17 @@
+BINARY=protocol-server
+
+MODULE=protocol-server
+DESC=Protocol Server repo
+VERSION=`git describe --tags --long`
+BUILD=`date +%F\ %T`
+
+LDFLAGS=-ldflags "-X 'protocol-server/common.Module=${MODULE}' -X 'protocol-server/common.Desc=${DESC}' -X 'protocol-server/common.Version=${VERSION}' -X 'protocol-server/common.Build=${BUILD}'"
+
+build:
+	go build ${LDFLAGS} -o ${BINARY}
+
+install:
+	go install ${LDFLAGS}
+
+clean:
+	if [ -f ${BINARY} ] ; then rm ${BINARY}; fi

+ 32 - 0
README.md

@@ -0,0 +1,32 @@
+DR Protocol Server
+
+#### env
+- golang 1.24
+- postgres14
+
+#### run
+```shell
+go run main.go run
+```
+
+#### deploy
+```shell
+# vim /etc/systemd/system/protocol-server.service
+[Unit]
+Description=dr protocol service
+After=network.target
+ 
+[Service]
+Type=simple
+WorkingDirectory=xxx/DRProtocolServer
+ExecStart=xxx/DRProtocolServer/protocol-server run
+ 
+[Install]
+WantedBy=multi-user.target
+
+# 重新加载systemd配置
+sudo systemctl daemon-load
+
+# 启动服务
+sudo systemctl restart protocol-server.service
+```

+ 37 - 0
cmd/cobra.go

@@ -0,0 +1,37 @@
+package cmd
+
+import (
+	"os"
+)
+
+import (
+	"github.com/spf13/cobra"
+)
+
+import (
+	"protocol-server/cmd/crypto"
+	"protocol-server/cmd/server"
+	"protocol-server/cmd/version"
+)
+
+var rootCmd = &cobra.Command{
+	Use:          "protocol-server",
+	Short:        "protocol-server run",
+	SilenceUsage: true,
+	Run: func(cmd *cobra.Command, args []string) {
+		cmd.Help()
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(server.StartCmd)
+	rootCmd.AddCommand(version.StartCmd)
+	rootCmd.AddCommand(crypto.StartCmd)
+}
+
+// Execute : apply commands
+func Execute() {
+	if err := rootCmd.Execute(); err != nil {
+		os.Exit(-1)
+	}
+}

+ 49 - 0
cmd/crypto/crypto.go

@@ -0,0 +1,49 @@
+package crypto
+
+import (
+	"fmt"
+)
+
+import (
+	"github.com/spf13/cobra"
+)
+
+import (
+	"protocol-server/common"
+)
+
+var (
+	orig     string
+	StartCmd = &cobra.Command{
+		Use:     "crypto",
+		Short:   "crypto",
+		Example: "protocol-server crypto -o 123456",
+		PreRun: func(cmd *cobra.Command, args []string) {
+
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return run()
+		},
+	}
+)
+
+func init() {
+	StartCmd.Flags().StringVarP(&orig, "orig", "o", "", "orig")
+}
+
+func run() error {
+	ciphertext, err := common.DBPwdEncrypt([]byte(orig))
+	if err != nil {
+		fmt.Println("Error encrypting:", err)
+		return err
+	}
+	fmt.Printf("Ciphertext: %x\n", ciphertext)
+
+	decryptedtext, err := common.DBPwdDecrypt(ciphertext)
+	if err != nil {
+		fmt.Println("Error decrypting:", err)
+		return err
+	}
+	fmt.Printf("Decryptedtext: %s\n", string(decryptedtext))
+	return nil
+}

+ 103 - 0
cmd/server/server.go

@@ -0,0 +1,103 @@
+package server
+
+import (
+	"fmt"
+	"log/slog"
+	"net"
+	"os"
+	"os/signal"
+	"strings"
+)
+
+import (
+	"github.com/spf13/cobra"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/health"
+	"google.golang.org/grpc/health/grpc_health_v1"
+)
+
+import (
+	"protocol-server/common"
+	"protocol-server/logger"
+	"protocol-server/models"
+	pb "protocol-server/rpc_idl/dr_protocol_pb"
+	"protocol-server/service"
+)
+
+var (
+	configFolder string
+	addr         string
+	mode         string
+	StartCmd     = &cobra.Command{
+		Use:          "run",
+		Short:        "Start API server",
+		Example:      "protocol-server run -c config/config.toml",
+		SilenceUsage: true,
+		PreRun: func(cmd *cobra.Command, args []string) {
+			setup()
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return run()
+		},
+	}
+)
+
+func init() {
+	StartCmd.Flags().StringVarP(&configFolder, "config", "c", "config/", "Start server with provided configuration folder")
+	StartCmd.Flags().StringVarP(&addr, "addr", "a", "", "Tcp server listening on")
+	StartCmd.Flags().StringVarP(&mode, "mode", "m", "debug", "server mode ; eg:debug,test,release")
+}
+
+func setup() {
+	if strings.HasSuffix(configFolder, "/") {
+		configFolder = configFolder[:len(configFolder)-1]
+		if strings.HasSuffix(configFolder, "/") {
+			panic("invalid config folder")
+		}
+	}
+	configToml := configFolder + "/config.toml"
+	//1. 读取配置
+	common.SetupConfig(configToml, addr, mode)
+	//2. 设置日志
+	logger.SetupLogger(true)
+	//3. 初始化gorm
+	models.SetupGorm(true)
+	//4. 初始化i18n
+	common.SetupTrans()
+
+	slog.Info(`starting server`, "pid", os.Getpid())
+}
+
+func run() error {
+	slog.Info(fmt.Sprintf("common.ServerConfig.Protocol %v", common.ServerConfig.Protocol))
+	lis, err := net.Listen("tcp", common.ServerConfig.Protocol)
+	if err != nil {
+		slog.Error("failed to listen", "err", err.Error())
+		return err
+	}
+	s := grpc.NewServer()
+	grpc_health_v1.RegisterHealthServer(s, health.NewServer())
+	pb.RegisterBasicServer(s, &service.BasicServer{})
+
+	slog.Info("module: " + common.Module)
+	slog.Info("desc: " + common.Desc)
+	slog.Info("version: " + common.Version)
+	slog.Info("build: " + common.Build)
+	slog.Info(fmt.Sprintf("server run at: %s", common.Now()))
+	slog.Info(fmt.Sprintf("server listening at %v", lis.Addr()))
+	slog.Info("Enter Control + C Shutdown Server \n")
+	if err := s.Serve(lis); err != nil {
+		slog.Error("failed to serve", "err", err.Error())
+		return err
+	}
+
+	quit := make(chan os.Signal)
+	signal.Notify(quit, os.Interrupt)
+	<-quit
+	fmt.Printf("%s Shutdown Server ... \n", common.Now())
+
+	s.GracefulStop()
+	slog.Info("Server exiting")
+	logger.ShutdownLogger()
+	return nil
+}

+ 35 - 0
cmd/version/version.go

@@ -0,0 +1,35 @@
+package version
+
+import (
+	"fmt"
+)
+
+import (
+	"github.com/spf13/cobra"
+)
+
+import (
+	"protocol-server/common"
+)
+
+var (
+	StartCmd = &cobra.Command{
+		Use:     "version",
+		Short:   "Get version info",
+		Example: "protocol-server version",
+		PreRun: func(cmd *cobra.Command, args []string) {
+
+		},
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return run()
+		},
+	}
+)
+
+func run() error {
+	fmt.Printf("module: %s\n", common.Module)
+	fmt.Printf("desc: %s\n", common.Desc)
+	fmt.Printf("version: %s\n", common.Version)
+	fmt.Printf("build: %s\n", common.Build)
+	return nil
+}

+ 217 - 0
common/config.go

@@ -0,0 +1,217 @@
+package common
+
+import (
+	"encoding/hex"
+	"fmt"
+)
+
+import (
+	"github.com/spf13/viper"
+)
+
+//配置文件
+
+var (
+	BasicConfig    = new(basic)
+	ServerConfig   = new(server)
+	LoggerConfig   = new(logger)
+	RobotConfig    = new(robot)
+	PostgresConfig = new(postgres)
+	MetadataConfig = new(metadata)
+)
+
+// 应用配置
+type server struct {
+	Auth     string
+	Resource string
+	Study    string
+	Protocol string
+}
+
+type basic struct {
+	Jwt  string
+	Mode string
+}
+
+// 日志配置
+type logger struct {
+	LogDir     string //日志文件夹路径
+	LogLevel   string //日志打印等级
+	MaxSize    int    //在进行切割之前,日志文件的最大大小(以MB为单位)
+	MaxBackups int    //最多保留文件个数
+	MaxAge     int    //保留旧文件的最大天数
+	Compress   bool   //是否压缩/归档旧文件
+	Stdout     bool
+}
+
+type robot struct {
+	Info string
+	Dev  string
+	Prod string
+}
+
+// 数据库配置
+type postgres struct {
+	Ip       string
+	Port     int
+	Username string
+	Password string
+	Name     string
+}
+
+func (p *postgres) setup() {
+	raw, err := hex.DecodeString(p.Password)
+	if err != nil {
+		panic("db password is invalid")
+	}
+	pwd, err := DBPwdDecrypt(raw)
+	if err != nil {
+		panic("db password is invalid")
+	}
+	p.Password = string(pwd)
+}
+
+// metadata配置
+type metadata struct {
+	Locales   []string
+	Languages []Lang
+	Product   Product
+	Sources   []Source
+}
+
+func (p *metadata) setup() {
+	if !ValidLanguages(p.Languages) {
+		panic(fmt.Sprintf("invalid languages, optional values are: %v", AllLanguages()))
+	}
+	if !ValidProduct(p.Product) {
+		panic(fmt.Sprintf("invalid product, optional values are: %v", AllProducts()))
+	}
+	if !ValidSources(p.Sources) {
+		panic(fmt.Sprintf("invalid sources, optional values are: %v", AllSources()))
+	}
+}
+
+func (p *metadata) GetLanguages() []Lang {
+	if len(p.Languages) == 0 {
+		return AllLanguages()
+	}
+	return p.Languages
+}
+
+func (p *metadata) GetProduct() Product {
+	return p.Product
+}
+
+func (p *metadata) GetSources() []Source {
+	if len(p.Sources) == 0 {
+		return AllSources()
+	}
+	return p.Sources
+}
+
+func InitBasic(cfg *viper.Viper) *basic {
+	var basic basic
+	err := cfg.Unmarshal(&basic)
+	if err != nil {
+		panic("InitBasic err")
+	}
+	return &basic
+}
+
+func InitServer(cfg *viper.Viper) *server {
+	var server server
+	err := cfg.Unmarshal(&server)
+	if err != nil {
+		panic("InitServer err")
+	}
+	return &server
+}
+
+func InitLog(cfg *viper.Viper) *logger {
+	var logger logger
+	err := cfg.Unmarshal(&logger)
+	if err != nil {
+		panic("InitLog err")
+	}
+	return &logger
+}
+
+func InitRobot(cfg *viper.Viper) *robot {
+	var robot robot
+	err := cfg.Unmarshal(&robot)
+	if err != nil {
+		panic("InitRobot error")
+	}
+	return &robot
+}
+
+func InitPostgres(cfg *viper.Viper) *postgres {
+	var postgres postgres
+	err := cfg.Unmarshal(&postgres)
+	if err != nil {
+		panic("InitPostgres err")
+	}
+
+	postgres.setup()
+	return &postgres
+}
+
+func InitMetadata(cfg *viper.Viper) *metadata {
+	var metadata metadata
+	err := cfg.Unmarshal(&metadata)
+	if err != nil {
+		panic("InitMetadata err")
+	}
+
+	metadata.setup()
+	return &metadata
+}
+
+// 载入配置文件
+func SetupConfig(path string, addr string, mode string) {
+	viper.SetConfigFile(path)
+	if err := viper.ReadInConfig(); err != nil {
+		panic(err)
+	}
+
+	cfgBasic := viper.Sub("basic")
+	if cfgBasic == nil {
+		panic("No found basic in the configuration")
+	}
+	BasicConfig = InitBasic(cfgBasic)
+
+	cfgServer := viper.Sub("server")
+	if cfgServer == nil {
+		panic("No found server in the configuration")
+	}
+	ServerConfig = InitServer(cfgServer)
+
+	BasicConfig.Mode = mode
+	if addr != "" {
+		ServerConfig.Protocol = addr
+	}
+
+	cfgLog := viper.Sub("logger")
+	if cfgLog == nil {
+		panic("No found logger in the configuration")
+	}
+	LoggerConfig = InitLog(cfgLog)
+
+	cfgRobot := viper.Sub("robot")
+	if cfgLog == nil {
+		panic("No found robot in the configuration")
+	}
+	RobotConfig = InitRobot(cfgRobot)
+
+	cfgPostgres := viper.Sub("postgres")
+	if cfgPostgres == nil {
+		panic("No found postgres in the configuration")
+	}
+	PostgresConfig = InitPostgres(cfgPostgres)
+
+	metadataApolloConfig := viper.Sub("metadata")
+	if metadataApolloConfig == nil {
+		panic("No found metadata in the configuration")
+	}
+	MetadataConfig = InitMetadata(metadataApolloConfig)
+}

+ 95 - 0
common/enum.go

@@ -0,0 +1,95 @@
+package common
+
+type Lang string
+
+const (
+	LANG_EN Lang = "en"
+	LANG_ZH Lang = "zh"
+)
+
+func (p Lang) ToString() string {
+	switch p {
+	case LANG_EN:
+		return "en"
+	case LANG_ZH:
+		return "zh"
+	default:
+		return ""
+	}
+}
+
+func ValidLanguages(langs []Lang) bool {
+	for _, lang := range langs {
+		if lang.ToString() == "" {
+			return false
+		}
+	}
+	return true
+}
+
+func AllLanguages() []Lang {
+	return []Lang{LANG_EN, LANG_ZH}
+}
+
+type Source string
+
+const (
+	SOURCE_GUI     Source = "gui"
+	SOURCE_BROWSER Source = "browser"
+	SOURCE_ANDROID Source = "android"
+	SOURCE_DEV     Source = "dev"
+)
+
+func (p Source) ToString() string {
+	switch p {
+	case SOURCE_GUI:
+		return "gui"
+	case SOURCE_BROWSER:
+		return "browser"
+	case SOURCE_ANDROID:
+		return "android"
+	case SOURCE_DEV:
+		return "dev"
+	default:
+		return ""
+	}
+}
+
+func ValidSources(sources []Source) bool {
+	for _, source := range sources {
+		if source.ToString() == "" {
+			return false
+		}
+	}
+	return true
+}
+
+func AllSources() []Source {
+	return []Source{SOURCE_GUI, SOURCE_BROWSER, SOURCE_ANDROID, SOURCE_DEV}
+}
+
+type Product string
+
+const (
+	PRODUCT_DROC Product = "droc"
+	PRODUCT_VET  Product = "vetdroc"
+)
+
+func (p Product) ToString() string {
+	switch p {
+	case PRODUCT_DROC:
+		return "droc"
+	case PRODUCT_VET:
+		return "vetdroc"
+	default:
+		return ""
+	}
+}
+
+func ValidProduct(product Product) bool {
+	return product.ToString() != ""
+}
+
+func AllProducts() []Product {
+	return []Product{PRODUCT_DROC, PRODUCT_VET}
+}

+ 16 - 0
common/global.go

@@ -0,0 +1,16 @@
+package common
+
+import "os"
+
+var (
+	Module  = ""
+	Desc    = ""
+	Version = "latest"
+	Build   = "current"
+
+	Hostname string
+)
+
+func init() {
+	Hostname, _ = os.Hostname()
+}

+ 31 - 0
common/translator.go

@@ -0,0 +1,31 @@
+package common
+
+import (
+	"log/slog"
+)
+
+import (
+	"github.com/nicksnyder/go-i18n/v2/i18n"
+	"github.com/pelletier/go-toml/v2"
+	"golang.org/x/text/language"
+)
+
+var bundle *i18n.Bundle
+
+func SetupTrans() {
+	slog.Info("setup trans")
+	bundle = i18n.NewBundle(language.English)
+	bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
+	for _, filename := range MetadataConfig.Locales {
+		slog.Info("loading locale", "filename", filename)
+		_, err := bundle.LoadMessageFile(filename)
+		if err != nil {
+			panic(err)
+		}
+	}
+	slog.Info("setup trans ok")
+}
+
+func GetTrans(lang string) *i18n.Localizer {
+	return i18n.NewLocalizer(bundle, lang)
+}

+ 84 - 0
common/utils.go

@@ -0,0 +1,84 @@
+package common
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/md5"
+	"encoding/hex"
+	"time"
+)
+
+var CstSh, _ = time.LoadLocation("Asia/Shanghai")
+
+const (
+	LocateDateFormat  = "2006-01-02"
+	LocateTimeFormat  = "2006-01-02 15:04:05"
+	LocateMilliFormat = "2006-01-02 15:04:05.9999"
+)
+
+func Date() string {
+	return time.Now().In(CstSh).Format(LocateDateFormat)
+}
+
+func Now() string {
+	return time.Now().In(CstSh).Format(LocateTimeFormat)
+}
+
+func NowMilli() string {
+	return time.Now().In(CstSh).Format(LocateMilliFormat)
+}
+
+func MD5(v []byte) string {
+	h := md5.New()
+	h.Write(v)
+	re := h.Sum(nil)
+	return hex.EncodeToString(re)
+}
+
+var dbPwKey = []byte("X3O6wVF&6*&lSVk0*504V~q7>\"k]6S'*") // 32 bytes for AES-256
+var dbPwNonceHex = "1962a6f6f9999447632c8a34"
+
+func EncryptGCM(key []byte, nonce []byte, plaintext []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	gcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return nil, err
+	}
+
+	ciphertext := gcm.Seal(nil, nonce, plaintext, nonce)
+
+	return ciphertext, nil
+}
+
+func DecryptGCM(key []byte, nonce []byte, ciphertext []byte) ([]byte, error) {
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	gcm, err := cipher.NewGCM(block)
+	if err != nil {
+		return nil, err
+	}
+
+	plaintext, err := gcm.Open(nil, nonce, ciphertext, nonce)
+	if err != nil {
+		return nil, err
+	}
+
+	return plaintext, nil
+}
+
+func DBPwdEncrypt(ciphertext []byte) ([]byte, error) {
+	nonce, _ := hex.DecodeString(dbPwNonceHex)
+	return EncryptGCM(dbPwKey, nonce, ciphertext)
+}
+
+func DBPwdDecrypt(ciphertext []byte) ([]byte, error) {
+	nonce, _ := hex.DecodeString(dbPwNonceHex)
+	return DecryptGCM(dbPwKey, nonce, ciphertext)
+}

+ 43 - 0
config/config.toml.template

@@ -0,0 +1,43 @@
+[basic]
+jwt = "BOLQ3HoltjaQqAgWXAG6UXnq2OWGefzqYGwyiYJjAVmuDNyJAOZaFqK8cgQsUrhDA5WDVFuk0mqDRHAK"
+mode = "debug" # 模式 debug | test | release
+
+[server]
+auth = "0.0.0.0:6001"
+resource = "localhost:6102"
+study = "localhost:6103"
+protocol = "localhost:6104"
+
+[logger]
+logDir = "./logs/" # 日志存储目录
+logLevel = "debug" # 日志等级:debug; info; warn; error; fatal;
+maxSize = 10 # 在进行切割之前,日志文件的最大大小(以MB为单位)
+maxBackups = 100 # 最多保留文件个数
+maxAge = 90 # 保留旧文件的最大天数
+compress = false # 是否压缩/归档旧文件
+stdout = true
+
+[robot]
+info = ""
+dev = ""
+prod = ""
+
+# 数据库配置
+[postgres]
+ip = "127.0.0.1"
+port = 5432
+username = "mytest"
+password = "24a4e49b95adb006fbadd7c5a74dbb2ee4d67d61abab"
+name = "mytestdatabase"
+
+[metadata]
+locales = [
+    "../locale/common.en.toml",
+    "../locale/common.zh.toml"
+]
+# 支持的语言,可选项:"en","zh"
+languages = ["en", "zh"]
+# 支持的产品,可选项:"droc","vetdroc"
+product = "vetdroc"
+# 支持的访问源,可选项:"gui","browser","android"
+sources = ["gui", "browser", "android"]

+ 41 - 0
go.mod

@@ -0,0 +1,41 @@
+module protocol-server
+
+go 1.24.3
+
+require (
+	github.com/nicksnyder/go-i18n/v2 v2.6.0
+	github.com/pelletier/go-toml/v2 v2.2.4
+	github.com/spf13/cast v1.8.0
+	github.com/spf13/cobra v1.9.1
+	github.com/spf13/viper v1.20.1
+	golang.org/x/text v0.25.0
+	google.golang.org/grpc v1.72.2
+	google.golang.org/protobuf v1.36.6
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1
+	gorm.io/driver/postgres v1.5.11
+	gorm.io/gorm v1.30.0
+)
+
+require (
+	github.com/fsnotify/fsnotify v1.9.0 // indirect
+	github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+	github.com/jackc/pgx/v5 v5.7.5 // indirect
+	github.com/jackc/puddle/v2 v2.2.2 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/sagikazarmark/locafero v0.9.0 // indirect
+	github.com/sourcegraph/conc v0.3.0 // indirect
+	github.com/spf13/afero v1.14.0 // indirect
+	github.com/spf13/pflag v1.0.6 // indirect
+	github.com/subosito/gotenv v1.6.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	golang.org/x/crypto v0.38.0 // indirect
+	golang.org/x/net v0.40.0 // indirect
+	golang.org/x/sync v0.14.0 // indirect
+	golang.org/x/sys v0.33.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)

+ 112 - 0
go.sum

@@ -0,0 +1,112 @@
+github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
+github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+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-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
+github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+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-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
+github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
+github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
+github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
+github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
+github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
+github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
+github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
+github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
+go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
+go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
+go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
+go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
+go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
+go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
+go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
+go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
+go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
+golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
+google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
+google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
+gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=

+ 103 - 0
logger/gorm.go

@@ -0,0 +1,103 @@
+package logger
+
+import (
+	"context"
+	"errors"
+	"log/slog"
+	"regexp"
+	"strings"
+	"time"
+)
+
+import (
+	"gorm.io/gorm"
+	gormlogger "gorm.io/gorm/logger"
+)
+
+var (
+	pbkdf2Re *regexp.Regexp
+)
+
+func init() {
+	pbkdf2Re, _ = regexp.Compile(`'pbkdf2:\S+?'`)
+}
+
+func desensitize(str string) string {
+	fs := pbkdf2Re.FindAllString(str, -1)
+	if len(fs) > 0 {
+		for _, f := range fs {
+			str = strings.Replace(str, f, "'******'", -1)
+		}
+	}
+	return str
+}
+
+type GormLogger struct {
+	Logger                    *slog.Logger
+	LogLevel                  gormlogger.LogLevel
+	SlowThreshold             time.Duration
+	SkipCallerLookup          bool
+	IgnoreRecordNotFoundError bool
+}
+
+func NewGormLogger(logger2 *slog.Logger) *GormLogger {
+	return &GormLogger{
+		Logger:                    logger2,
+		LogLevel:                  gormlogger.Info,
+		SlowThreshold:             100 * time.Millisecond,
+		SkipCallerLookup:          false,
+		IgnoreRecordNotFoundError: true,
+	}
+}
+
+func (l GormLogger) SetAsDefault() {
+	gormlogger.Default = l
+}
+
+func (l GormLogger) LogMode(level gormlogger.LogLevel) gormlogger.Interface {
+	return GormLogger{
+		SlowThreshold:             l.SlowThreshold,
+		LogLevel:                  level,
+		SkipCallerLookup:          l.SkipCallerLookup,
+		IgnoreRecordNotFoundError: l.IgnoreRecordNotFoundError,
+	}
+}
+
+func (l GormLogger) Info(ctx context.Context, str string, args ...interface{}) {
+	if l.LogLevel < gormlogger.Info {
+		return
+	}
+	logger.Info(str, slog.Any("data", args))
+}
+
+func (l GormLogger) Warn(ctx context.Context, str string, args ...interface{}) {
+	if l.LogLevel < gormlogger.Warn {
+		return
+	}
+	logger.Warn(str, slog.Any("data", args))
+}
+
+func (l GormLogger) Error(ctx context.Context, str string, args ...interface{}) {
+	if l.LogLevel < gormlogger.Error {
+		return
+	}
+	logger.Error(str, slog.Any("data", args))
+}
+
+func (l GormLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
+	if l.LogLevel <= 0 {
+		return
+	}
+	elapsed := time.Since(begin)
+	switch {
+	case err != nil && l.LogLevel >= gormlogger.Error && (!l.IgnoreRecordNotFoundError || !errors.Is(err, gorm.ErrRecordNotFound)):
+		sql, rows := fc()
+		logger.Error("gorm trace error", "err", err, "elapsed", elapsed, "rows", rows, "sql", desensitize(sql))
+	case l.SlowThreshold != 0 && elapsed > l.SlowThreshold && l.LogLevel >= gormlogger.Warn:
+		sql, rows := fc()
+		logger.Warn("gorm trace warn", "elapsed", elapsed, "rows", rows, "sql", desensitize(sql))
+	case l.LogLevel >= gormlogger.Info:
+		sql, rows := fc()
+		logger.Info("gorm trace info", "elapsed", elapsed, "rows", rows, "sql", desensitize(sql))
+	}
+}

+ 86 - 0
logger/logger.go

@@ -0,0 +1,86 @@
+package logger
+
+import (
+	"io"
+	"log/slog"
+	"os"
+	"strings"
+)
+
+import (
+	"gopkg.in/natefinch/lumberjack.v2"
+)
+
+import (
+	"protocol-server/common"
+)
+
+var lumberJackLogger *lumberjack.Logger
+var logger *slog.Logger
+
+func SetupLogger(persisted bool) {
+	//设置日志级别
+	var level = new(slog.LevelVar)
+	switch common.LoggerConfig.LogLevel {
+	case "DEBUG":
+		level.Set(slog.LevelDebug)
+	case "INFO":
+		level.Set(slog.LevelInfo)
+	case "WARN":
+		level.Set(slog.LevelWarn)
+	case "ERROR":
+		level.Set(slog.LevelError)
+	default:
+		level.Set(slog.LevelInfo)
+	}
+	opts := &slog.HandlerOptions{
+		Level:     level,
+		AddSource: true,
+	}
+
+	filename := common.LoggerConfig.LogDir + "/protocol.log"
+	if strings.HasSuffix(common.LoggerConfig.LogDir, "/") {
+		filename = common.LoggerConfig.LogDir + "protocol.log"
+	}
+	lumberJackLogger = &lumberjack.Logger{
+		Filename:   filename,                       //日志文件的位置
+		MaxSize:    common.LoggerConfig.MaxSize,    //在进行切割之前,日志文件的最大大小(以MB为单位)
+		MaxBackups: common.LoggerConfig.MaxBackups, //最多保留文件个数
+		MaxAge:     common.LoggerConfig.MaxAge,     //保留旧文件的最大天数
+		Compress:   common.LoggerConfig.Compress,   //是否压缩/归档旧文件
+		LocalTime:  true,
+	}
+
+	if persisted {
+		if common.LoggerConfig.Stdout {
+			logger = slog.New(slog.NewTextHandler(io.MultiWriter(lumberJackLogger, os.Stdout), opts))
+		} else {
+			logger = slog.New(slog.NewTextHandler(lumberJackLogger, opts))
+		}
+	} else {
+		logger = slog.New(slog.NewTextHandler(os.Stdout, opts))
+	}
+	slog.SetDefault(logger)
+	//todo 输出到robot
+
+	slog.Info("setup logger ok",
+		"level", level.String(),
+		"filename", filename,
+		"max size", common.LoggerConfig.MaxSize,
+		"max backups", common.LoggerConfig.MaxBackups,
+		"max age", common.LoggerConfig.MaxAge,
+		"compress", common.LoggerConfig.Compress,
+	)
+}
+
+func ShutdownLogger() {
+	lumberJackLogger.Rotate()
+}
+
+func WithGroup(name string) *slog.Logger {
+	return logger.WithGroup(name)
+}
+
+func With(args ...any) *slog.Logger {
+	return logger.With(args)
+}

+ 9 - 0
main.go

@@ -0,0 +1,9 @@
+package main
+
+import (
+	"protocol-server/cmd"
+)
+
+func main() {
+	cmd.Execute()
+}

+ 14 - 0
models/model.go

@@ -0,0 +1,14 @@
+package models
+
+import (
+	"gorm.io/gorm"
+)
+
+type Procedure struct {
+	gorm.Model
+	ProcedureID string
+}
+
+func (Procedure) TableName() string {
+	return "p_procedure"
+}

+ 34 - 0
models/scope.go

@@ -0,0 +1,34 @@
+package models
+
+import (
+	"gorm.io/gorm"
+)
+
+func Paginate(pageNum, pageSize int) func(db *gorm.DB) *gorm.DB {
+	return func(db *gorm.DB) *gorm.DB {
+		offset := (pageNum - 1) * pageSize
+		return db.Offset(offset).Limit(pageSize)
+	}
+}
+
+func Query(query string, args ...interface{}) func(db *gorm.DB) *gorm.DB {
+	implement := false
+	for _, arg := range args {
+		switch arg.(type) {
+		case string:
+			if len(arg.(string)) > 0 {
+				implement = true
+			}
+		default:
+			implement = true
+		}
+	}
+	if implement {
+		return func(db *gorm.DB) *gorm.DB {
+			return db.Where(query, args...)
+		}
+	}
+	return func(db *gorm.DB) *gorm.DB {
+		return db
+	}
+}

+ 57 - 0
models/setup.go

@@ -0,0 +1,57 @@
+package models
+
+import (
+	"fmt"
+	"log/slog"
+)
+
+import (
+	"gorm.io/driver/postgres"
+	"gorm.io/gorm"
+)
+
+import (
+	"protocol-server/common"
+	"protocol-server/logger"
+)
+
+var (
+	DB  *gorm.DB
+	err error
+)
+
+func panicHelper(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
+
+func migrate() {
+	panicHelper(DB.AutoMigrate(&Procedure{}))
+}
+
+func SetupGorm(m bool) {
+	slog.Info("setup gorm")
+
+	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
+		common.PostgresConfig.Ip,
+		common.PostgresConfig.Username,
+		common.PostgresConfig.Password,
+		common.PostgresConfig.Name,
+		common.PostgresConfig.Port,
+	)
+	if common.LoggerConfig.LogLevel == "debug" {
+		DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
+			Logger: logger.NewGormLogger(logger.WithGroup("gorm")),
+		})
+	} else {
+		DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
+	}
+	if err != nil {
+		panic(err)
+	}
+	if m {
+		migrate()
+	}
+	slog.Info("setup gorm ok")
+}

+ 1 - 0
rpc_idl

@@ -0,0 +1 @@
+Subproject commit 41e78dbf6c013fa7c6d92119c4ced7b1fd6ca535

+ 25 - 0
service/basic.go

@@ -0,0 +1,25 @@
+package service
+
+import (
+	"context"
+	"log/slog"
+)
+
+import (
+	"protocol-server/common"
+	pb "protocol-server/rpc_idl/dr_protocol_pb"
+)
+
+type BasicServer struct {
+	pb.UnimplementedBasicServer
+}
+
+func (s *BasicServer) SoftwareInfo(_ context.Context, in *pb.EmptyRequest) (*pb.SoftwareInfoReply, error) {
+	slog.Info("Received SoftwareInfo")
+	return &pb.SoftwareInfoReply{
+		Module:  common.Module,
+		Desc:    common.Desc,
+		Build:   common.Build,
+		Version: common.Version,
+	}, nil
+}

+ 12 - 0
static/locale/common.en.toml

@@ -0,0 +1,12 @@
+ErrCode_Success = "Success"
+ErrCode_InvalidTokenError = "Invalid Token"
+ErrCode_InvalidRoleError = "Invalid Role"
+ErrCode_InvalidParamError = "Invalid Param"
+ErrCode_NotExistsError = "Not Exists"
+ErrCode_NotSupportError = "Not Support"
+ErrCode_NoAuthError = "No Auth"
+ErrCode_ExistsError = "Already Exists"
+ErrCode_NotAvailableError = "Not Available"
+ErrCode_UnknownError = "Service exception, please contact the administrator"
+
+ErrCode_InvalidUsernameOrPasswdError = "Invalid username or password"

+ 12 - 0
static/locale/common.zh.toml

@@ -0,0 +1,12 @@
+ErrCode_Success = "成功"
+ErrCode_InvalidTokenError = "无效的凭证"
+ErrCode_InvalidRoleError = "无效的角色"
+ErrCode_InvalidParamError = "无效的参数"
+ErrCode_NotExistsError = "不存在"
+ErrCode_NotSupportError = "不支持"
+ErrCode_NoAuthError = "无权限"
+ErrCode_ExistsError = "已存在"
+ErrCode_NotAvailableError = "不可用"
+ErrCode_UnknownError = "服务异常,请联系管理员"
+
+ErrCode_InvalidUsernameOrPasswdError = "无效的用户名或密码"