init
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
9
.idea/DT.iml
generated
Normal file
9
.idea/DT.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/DT.iml" filepath="$PROJECT_DIR$/.idea/DT.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
39
config.yaml
Normal file
39
config.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
is_debug: true
|
||||||
|
|
||||||
|
|
||||||
|
service:
|
||||||
|
http:
|
||||||
|
host: 0.0.0.0 # 默认localhost
|
||||||
|
port: 8001 # 默认9999
|
||||||
|
tcp:
|
||||||
|
host: 0.0.0.0 # 默认localhost
|
||||||
|
port: 8002 # 默认9999
|
||||||
|
|
||||||
|
db:
|
||||||
|
db_host: "127.0.0.1"
|
||||||
|
db_port: "3306"
|
||||||
|
db_name: "dianti"
|
||||||
|
db_user: "root"
|
||||||
|
db_pass: "root"
|
||||||
|
table_prefix: "dt_"
|
||||||
|
time_zone: "Local"
|
||||||
|
log_level: 4
|
||||||
|
slow_threshold: 200
|
||||||
|
idle_conns: 10
|
||||||
|
open_conns: 50
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#jwt:
|
||||||
|
# signing_key: iuu
|
||||||
|
# expires_time: 7d
|
||||||
|
# buffer_time: 1d
|
||||||
|
# issuer: iuu.me
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#influxdb:
|
||||||
|
# # host: "http://192.168.0.47:18086"
|
||||||
|
# # token: "BfG_zW41Wddgbuig0GXt6TuDWpHjUgjTJGDFi9ZI6fOXeYwyWKhakTrwbRT8f4uqFQCWXbRGxs8f5GaChW5tqw=="
|
||||||
|
# host: "http://192.168.0.9:8086"
|
||||||
|
# token: "4Sz5qRK-VY0aGh7m7sYDkG5tWCwKjbDYAlkgFpvUcZTBn7XahGqczeye7BQCRjcWP8fqsefPeNkwfvUnqa69oA=="
|
||||||
12
config/config.go
Normal file
12
config/config.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
const (
|
||||||
|
AppEnv = "dev"
|
||||||
|
EnvConfig = "config.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
IsDebug bool `mapstructure:"is_debug" json:"is_debug" yaml:"is_debug"`
|
||||||
|
Db DbConf `mapstructure:"db" json:"db" yaml:"db"`
|
||||||
|
Service ServiceConf `mapstructure:"service" json:"service" yaml:"service"`
|
||||||
|
}
|
||||||
15
config/db.go
Normal file
15
config/db.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type DbConf struct {
|
||||||
|
DbHost string `mapstructure:"db_host" json:"db_host" yaml:"db_host"`
|
||||||
|
DbPort int `mapstructure:"db_port" json:"db_port" yaml:"db_port"`
|
||||||
|
DbName string `mapstructure:"db_name" json:"db_name" yaml:"db_name"`
|
||||||
|
DbUser string `mapstructure:"db_user" json:"db_user" yaml:"db_user"`
|
||||||
|
DbPass string `mapstructure:"db_pass" json:"db_pass" yaml:"db_pass"`
|
||||||
|
TablePrefix string `mapstructure:"table_prefix" json:"table_prefix" yaml:"table_prefix"`
|
||||||
|
TimeZone string `mapstructure:"time_zone" json:"time_zone" yaml:"time_zone"`
|
||||||
|
LogLevel int `mapstructure:"log_level" json:"log_level" yaml:"log_level"` // LogLevel SQL日志级别 (1-静音 2-错误 3-警告 4-信息)
|
||||||
|
SlowThreshold int `mapstructure:"slow_threshold" json:"slow_threshold" yaml:"slow_threshold"` // SlowThreshold 慢SQL阈值(毫秒)。慢SQL会在log_level大于等于3时输出。
|
||||||
|
IdleConns int `mapstructure:"idle_conns" json:"idle_conns" yaml:"idle_conns"` // 空闲连接池中的最大连接数,建议为open_conns的百分之5-20之间
|
||||||
|
OpenConns int `mapstructure:"open_conns" json:"open_conns" yaml:"open_conns"` // 最大打开连接数,建议这里设置为50
|
||||||
|
}
|
||||||
15
config/service.go
Normal file
15
config/service.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type ServiceConf struct {
|
||||||
|
Http HttpConf `mapstructure:"http" json:"http" yaml:"http"`
|
||||||
|
Tcp TcpConf `mapstructure:"tcp" json:"tcp" yaml:"tcp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpConf struct {
|
||||||
|
Host string `mapstructure:"host" json:"host" yaml:"host"`
|
||||||
|
Port string `mapstructure:"port" json:"port" yaml:"port"`
|
||||||
|
}
|
||||||
|
type TcpConf struct {
|
||||||
|
Host string `mapstructure:"host" json:"host" yaml:"host"`
|
||||||
|
Port string `mapstructure:"port" json:"port" yaml:"port"`
|
||||||
|
}
|
||||||
61
core/gorm/gorm.go
Normal file
61
core/gorm/gorm.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package gorm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/global"
|
||||||
|
"DT/model"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"gorm.io/gorm/schema"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitGorm() {
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=%s", global.AppConf.Db.DbUser, global.AppConf.Db.DbPass, global.AppConf.Db.DbHost, global.AppConf.Db.DbPort, global.AppConf.Db.DbName, global.AppConf.Db.TimeZone)
|
||||||
|
postgresConfig := mysql.Config{
|
||||||
|
DSN: dsn,
|
||||||
|
}
|
||||||
|
|
||||||
|
newLogger := logger.New(
|
||||||
|
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||||
|
logger.Config{
|
||||||
|
SlowThreshold: time.Duration(global.AppConf.Db.SlowThreshold) * time.Millisecond,
|
||||||
|
LogLevel: logger.LogLevel(global.AppConf.Db.LogLevel),
|
||||||
|
IgnoreRecordNotFoundError: true,
|
||||||
|
Colorful: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
MYDB, err := gorm.Open(mysql.New(postgresConfig), &gorm.Config{
|
||||||
|
Logger: newLogger,
|
||||||
|
NamingStrategy: schema.NamingStrategy{
|
||||||
|
SingularTable: true,
|
||||||
|
TablePrefix: global.AppConf.Db.TablePrefix,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
DB := MYDB
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[-] 数据库连接失败")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
sqlDB, _ := DB.DB()
|
||||||
|
sqlDB.SetMaxIdleConns(global.AppConf.Db.IdleConns)
|
||||||
|
sqlDB.SetMaxOpenConns(global.AppConf.Db.OpenConns)
|
||||||
|
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||||
|
fmt.Println("[+]连接成功!")
|
||||||
|
global.Db = DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func AutoMigrate(db *gorm.DB) {
|
||||||
|
err := db.AutoMigrate(
|
||||||
|
new(model.Device),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[-] 迁移数据表失败:", err.Error())
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
91
core/logger/logger.go
Normal file
91
core/logger/logger.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/global"
|
||||||
|
"fmt"
|
||||||
|
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitLogger() *global.Logger {
|
||||||
|
L := logrus.New()
|
||||||
|
path := "./log/log"
|
||||||
|
file, _ := rotatelogs.New(
|
||||||
|
path+".%Y%m%d",
|
||||||
|
rotatelogs.WithLinkName(path),
|
||||||
|
rotatelogs.WithMaxAge(time.Duration(100*24)*time.Hour), //自动删除
|
||||||
|
rotatelogs.WithRotationTime(time.Duration(24*60)*time.Minute), //分割时间
|
||||||
|
)
|
||||||
|
L.SetOutput(file)
|
||||||
|
L.SetFormatter(&logrus.JSONFormatter{})
|
||||||
|
Log := &global.Logger{Logger: L, Heads: []string{}}
|
||||||
|
global.Log = Log
|
||||||
|
return Log
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs = &errFunc{lock: new(sync.Mutex)}
|
||||||
|
|
||||||
|
func ErrorRegister(f func(string)) {
|
||||||
|
errs.register(f, false)
|
||||||
|
}
|
||||||
|
func ErrorRegisterOnly1(f func(string)) {
|
||||||
|
errs.register(f, true)
|
||||||
|
}
|
||||||
|
func ErrorWx(e ...string) {
|
||||||
|
errs.run(strings.Join(e, " "))
|
||||||
|
global.Log.Error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
type errFunc struct {
|
||||||
|
f []func(string)
|
||||||
|
lastTime int64
|
||||||
|
lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errFunc) register(f func(string), only bool) {
|
||||||
|
if only {
|
||||||
|
e.f = []func(string){f}
|
||||||
|
} else {
|
||||||
|
e.f = append(e.f, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errFunc) run(logs string) {
|
||||||
|
if len(e.f) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if time.Now().Unix()-e.lastTime > 1 {
|
||||||
|
e.lock.Lock()
|
||||||
|
e.lastTime = time.Now().Unix()
|
||||||
|
defer e.lock.Unlock()
|
||||||
|
for _, v := range e.f {
|
||||||
|
v(logs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StackSend(skip int, e string) {
|
||||||
|
e += "\n" + Stack(skip)
|
||||||
|
if global.AppConf.IsDebug {
|
||||||
|
fmt.Print(e)
|
||||||
|
}
|
||||||
|
errs.run(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stack(skip int) (re string) {
|
||||||
|
for i := skip; ; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !strings.Contains(file, "local/go/src") && !strings.Contains(file, "/go/pkg") {
|
||||||
|
logs := fmt.Sprintf("%s:%d\n", file, line)
|
||||||
|
re += logs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
59
core/viper/viper.go
Normal file
59
core/viper/viper.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/config"
|
||||||
|
"DT/global"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitViper 解析yaml格式文件
|
||||||
|
func InitViper(path ...string) {
|
||||||
|
var confPath string
|
||||||
|
if len(path) == 0 {
|
||||||
|
flag.StringVar(&confPath, "c", "", "choose config file.")
|
||||||
|
flag.Parse()
|
||||||
|
if confPath == "" {
|
||||||
|
if AppEnv := os.Getenv(config.AppEnv); AppEnv == "" {
|
||||||
|
confPath = config.EnvConfig
|
||||||
|
} else {
|
||||||
|
confPath = AppEnv
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
confPath = path[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
v := viper.New()
|
||||||
|
// 指定配置文件路径
|
||||||
|
v.SetConfigFile(confPath)
|
||||||
|
// 如果配置文件的名称中没有扩展名,则需要配置此项
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
// 查找并读取配置文件
|
||||||
|
err := v.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[-]读取配置文件错误: %s \n", err)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
// 监控并重新读取配置文件
|
||||||
|
v.WatchConfig()
|
||||||
|
v.OnConfigChange(func(e fsnotify.Event) {
|
||||||
|
if err = v.Unmarshal(&global.AppConf); err != nil {
|
||||||
|
fmt.Printf("[-]重新解析配置文件失败: %s \n", err)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
fmt.Println("[+]重新加载配置文件完成")
|
||||||
|
})
|
||||||
|
if err = v.Unmarshal(&global.AppConf); err != nil {
|
||||||
|
fmt.Printf("[-]解析配置文件失败: %s \n", err)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
fmt.Println("[+]加载配置文件完成")
|
||||||
|
|
||||||
|
//dump.P(global.AppConf)
|
||||||
|
|
||||||
|
}
|
||||||
14
global/logger.go
Normal file
14
global/logger.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
*logrus.Logger
|
||||||
|
Heads []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) AddHead(args ...string) {
|
||||||
|
for _, v := range args {
|
||||||
|
l.Heads = append(l.Heads, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
19
global/var.go
Normal file
19
global/var.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/config"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AppConf 配置信息
|
||||||
|
AppConf *config.Config
|
||||||
|
// Db 数据库
|
||||||
|
Db *gorm.DB
|
||||||
|
|
||||||
|
//InFluxDb influxdb2.Client
|
||||||
|
|
||||||
|
Log *Logger
|
||||||
|
|
||||||
|
//Cron *cron.Cron
|
||||||
|
)
|
||||||
66
go.mod
Normal file
66
go.mod
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
module DT
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.8.0
|
||||||
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/gookit/goutil v0.6.18
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
gorm.io/driver/mysql v1.5.7
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gookit/color v1.5.4 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
|
golang.org/x/text v0.21.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
162
go.sum
Normal file
162
go.sum
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/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.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||||
|
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||||
|
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||||
|
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
|
||||||
|
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
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/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||||
|
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
|
||||||
|
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
|
||||||
|
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0 h1:gMESpZy44/4pXLO/m+sL0yBd1W6LjgjrrD4a68Gapyg=
|
||||||
|
github.com/lestrrat-go/strftime v1.1.0/go.mod h1:uzeIB52CeUJenCo1syghlugshMysrqUT51HlxphXVeI=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
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.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.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=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
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/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
194
httpserver/server.go
Normal file
194
httpserver/server.go
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package httpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/tcpserver"
|
||||||
|
"DT/ws"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server HTTP服务器结构
|
||||||
|
type Server struct {
|
||||||
|
tcpServer *tcpserver.Server
|
||||||
|
httpServer *http.Server
|
||||||
|
router *gin.Engine
|
||||||
|
hub *ws.Hub
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer 创建一个新的HTTP服务器
|
||||||
|
func NewServer(addr string, tcpServer *tcpserver.Server) *Server {
|
||||||
|
router := gin.Default()
|
||||||
|
hub := ws.NewHub()
|
||||||
|
|
||||||
|
server := &Server{
|
||||||
|
tcpServer: tcpServer,
|
||||||
|
router: router,
|
||||||
|
httpServer: &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: router,
|
||||||
|
},
|
||||||
|
hub: hub,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动 hub
|
||||||
|
go hub.Run()
|
||||||
|
|
||||||
|
// 注册路由
|
||||||
|
server.registerRoutes()
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加请求结构体
|
||||||
|
type SendCommandRequest struct {
|
||||||
|
Imei string `json:"Imei" binding:"required"`
|
||||||
|
Command string `json:"command" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerRoutes 方法
|
||||||
|
func (s *Server) registerRoutes() {
|
||||||
|
// 获取在线客户端列表
|
||||||
|
s.router.GET("/clients", s.handleGetClients)
|
||||||
|
|
||||||
|
// 添加发送命令的路由
|
||||||
|
s.router.POST("/clients_send", s.handleSendCommand)
|
||||||
|
|
||||||
|
// WebSocket 路由 - 使用查询参数获取 IMEI
|
||||||
|
s.router.GET("/ws/:imei", s.handleWebSocket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理发送命令的方法
|
||||||
|
func (s *Server) handleSendCommand(c *gin.Context) {
|
||||||
|
var req SendCommandRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"code": 400,
|
||||||
|
"msg": "invalid request: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找目标客户端
|
||||||
|
clients := s.tcpServer.GetOnlineClients()
|
||||||
|
var targetClient *tcpserver.Client
|
||||||
|
found := false
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
if client["imei"] == req.Imei {
|
||||||
|
if clientObj, ok := s.tcpServer.GetClient(client["id"].(string)); ok {
|
||||||
|
targetClient = clientObj
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"code": 404,
|
||||||
|
"msg": "client not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送命令
|
||||||
|
_, err := targetClient.Conn.Write(append([]byte(req.Command), '\r', '\n'))
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"code": 500,
|
||||||
|
"msg": "failed to send command: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"imei": req.Imei,
|
||||||
|
"command": req.Command,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetClients 处理获取客户端列表的请求
|
||||||
|
func (s *Server) handleGetClients(c *gin.Context) {
|
||||||
|
clients := s.tcpServer.GetOnlineClients()
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "success",
|
||||||
|
"data": gin.H{
|
||||||
|
"total": len(clients),
|
||||||
|
"clients": clients,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 WebSocket 处理函数
|
||||||
|
func (s *Server) handleWebSocket(c *gin.Context) {
|
||||||
|
imei := c.Param("imei")
|
||||||
|
//imei := c.Query("imei")
|
||||||
|
if imei == "" {
|
||||||
|
log.Printf("IMEI 参数为空")
|
||||||
|
c.String(http.StatusBadRequest, "IMEI parameter is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//devId, err := strconv.Atoi(deviceId)
|
||||||
|
//if err != nil {
|
||||||
|
// log.Printf("设备ID转换失败: %v", err)
|
||||||
|
// c.String(http.StatusBadRequest, "Invalid device ID")
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
log.Printf("WebSocket 连接请求: imei=%s", imei)
|
||||||
|
|
||||||
|
upgrader := websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("WebSocket 升级失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &ws.Client{
|
||||||
|
Hub: s.hub,
|
||||||
|
Conn: conn,
|
||||||
|
Send: make(chan *ws.WsMessage, 256),
|
||||||
|
Imei: imei,
|
||||||
|
//DeviceId: devId,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("WebSocket 客户端创建: imei=%s", imei)
|
||||||
|
client.Hub.Register <- client
|
||||||
|
|
||||||
|
go client.WritePump()
|
||||||
|
go client.ReadPump()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动HTTP服务器
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
return s.httpServer.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 优雅关闭HTTP服务器
|
||||||
|
func (s *Server) Stop(ctx context.Context) error {
|
||||||
|
fmt.Println("Stopping HTTP server...")
|
||||||
|
if err := s.httpServer.Shutdown(ctx); err != nil {
|
||||||
|
return fmt.Errorf("HTTP server forced to shutdown: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println("HTTP server stopped gracefully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHub 添加获取 Hub 的方法
|
||||||
|
func (s *Server) GetHub() *ws.Hub {
|
||||||
|
return s.hub
|
||||||
|
}
|
||||||
86
main.go
Normal file
86
main.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/core/gorm"
|
||||||
|
"DT/core/logger"
|
||||||
|
"DT/core/viper"
|
||||||
|
"DT/global"
|
||||||
|
"DT/httpserver"
|
||||||
|
"DT/tcpserver"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
viper.InitViper()
|
||||||
|
logger.InitLogger()
|
||||||
|
gorm.InitGorm()
|
||||||
|
//gorm.AutoMigrate(global.Db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
handler := &tcpserver.TCPHandler{}
|
||||||
|
|
||||||
|
tcpHost := global.AppConf.Service.Tcp.Host
|
||||||
|
tcpPort := global.AppConf.Service.Tcp.Port
|
||||||
|
tcpServer := tcpserver.NewServer(tcpHost+":"+tcpPort, handler.HandleClient)
|
||||||
|
handler.Server = tcpServer
|
||||||
|
|
||||||
|
httpHost := global.AppConf.Service.Http.Host
|
||||||
|
httpPort := global.AppConf.Service.Http.Port
|
||||||
|
httpServer := httpserver.NewServer(httpHost+":"+httpPort, tcpServer)
|
||||||
|
|
||||||
|
handler.Hub = httpServer.GetHub()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := tcpServer.Start(); err != nil {
|
||||||
|
fmt.Printf("TCP server error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
fmt.Printf("Listening and serving HTTP on %s:%s\r\n", httpHost, httpPort)
|
||||||
|
if err := httpServer.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
fmt.Printf("HTTP server error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
|
||||||
|
fmt.Println("Shutting down servers...")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := httpServer.Stop(ctx); err != nil {
|
||||||
|
fmt.Printf("Error stopping HTTP server: %v\n", err)
|
||||||
|
}
|
||||||
|
tcpServer.Stop()
|
||||||
|
fmt.Println("All servers stopped")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端发送 {"Type":"reg","Imei":"8610123456","Pwd":"123456","Ver":"v1.0.0"}
|
||||||
|
// 服务器回复 {"Type":"reg","Time":1734948442}
|
||||||
|
|
||||||
|
// 心跳
|
||||||
|
// 客户端{"Type":"ping"}
|
||||||
|
// 服务端{"Type":"pong"}
|
||||||
|
|
||||||
|
// 服务端发 {"Type":"ota","Ip":"192.168.31.1:80","File":"/xxx/1.bin"}
|
||||||
|
// 服务端发 {\"Type\":\"ota\",\"Ip\":\"192.168.31.1:80\",\"File\":\"/xxx/1.bin\"}
|
||||||
|
// 客户端回复 {"Type":"ota","State":"1"}
|
||||||
|
|
||||||
|
// 开始实时上传数据
|
||||||
|
// 服务端发 {"Type":"start"}
|
||||||
|
// 客户端回复 {"Type":"start","Data":"{\"Type\":\"ota\",\"Ip\":\"192.168.31.1:80\",\"File\":\"/xxx/1.bin\"}"}
|
||||||
|
// 客户端回复 {"Type":"start","Data":"{\"Type\":\"ota\",\"Ip\":\"192.168.31.1:80\",\"File\":\"/xxx/1.bin\"}"}
|
||||||
|
|
||||||
|
// 停止实时上传数据
|
||||||
|
// 服务端发 {"Type":"stop"}
|
||||||
|
// 客户端回复 {"Type":"stop","State":"1"}
|
||||||
25
model/device.go
Normal file
25
model/device.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/global"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
Id int `gorm:"column:id;primaryKey" json:"id"`
|
||||||
|
//DriverId string `gorm:"column:driver_id;comment:设备ID;type:varchar(255)" json:"driver_id"`
|
||||||
|
Imei string `gorm:"column:imei;comment:IMEI;type:varchar(255)" json:"imei"`
|
||||||
|
//DriverName string `gorm:"column:driver_name;comment:设备名称;type:varchar(255)" json:"driver_name"`
|
||||||
|
DriverPass string `gorm:"column:driver_pass;comment:设备密码;type:varchar(255)" json:"driver_pass"`
|
||||||
|
DriverVer string `gorm:"column:driver_ver;comment:固件版本;type:varchar(255)" json:"driver_ver"`
|
||||||
|
//DriverFd string `gorm:"column:driver_fd;comment:设备FD;type:varchar(255)" json:"driver_fd"`
|
||||||
|
Remark string `gorm:"column:remark;comment:备注;type:varchar(255)" json:"remark"`
|
||||||
|
Created time.Time `gorm:"column:created;autoCreateTime;comment:创建时间" json:"created"`
|
||||||
|
Updated time.Time `gorm:"column:updated;autoUpdateTime;comment:修改时间" json:"updated"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index;comment:删除时间" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Device) TableName() string {
|
||||||
|
return global.AppConf.Db.TablePrefix + "devices"
|
||||||
|
}
|
||||||
81
repository/device.go
Normal file
81
repository/device.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/global"
|
||||||
|
"DT/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (r *Device) GetDeviceList(qr map[string]interface{}) (p []*model.Device, err error) {
|
||||||
|
// db := global.Db
|
||||||
|
// for key, value := range qr {
|
||||||
|
// db = db.Where(key, value)
|
||||||
|
// }
|
||||||
|
// err = db.Order("id DESC").Find(&p).Error
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
//func (r *Device) GetDevicePage(req *form.DevicePageReq) (count int64, list []*device_model.Device, err error) {
|
||||||
|
// db := global.Db.Model(&device_model.Device{})
|
||||||
|
// //for key, value := range qr {
|
||||||
|
// // db = db.Where(key, value)
|
||||||
|
// //}
|
||||||
|
// //if req.DriverId > 0 {
|
||||||
|
// // db = db.Where("driver_id = ?", req.DriverId)
|
||||||
|
// //}
|
||||||
|
// err = db.Count(&count).Error
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// //err = db.Offset((req.Page.GetPageIndex() - 1) * req.Page.GetPageSize()).Limit(req.Page.GetPageSize()).Order("id desc").Preload("Rule").Find(&list).Error
|
||||||
|
// err = db.Offset((req.Page.GetPageIndex() - 1) * req.Page.GetPageSize()).Limit(req.Page.GetPageSize()).Order("id desc").Find(&list).Error
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (r *Device) CreateDevice(d *model.Device) error {
|
||||||
|
return global.Db.Create(d).Error
|
||||||
|
}
|
||||||
|
func (r *Device) UpdateDevice(d *model.Device) error {
|
||||||
|
return global.Db.Save(d).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Device) GetDevice(qr map[string]interface{}) (d *model.Device, err error) {
|
||||||
|
db := global.Db
|
||||||
|
for key, value := range qr {
|
||||||
|
db = db.Where(key, value)
|
||||||
|
}
|
||||||
|
err = db.First(&d).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//func (r *User) GetPeakValleyQuarterPage(req *form.PeakValleyQuarterListReq) (count int64, list []*peak_valley_model.PeakValleyQuarter, err error) {
|
||||||
|
// db := global.Db.Model(&peak_valley_model.PeakValleyQuarter{})
|
||||||
|
//
|
||||||
|
// //for key, value := range qr {
|
||||||
|
// // db = db.Where(key, value)
|
||||||
|
// //}
|
||||||
|
// err = db.Count(&count).Error
|
||||||
|
// if err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// err = db.Offset((req.Page.GetPageIndex() - 1) * req.Page.GetPageSize()).Limit(req.Page.GetPageSize()).Order("id desc").Preload("Rule").Find(&list).Error
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
//func (r *User) CreatePeakValleyQuarter(d *user_model.User) error {
|
||||||
|
// return global.Db.Create(d).Error
|
||||||
|
//}
|
||||||
|
//func (r *User) UpdatePeakValleyQuarter(d *user_model.User) error {
|
||||||
|
// return global.Db.Save(d).Error
|
||||||
|
//}
|
||||||
|
|
||||||
|
//func (r *User) GetUser(qr map[string]interface{}) (d *user_model.User, err error) {
|
||||||
|
// db := global.Db
|
||||||
|
// for key, value := range qr {
|
||||||
|
// db = db.Where(key, value)
|
||||||
|
// }
|
||||||
|
// err = db.First(&d).Error
|
||||||
|
// return
|
||||||
|
//}
|
||||||
7
repository/enter.go
Normal file
7
repository/enter.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
type groupRepository struct {
|
||||||
|
Device
|
||||||
|
}
|
||||||
|
|
||||||
|
var GroupRepositorys = new(groupRepository)
|
||||||
267
tcpserver/handler.go
Normal file
267
tcpserver/handler.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package tcpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"DT/repository"
|
||||||
|
"DT/ws"
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TCPHandler struct {
|
||||||
|
Server *Server
|
||||||
|
Hub *ws.Hub
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *TCPHandler) HandleClient(conn net.Conn) {
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
clientID := conn.RemoteAddr().String()
|
||||||
|
var client *Client
|
||||||
|
if value, ok := h.Server.GetClient(clientID); ok {
|
||||||
|
client = value
|
||||||
|
} else {
|
||||||
|
fmt.Println("找不到客户端:", clientID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastMessage := func(data []byte) {
|
||||||
|
if h.Hub != nil {
|
||||||
|
h.Hub.Broadcast <- &ws.WsMessage{IMEI: client.Imei, Data: string(data)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
message, err := reader.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("客户端已断开连接:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除末尾的换行符
|
||||||
|
message = bytes.TrimSpace(message)
|
||||||
|
fmt.Printf("收到消息来自 %s: %s\n", conn.RemoteAddr(), string(message))
|
||||||
|
|
||||||
|
if !json.Valid(message) {
|
||||||
|
fmt.Printf("来自客户端的数据非法 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullMsg map[string]interface{}
|
||||||
|
if err := json.Unmarshal(message, &fullMsg); err != nil {
|
||||||
|
fmt.Printf("Error parsing message: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
msgType, _ := fullMsg["Type"].(string)
|
||||||
|
if msgType == "" {
|
||||||
|
fmt.Printf("接收到的消息类型为空 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msgType {
|
||||||
|
case "reg":
|
||||||
|
// 处理登录请求
|
||||||
|
if err := h.Server.HandleAuth(client, message); err != nil {
|
||||||
|
fmt.Printf("客户端授权失败: %v\n", err)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("客户端已授权: %s\n", client.Imei)
|
||||||
|
// 广播登录消息
|
||||||
|
broadcastMessage(message)
|
||||||
|
|
||||||
|
case "ping":
|
||||||
|
// 处理心跳
|
||||||
|
if !client.IsAuth {
|
||||||
|
fmt.Printf("来自未授权客户端的心跳 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.Server.HandleHeartbeat(client, message); err != nil {
|
||||||
|
fmt.Printf("心跳错误: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 广播心跳消息
|
||||||
|
broadcastMessage(message)
|
||||||
|
|
||||||
|
case "ota":
|
||||||
|
// 处理 OTA
|
||||||
|
if !client.IsAuth {
|
||||||
|
fmt.Printf("来自未授权客户端的消息 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.Server.HandleOta(client, message); err != nil {
|
||||||
|
fmt.Printf("OTA 错误: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 广播 OTA 消息
|
||||||
|
broadcastMessage(message)
|
||||||
|
|
||||||
|
case "start":
|
||||||
|
// 处理 客户端实时上报数据
|
||||||
|
if !client.IsAuth {
|
||||||
|
fmt.Printf("来自未授权客户端的消息 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.Server.RealTimeReporting(client, message); err != nil {
|
||||||
|
fmt.Printf("OTA 错误: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 广播 OTA 消息
|
||||||
|
broadcastMessage(message)
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 处理其他消息类型
|
||||||
|
if !client.IsAuth {
|
||||||
|
fmt.Printf("来自未授权客户端的消息 %s\n", conn.RemoteAddr())
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 广播其他类型的消息
|
||||||
|
broadcastMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleHeartbeat(client *Client, message []byte) error {
|
||||||
|
if !client.IsAuth {
|
||||||
|
return fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg Message
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Type == "ping" {
|
||||||
|
client.LastPing = time.Now()
|
||||||
|
response := Message{
|
||||||
|
MessageType: MessageType{Type: "pong"},
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = client.Conn.Write(append(responseData, '\r', '\n'))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleAuth(client *Client, message []byte) error {
|
||||||
|
var msg Message
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if msg.Type != "reg" {
|
||||||
|
return fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
model, err := repository.GroupRepositorys.Device.GetDevice(map[string]interface{}{"imei": msg.Imei})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("设备不存在")
|
||||||
|
}
|
||||||
|
if msg.Pwd != model.DriverPass {
|
||||||
|
return fmt.Errorf("设备密码不正确")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新版本号
|
||||||
|
model.DriverVer = msg.Ver
|
||||||
|
err = repository.GroupRepositorys.Device.UpdateDevice(model)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("更新设备版本号失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证成功,停止登录超时定时器
|
||||||
|
if client.authTimer != nil {
|
||||||
|
client.authTimer.Stop()
|
||||||
|
client.authTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 认证成功
|
||||||
|
client.Imei = msg.Imei
|
||||||
|
client.IsAuth = true
|
||||||
|
|
||||||
|
// 发送响应
|
||||||
|
response := Message{
|
||||||
|
MessageType: MessageType{Type: "reg"},
|
||||||
|
MessageTime: MessageTime{Time: time.Now().Unix()},
|
||||||
|
}
|
||||||
|
responseData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Conn.Write(append(responseData, '\r', '\n'))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleOta(client *Client, message []byte) error {
|
||||||
|
var msg Message
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if msg.Type != "ota" {
|
||||||
|
return fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("设备升级结果:%s\r\n", msg.State)
|
||||||
|
|
||||||
|
//model, err := repository.GroupRepositorys.Device.GetDevice(map[string]interface{}{"imei": msg.Imei})
|
||||||
|
//if err != nil {
|
||||||
|
// return fmt.Errorf("设备不存在")
|
||||||
|
//}
|
||||||
|
//if msg.Pwd != model.DriverPass {
|
||||||
|
// return fmt.Errorf("设备密码不正确")
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 更新版本号
|
||||||
|
//model.DriverVer = msg.Ver
|
||||||
|
//err = repository.GroupRepositorys.Device.UpdateDevice(model)
|
||||||
|
//if err != nil {
|
||||||
|
// return fmt.Errorf("更新设备版本号失败")
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 认证成功,停止登录超时定时器
|
||||||
|
//if client.authTimer != nil {
|
||||||
|
// client.authTimer.Stop()
|
||||||
|
// client.authTimer = nil
|
||||||
|
//}
|
||||||
|
|
||||||
|
// 认证成功
|
||||||
|
//client.Imei = msg.Imei
|
||||||
|
//client.IsAuth = true
|
||||||
|
|
||||||
|
// 发送响应
|
||||||
|
//response := Message{
|
||||||
|
// MessageType: MessageType{Type: "reg"},
|
||||||
|
// MessageTime: MessageTime{Time: time.Now().Unix()},
|
||||||
|
//}
|
||||||
|
//responseData, err := json.Marshal(response)
|
||||||
|
//if err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//_, err = client.Conn.Write(append(responseData, '\r', '\n'))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RealTimeReporting(client *Client, message []byte) error {
|
||||||
|
var msg Message
|
||||||
|
if err := json.Unmarshal(message, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if msg.Type != "start" {
|
||||||
|
return fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
fmt.Printf("设备实时上报数据:%s\r\n", msg.Data)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
35
tcpserver/message.go
Normal file
35
tcpserver/message.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package tcpserver
|
||||||
|
|
||||||
|
type MessageType struct {
|
||||||
|
Type string `json:"Type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessagePassword struct {
|
||||||
|
Pwd string `json:"Pwd,omitempty"`
|
||||||
|
}
|
||||||
|
type MessageImei struct {
|
||||||
|
Imei string `json:"Imei,omitempty"`
|
||||||
|
}
|
||||||
|
type MessageVer struct {
|
||||||
|
Ver string `json:"Ver,omitempty"`
|
||||||
|
}
|
||||||
|
type MessageTime struct {
|
||||||
|
Time int64 `json:"Time,omitempty"`
|
||||||
|
}
|
||||||
|
type MessageState struct {
|
||||||
|
State string `json:"State,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageData struct {
|
||||||
|
Data string `json:"Data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
MessageType
|
||||||
|
MessageImei
|
||||||
|
MessagePassword
|
||||||
|
MessageVer
|
||||||
|
MessageTime
|
||||||
|
MessageState
|
||||||
|
MessageData
|
||||||
|
}
|
||||||
225
tcpserver/tcpserver.go
Normal file
225
tcpserver/tcpserver.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package tcpserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server 定义 TCP 服务器结构
|
||||||
|
type Server struct {
|
||||||
|
Address string // 监听地址
|
||||||
|
Handler func(net.Conn) // 客户端连接处理函数
|
||||||
|
listener net.Listener // TCP 监听器
|
||||||
|
connections sync.Map // 活跃的客户端连接
|
||||||
|
wg sync.WaitGroup // 等待所有 Goroutine 完成
|
||||||
|
stopChan chan struct{} // 关闭信号
|
||||||
|
|
||||||
|
clients map[string]*Client
|
||||||
|
|
||||||
|
clientsMux sync.RWMutex
|
||||||
|
stopOnce sync.Once // 添加 sync.Once 来确保 Stop 只被执行一次
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client 定义客户端结构
|
||||||
|
type Client struct {
|
||||||
|
ID string
|
||||||
|
Imei string // 添加 IMEI
|
||||||
|
Conn net.Conn
|
||||||
|
ConnectedAt time.Time
|
||||||
|
LastPing time.Time
|
||||||
|
Done chan struct{}
|
||||||
|
IsAuth bool // 添加认证状态
|
||||||
|
authTimer *time.Timer // 添加登录超时定时器
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer 创建一个新的 TCP 服务器
|
||||||
|
func NewServer(address string, handler func(net.Conn)) *Server {
|
||||||
|
return &Server{
|
||||||
|
Address: address,
|
||||||
|
Handler: handler,
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
clients: make(map[string]*Client),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动服务器
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
var err error
|
||||||
|
s.listener, err = net.Listen("tcp", s.Address)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to start server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Listening and serving TCP on", s.Address)
|
||||||
|
|
||||||
|
// 捕获终止信号
|
||||||
|
go s.handleShutdown()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// 非阻塞检查关闭信号
|
||||||
|
select {
|
||||||
|
case <-s.stopChan:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接受新连接
|
||||||
|
conn, err := s.listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-s.stopChan:
|
||||||
|
return nil // 服务器已停止
|
||||||
|
default:
|
||||||
|
fmt.Println("Error accepting connection:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录活跃连接并添加到 clients 映射
|
||||||
|
s.connections.Store(conn.RemoteAddr(), conn)
|
||||||
|
client := s.addClient(conn)
|
||||||
|
fmt.Printf("客户端已连接: %s\n", client.ID)
|
||||||
|
|
||||||
|
// 使用 Goroutine 处理客户端
|
||||||
|
s.wg.Add(1)
|
||||||
|
go func(c net.Conn, clientID string) {
|
||||||
|
defer s.wg.Done()
|
||||||
|
defer func() {
|
||||||
|
s.connections.Delete(c.RemoteAddr())
|
||||||
|
s.removeClient(clientID)
|
||||||
|
c.Close()
|
||||||
|
fmt.Printf("客户端已断开连接: %s\n", clientID)
|
||||||
|
}()
|
||||||
|
s.Handler(c)
|
||||||
|
}(conn, client.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleShutdown 处理优雅关闭
|
||||||
|
func (s *Server) handleShutdown() {
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
<-c
|
||||||
|
fmt.Println("\nReceived shutdown signal...")
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOnlineClients 获取所有在线客户端信息
|
||||||
|
func (s *Server) GetOnlineClients() []map[string]interface{} {
|
||||||
|
s.clientsMux.RLock()
|
||||||
|
defer s.clientsMux.RUnlock()
|
||||||
|
|
||||||
|
clients := make([]map[string]interface{}, 0, len(s.clients))
|
||||||
|
for _, client := range s.clients {
|
||||||
|
clientInfo := map[string]interface{}{
|
||||||
|
"id": client.ID,
|
||||||
|
"imei": client.Imei,
|
||||||
|
"addr": client.Conn.RemoteAddr().String(),
|
||||||
|
"connected_at": client.ConnectedAt,
|
||||||
|
"last_ping": client.LastPing,
|
||||||
|
"is_auth": client.IsAuth,
|
||||||
|
}
|
||||||
|
clients = append(clients, clientInfo)
|
||||||
|
}
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
// addClient 添加新客户端
|
||||||
|
func (s *Server) addClient(conn net.Conn) *Client {
|
||||||
|
s.clientsMux.Lock()
|
||||||
|
defer s.clientsMux.Unlock()
|
||||||
|
|
||||||
|
client := &Client{
|
||||||
|
ID: conn.RemoteAddr().String(),
|
||||||
|
Conn: conn,
|
||||||
|
ConnectedAt: time.Now(),
|
||||||
|
LastPing: time.Now(),
|
||||||
|
Done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
s.clients[client.ID] = client
|
||||||
|
|
||||||
|
// 启动心跳检测
|
||||||
|
go client.startHeartbeat(s)
|
||||||
|
|
||||||
|
// 添加登录超时检测
|
||||||
|
client.authTimer = time.AfterFunc(time.Minute, func() {
|
||||||
|
if !client.IsAuth {
|
||||||
|
fmt.Printf("Client %s authentication timeout\n", client.ID)
|
||||||
|
client.Conn.Close() // 强制关闭连接
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeClient 移除客户端
|
||||||
|
func (s *Server) removeClient(id string) {
|
||||||
|
s.clientsMux.Lock()
|
||||||
|
defer s.clientsMux.Unlock()
|
||||||
|
if client, ok := s.clients[id]; ok {
|
||||||
|
if client.authTimer != nil {
|
||||||
|
client.authTimer.Stop()
|
||||||
|
client.authTimer = nil
|
||||||
|
}
|
||||||
|
close(client.Done) // 停止心跳检测
|
||||||
|
delete(s.clients, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 添加一个新方法用于外部调用关闭服务
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
s.stopOnce.Do(func() {
|
||||||
|
fmt.Println("Stopping TCP server...")
|
||||||
|
|
||||||
|
// 关闭监听器
|
||||||
|
close(s.stopChan)
|
||||||
|
if s.listener != nil {
|
||||||
|
s.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭所有连接
|
||||||
|
s.clientsMux.Lock()
|
||||||
|
for id, client := range s.clients {
|
||||||
|
client.Conn.Close()
|
||||||
|
delete(s.clients, id)
|
||||||
|
}
|
||||||
|
s.clientsMux.Unlock()
|
||||||
|
|
||||||
|
// 等待所有 Goroutine 完成
|
||||||
|
s.wg.Wait()
|
||||||
|
fmt.Println("TCP server stopped.")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加心跳检测方法
|
||||||
|
func (c *Client) startHeartbeat(s *Server) {
|
||||||
|
ticker := time.NewTicker(20 * time.Second) // 每10秒检查一次
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.Done:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if time.Since(c.LastPing) > 120*time.Second {
|
||||||
|
fmt.Printf("客户端 %s 心跳超时 \n", c.ID)
|
||||||
|
c.Conn.Close() // 强制关闭连接
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClient 获取指定ID的客户端
|
||||||
|
func (s *Server) GetClient(id string) (*Client, bool) {
|
||||||
|
s.clientsMux.RLock()
|
||||||
|
defer s.clientsMux.RUnlock()
|
||||||
|
client, ok := s.clients[id]
|
||||||
|
return client, ok
|
||||||
|
}
|
||||||
116
ws/client.go
Normal file
116
ws/client.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
writeWait = 10 * time.Second
|
||||||
|
pongWait = 60 * time.Second
|
||||||
|
pingPeriod = (pongWait * 9) / 10
|
||||||
|
maxMessageSize = 512
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client WebSocket 客户端连接
|
||||||
|
type Client struct {
|
||||||
|
Hub *Hub
|
||||||
|
Conn *websocket.Conn
|
||||||
|
Send chan *WsMessage
|
||||||
|
Imei string
|
||||||
|
DeviceId int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ReadPump() {
|
||||||
|
defer func() {
|
||||||
|
c.Hub.Unregister <- c
|
||||||
|
c.Conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Conn.SetReadLimit(maxMessageSize)
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
c.Conn.SetPongHandler(func(string) error {
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, message, err := c.Conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||||
|
log.Printf("读取错误: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是字符串 "PING"
|
||||||
|
if string(message) == "PING" {
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
err := c.Conn.WriteMessage(websocket.TextMessage, []byte("PONG"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("发送 PONG 响应失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 JSON 消息
|
||||||
|
var msg struct {
|
||||||
|
Type string `json:"Type"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(message, &msg); err == nil && msg.Type == "ping" {
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
response := struct {
|
||||||
|
Type string `json:"Type"`
|
||||||
|
}{
|
||||||
|
Type: "pong",
|
||||||
|
}
|
||||||
|
responseData, _ := json.Marshal(response)
|
||||||
|
err := c.Conn.WriteMessage(websocket.TextMessage, responseData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("发送 pong 响应失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) WritePump() {
|
||||||
|
ticker := time.NewTicker(pingPeriod)
|
||||||
|
defer func() {
|
||||||
|
ticker.Stop()
|
||||||
|
c.Conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case message, ok := <-c.Send:
|
||||||
|
if !ok {
|
||||||
|
c.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
log.Printf("准备发送消息到 WebSocket: IMEI=%s, Data=%s", message.IMEI, message.Data)
|
||||||
|
|
||||||
|
// 直接发送原始消息字符串
|
||||||
|
err := c.Conn.WriteMessage(websocket.TextMessage, []byte(message.Data))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("发送消息失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("消息发送成功: IMEI=%s", message.IMEI)
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(writeWait))
|
||||||
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
log.Printf("发送 ping 失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
ws/hub.go
Normal file
88
ws/hub.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package ws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hub 维护活动客户端的集合并广播消息
|
||||||
|
type Hub struct {
|
||||||
|
// 按 IMEI 分组的客户端连接
|
||||||
|
Clients map[string]map[*Client]bool
|
||||||
|
|
||||||
|
// 广播消息的通道(添加 IMEI)
|
||||||
|
Broadcast chan *WsMessage
|
||||||
|
|
||||||
|
// 注册请求
|
||||||
|
Register chan *Client
|
||||||
|
|
||||||
|
// 注销请求
|
||||||
|
Unregister chan *Client
|
||||||
|
|
||||||
|
// 互斥锁保护 clients map
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message WebSocket 消息结构
|
||||||
|
type WsMessage struct {
|
||||||
|
IMEI string `json:"imei"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHub() *Hub {
|
||||||
|
return &Hub{
|
||||||
|
Broadcast: make(chan *WsMessage),
|
||||||
|
Register: make(chan *Client),
|
||||||
|
Unregister: make(chan *Client),
|
||||||
|
Clients: make(map[string]map[*Client]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hub) Run() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case client := <-h.Register:
|
||||||
|
h.mu.Lock()
|
||||||
|
if _, ok := h.Clients[client.Imei]; !ok {
|
||||||
|
h.Clients[client.Imei] = make(map[*Client]bool)
|
||||||
|
}
|
||||||
|
h.Clients[client.Imei][client] = true
|
||||||
|
log.Printf("客户端注册: IMEI=%s", client.Imei)
|
||||||
|
h.mu.Unlock()
|
||||||
|
|
||||||
|
case client := <-h.Unregister:
|
||||||
|
h.mu.Lock()
|
||||||
|
if _, ok := h.Clients[client.Imei]; ok {
|
||||||
|
if _, ok := h.Clients[client.Imei][client]; ok {
|
||||||
|
delete(h.Clients[client.Imei], client)
|
||||||
|
close(client.Send)
|
||||||
|
if len(h.Clients[client.Imei]) == 0 {
|
||||||
|
delete(h.Clients, client.Imei)
|
||||||
|
}
|
||||||
|
log.Printf("客户端注销: IMEI=%s", client.Imei)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.mu.Unlock()
|
||||||
|
|
||||||
|
case broadcast := <-h.Broadcast:
|
||||||
|
h.mu.RLock()
|
||||||
|
log.Printf("收到广播消息: IMEI=%s, Data=%s", broadcast.IMEI, broadcast.Data)
|
||||||
|
if clients, ok := h.Clients[broadcast.IMEI]; ok {
|
||||||
|
log.Printf("找到目标客户端组: IMEI=%s, 客户端数=%d", broadcast.IMEI, len(clients))
|
||||||
|
for client := range clients {
|
||||||
|
select {
|
||||||
|
case client.Send <- broadcast:
|
||||||
|
log.Printf("消息已发送到客户端: IMEI=%s", client.Imei)
|
||||||
|
default:
|
||||||
|
log.Printf("发送失败,关闭客户端: IMEI=%s", client.Imei)
|
||||||
|
close(client.Send)
|
||||||
|
delete(clients, client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("未找到目标客户端组: IMEI=%s", broadcast.IMEI)
|
||||||
|
}
|
||||||
|
h.mu.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user