├── global_const.go ├── global_enum.go ├── .gitignore ├── func_init.go ├── go.mod ├── func_csv.go ├── util_buff.go ├── util_file.go ├── util_time.go ├── func_mysql.go ├── func_ws.go ├── func_tcp.go ├── func_go.go ├── README.md ├── util_rand.go ├── global_variable.go ├── func_udp.go ├── util_string.go ├── func_web_server.go ├── func_udp_client.go ├── func_system.go ├── func_timer.go ├── func_nats.go ├── func_redis.go ├── func_ws_client.go ├── func_log.go ├── starGo_test.go ├── func_tcp_client.go ├── LICENSE └── go.sum /global_const.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | const clientExpireTime int64 = 60 4 | -------------------------------------------------------------------------------- /global_enum.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | type logLv int 4 | 5 | const ( 6 | Debug logLv = iota //调试信息 7 | Info //资讯讯息 8 | Warn //警告状况发生 9 | Error //一般错误,可能导致功能不正常 10 | Fatal //严重错误,会导致进程退出 11 | ) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | /.idea 15 | /log 16 | /csv -------------------------------------------------------------------------------- /func_init.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import "os" 4 | 5 | func init() { 6 | logCh = make(chan *logObj, 1024) 7 | logFileMap = make(map[logLv]*os.File) 8 | logStart() 9 | 10 | oneMinuteFunc = make(map[string]timerFunc) 11 | fiveMinuteFunc = make(map[string]timerFunc) 12 | thirtyMinuteFunc = make(map[string]timerFunc) 13 | timerStart() 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tuanzijia/star_go 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.0 7 | github.com/go-redis/redis v6.15.6+incompatible 8 | github.com/gorilla/websocket v1.4.1 9 | github.com/jinzhu/gorm v1.9.11 10 | github.com/nats-io/nats-server/v2 v2.1.0 // indirect 11 | github.com/nats-io/nats.go v1.8.1 12 | github.com/satori/go.uuid v1.2.0 13 | github.com/zhnxin/csvreader v0.0.0-20190606083136-f9e31b1bce61 14 | gopkg.in/yaml.v2 v2.2.8 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /func_csv.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "github.com/zhnxin/csvreader" 5 | ) 6 | 7 | type Csv struct { 8 | reader *csvreader.Decoder 9 | } 10 | 11 | func NewCsvReader() *Csv { 12 | return &Csv{reader: csvreader.New()} 13 | } 14 | 15 | func (c *Csv) UnMarshalFile(file string, data interface{}) { 16 | err := c.reader.UnMarshalFile(file, data) 17 | if err != nil { 18 | ErrorLog("反序列化csv文件出错,错误信息:%v", err) 19 | } 20 | } 21 | 22 | func (c *Csv) UnMarshalFileWithHeader(file string, data interface{}, header []string) { 23 | err := c.reader.WithHeader(header).UnMarshalFile(file, &data) 24 | if err != nil { 25 | ErrorLog("反序列化csv文件出错,错误信息:%v", err) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /util_buff.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | ) 7 | 8 | func Int32ToBytes(n int32, bigEndian bool) []byte { 9 | bytesBuffer := bytes.NewBuffer([]byte{}) 10 | if bigEndian { 11 | _ = binary.Write(bytesBuffer, binary.BigEndian, n) 12 | } else { 13 | _ = binary.Write(bytesBuffer, binary.LittleEndian, n) 14 | } 15 | 16 | return bytesBuffer.Bytes() 17 | } 18 | 19 | func BytesToInt32(data []byte, bigEndian bool) int32 { 20 | bytesBuffer := bytes.NewBuffer(data) 21 | 22 | var result int32 23 | if bigEndian { 24 | _ = binary.Read(bytesBuffer, binary.BigEndian, &result) 25 | } else { 26 | _ = binary.Read(bytesBuffer, binary.LittleEndian, &result) 27 | } 28 | 29 | return result 30 | } 31 | -------------------------------------------------------------------------------- /util_file.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | // 文件夹是否存在(obsolete) 10 | func IsDirExists(path string) bool { 11 | file, err := os.Stat(path) 12 | if err != nil { 13 | return false 14 | } else { 15 | return file.IsDir() 16 | } 17 | } 18 | 19 | // 文件是否存在 20 | func IsFileExists(path string) (bool, error) { 21 | file, err := os.Stat(path) 22 | if err == nil { 23 | return file.IsDir() == false, nil 24 | } else { 25 | if os.IsNotExist(err) { 26 | return false, nil 27 | } 28 | } 29 | 30 | return true, err 31 | } 32 | 33 | // 读取json文件 34 | func ReadJsonFile(path string, v interface{}) error { 35 | data, err := ioutil.ReadFile(path) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | err = json.Unmarshal(data, v) 41 | if err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /util_time.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // ToDateTime 转换成日期格式 9 | func ToDateTime(timeVal string) (time.Time, error) { 10 | if IsEmpty(timeVal) { 11 | return time.Time{}, fmt.Errorf("输入是空字符串") 12 | } 13 | 14 | return time.ParseInLocation("2006-01-02 15:04:05", timeVal, time.Local) 15 | } 16 | 17 | // ToDate 转换成时间格式 18 | func ToDate(timeVal string) (time.Time, error) { 19 | if IsEmpty(timeVal) { 20 | return time.Time{}, fmt.Errorf("输入是空字符串") 21 | } 22 | 23 | return time.ParseInLocation("2006-01-02", timeVal, time.Local) 24 | } 25 | 26 | // ToDateTimeString 转换成时间字符串 27 | func ToDateTimeString(timeVal time.Time) string { 28 | return timeVal.Local().Format("2006-01-02 15:04:05") 29 | } 30 | 31 | // ToDateString 转换成日期字符串 32 | func ToDateString(timeVal time.Time) string { 33 | return timeVal.Local().Format("2006-01-02") 34 | } 35 | -------------------------------------------------------------------------------- /func_mysql.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/jinzhu/gorm" 7 | _ "github.com/jinzhu/gorm/dialects/mysql" 8 | ) 9 | 10 | type Mysql struct { 11 | db *gorm.DB 12 | connectionStr string 13 | } 14 | 15 | func NewMysql(connection string, maxIdleCount, maxOpenCount int) *Mysql { 16 | mysql := new(Mysql) 17 | db, err := gorm.Open("mysql", connection) 18 | if err != nil { 19 | ErrorLog("连接mysql出错,错误信息:%v", err) 20 | panic(fmt.Errorf("连接mysql出错,错误信息:%v", err)) 21 | } 22 | db.DB().SetMaxIdleConns(maxIdleCount) 23 | db.DB().SetMaxOpenConns(maxOpenCount) 24 | mysql.db = db 25 | mysql.connectionStr = connection 26 | mysqlCfg = mysql 27 | 28 | return mysql 29 | } 30 | 31 | func (m *Mysql) GetDb() *gorm.DB { 32 | return m.db 33 | } 34 | 35 | func (m *Mysql) GetConnectionStr() string { 36 | return m.connectionStr 37 | } 38 | 39 | func (m *Mysql) RegisterTableModel(model interface{}) error { 40 | return m.db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").AutoMigrate(model).Error 41 | 42 | } 43 | 44 | func (m *Mysql) RegisterTableModelForTableName(tableName string, model interface{}) error { 45 | return m.db.Table(tableName).AutoMigrate(model).Error 46 | } 47 | -------------------------------------------------------------------------------- /func_ws.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | ) 8 | 9 | var upgrade = websocket.Upgrader{ 10 | ReadBufferSize: 1024, 11 | WriteBufferSize: 1024, 12 | } 13 | 14 | func handleConn(w http.ResponseWriter, r *http.Request) { 15 | conn, err := upgrade.Upgrade(w, r, nil) 16 | if err != nil { 17 | ErrorLog("webSocket获取连接时出错,错误信息:%v", err) 18 | return 19 | } 20 | 21 | // 新注册客户端 22 | client := newWebSocketClient(conn) 23 | client.start() 24 | registerWebSocketClient(client) 25 | 26 | InfoLog("收到客户端:%v连接请求", client.GetConn().RemoteAddr().String()) 27 | } 28 | 29 | // 启动服务器 30 | func StartWebSocketServer(addr string, url string, handler ClientCallBack, clientExpireHandler ClientExpireCallBack, headerLen int32) error { 31 | InfoLog("开始监听WebSocket地址:%v,url:%v", addr, url) 32 | http.HandleFunc(url, handleConn) 33 | err := http.ListenAndServe(addr, nil) 34 | if err != nil { 35 | ErrorLog("监听webSocket地址:%v出错,错误信息:%v", addr, err) 36 | return err 37 | } 38 | 39 | // 注册回调方法 40 | wsHandlerReceiveFunc = handler 41 | wsClientExpireHandleFunc = clientExpireHandler 42 | 43 | // 记录头部数据长度 44 | wsReceiveDataHeaderLen = headerLen 45 | 46 | // 启动客户端处理协程 47 | clearExpireWebSocketClient() 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /func_tcp.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import "net" 4 | 5 | func StartTcpServer(addr string, handler ClientCallBack, clientExpireHandler ClientExpireCallBack, headerLen int32) error { 6 | InfoLog("开始监听Tcp地址:%v", addr) 7 | listen, err := net.Listen("tcp", addr) 8 | if err != nil { 9 | ErrorLog("tcp监听地址%v出错,错误信息:%v", addr, err) 10 | return err 11 | } 12 | Go(func(Stop chan struct{}) { 13 | c := make(chan struct{}) 14 | Go(func(Stop1 chan struct{}) { 15 | select { 16 | case <-Stop1: 17 | case <-c: 18 | } 19 | _ = listen.Close() 20 | }) 21 | 22 | for allForStopSignal == 0 { 23 | c, err := listen.Accept() 24 | if err != nil { 25 | ErrorLog("接收客户端连接失败,错误信息:%v", err) 26 | break 27 | } 28 | 29 | // 新注册客户端 30 | client := newTcpClient(c) 31 | registerTcpClient(client) 32 | client.start() 33 | 34 | InfoLog("收到客户端:%v的连接请求", c.RemoteAddr().String()) 35 | } 36 | }) 37 | 38 | // 注册回调方法 39 | tcpHandlerReceiveFunc = handler 40 | tcpClientExpireHandleFunc = clientExpireHandler 41 | 42 | // 记录头部数据长度 43 | tcpReceiveDataHeaderLen = headerLen 44 | 45 | // 启动客户端处理协程 46 | clearExpireTcpClient() 47 | 48 | return nil 49 | } 50 | 51 | func GetTcpClient(addr string) *Client { 52 | client, exists := tcpClientMap.Load(addr) 53 | if !exists { 54 | return nil 55 | } 56 | 57 | return client.(*Client) 58 | } 59 | -------------------------------------------------------------------------------- /func_go.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "sync/atomic" 6 | ) 7 | 8 | func Try(f func(), handler func(interface{})) { 9 | defer func() { 10 | if err := recover(); err != nil { 11 | if handler == nil { 12 | Stack() 13 | ErrorLog("错误信息:%v", err) 14 | } else { 15 | handler(err) 16 | } 17 | } 18 | }() 19 | f() 20 | } 21 | 22 | func Go(f func(Stop chan struct{})) { 23 | waitAllGroup.Add(1) 24 | id := atomic.AddUint64(&goId, 1) 25 | c := atomic.AddInt32(&goCount, 1) 26 | debugStr := SimpleTack() 27 | InfoLog("新开协程 id:%d 当前协程数量:%d 来自:%s", id, c, debugStr) 28 | 29 | go func() { 30 | Try(func() { f(stopChanForGo) }, nil) 31 | waitAllGroup.Done() 32 | c = atomic.AddInt32(&goCount, -1) 33 | InfoLog("协程运行结束 id:%d 当前协程数量:%d 来自:%s", id, c, debugStr) 34 | }() 35 | } 36 | 37 | func Go2(f func()) { 38 | waitAllGroup.Add(1) 39 | debugStr := SimpleTack() 40 | c := atomic.AddInt32(&goCount, 1) 41 | InfoLog("新开协程 当前协程数量:%d 来自:%s", c, debugStr) 42 | go func() { 43 | Try(func() { f() }, nil) 44 | waitAllGroup.Done() 45 | c = atomic.AddInt32(&goCount, -1) 46 | InfoLog("协程运行结束 当前协程数量:%d 来自:%s", c, debugStr) 47 | }() 48 | } 49 | 50 | func goForLog(f func(Stop chan struct{})) { 51 | defer func() { 52 | if err := recover(); err != nil { 53 | // 只打印异常,避免死循环 54 | fmt.Printf("捕获到日志抛出的异常:%v", err) 55 | } 56 | }() 57 | 58 | if logForStopSignal != 0 { 59 | return 60 | } 61 | 62 | waitLogGroup.Add(1) 63 | go func() { 64 | f(stopChanForLog) 65 | waitLogGroup.Done() 66 | }() 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # starGo 2 | starGo是一款高性能、分布式、轻量级、微服务的游戏服务器框架。框架采用了go语言开发,得益于go本身对高并发的强大支持,框架足够简洁,效率足够高效。服务器框架二次开发简单易上手,实现了高性能的异步网络库,分布式节点间的通信采用了高性能通信中间件nats,能够实现每秒20万的Qps,日志管理,常规的关系型数据库(mysql)和非关系型数据库redis的支持,goroutine的安全定时器工具等。 3 | 4 | 服务器框架可用于包括但不限于游戏服务器等的应用,可以在框架开发阶段上节省大量时间。同时可以通过nats的发布订阅通道进行服务器的热更新等操作. 5 | #### 优势特点 6 | 1) 开发效率高 7 | 2) 支持自定义的通信协议 8 | 3) 采用nats高性能通信中间,实现了异步发布订阅,请求响应等模式的通信请求 9 | 4) 分布式、微服务的架构,方便横向拓展 10 | 5) 协程安全的定时器实现 11 | 6) 对协程安全封装,优雅的实现开启退出 12 | 7) 内置redis、mysql数据库支持 13 | #### 安装教程 14 | 由于使用了nats做为通信中间件,所以需要安装nats服务器。使用docker安装非常的方便,简洁. 15 | 16 | docker pull nats:latest 17 | docker run -p 4222:4222 -p 8222:8222 -p 6222:6222 -ti nats:latest 18 | 19 | 然后安装依赖项,golang 1.13版本提供非常方便的依赖项管理工具go mod,你只需要输入go mod tidy,便可非常方便快捷的倒入依赖项。 20 | 21 | 当然你也可通过go get来手动导入依赖包 22 | 23 | go get github.com/go-redis/redis 24 | go get github.com/jinzhu/gorm 25 | go get github.com/nats-io/nats.go 26 | go get github.com/satori/go.uuid 27 | go get github.com/zhnxin/csvreader 28 | #### 使用说明 29 | 最简单的一个例子: 30 | 31 | import ( 32 | "github.com/tuanzijia/star_go" 33 | ) 34 | 35 | func main() { 36 | // 开启日志 37 | starGo.StartLog("log", starGo.Debug) 38 | 39 | // 启动服务器 40 | starGo.Start() 41 | 42 | // 开启tcp连接 43 | err := starGo.StartTcpServer("127.0.0.1:9999", nil, 4) 44 | if err != nil { 45 | starGo.ErrorLog(err) 46 | return 47 | } 48 | 49 | // 开启nats连接 50 | starGo.StartNatConn("127.0.0.1:4222") 51 | 52 | // 等待系统退出 53 | starGo.WaitForSystemExit() 54 | } 55 | #### 写在最后 56 | 我的想法是打造一款高性能,分布式,微服务,轻量级的游戏服务器框架初次写开源框架,肯定有很多不足和考虑不到的地方,如果有什么建议和意见欢迎提issues。 57 | -------------------------------------------------------------------------------- /util_rand.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "crypto/rand" 5 | "math" 6 | ) 7 | 8 | func RandInt64() int64 { 9 | result, err := rand.Int(rand.Reader, maxBigInt64Edge) 10 | if err != nil { 11 | return math.MaxInt64 12 | } 13 | 14 | return result.Int64() 15 | } 16 | 17 | func RandInt64n(n int64) int64 { 18 | if n <= 0 { 19 | return 0 20 | } 21 | if n&(n-1) == 0 { 22 | return RandInt64() & (n - 1) 23 | } 24 | max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) 25 | v := RandInt64() 26 | for v > max { 27 | v = RandInt64() 28 | } 29 | 30 | return v % n 31 | } 32 | 33 | func RandUint64() uint64 { 34 | return uint64(RandInt64())>>31 | uint64(RandInt64())<<32 35 | } 36 | 37 | func RandUint32() uint32 { 38 | return uint32(RandInt64() >> 31) 39 | } 40 | 41 | func RandInt32() int32 { 42 | return int32(RandInt64() >> 32) 43 | } 44 | 45 | func RandInt32n(n int32) int32 { 46 | if n <= 0 { 47 | return 0 48 | } 49 | if n&(n-1) == 0 { 50 | return RandInt32() & (n - 1) 51 | } 52 | max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) 53 | v := RandInt32() 54 | for v > max { 55 | v = RandInt32() 56 | } 57 | return v % n 58 | } 59 | 60 | func RandInt() int { 61 | u := uint(RandInt64()) 62 | return int(u << 1 >> 1) 63 | } 64 | 65 | func RandIntN(n int) int { 66 | if n <= 0 { 67 | return 0 68 | } 69 | if n <= 1<<31-1 { 70 | return int(RandInt32n(int32(n))) 71 | } 72 | return int(RandInt64n(int64(n))) 73 | } 74 | 75 | func Shuffle(n int, swap func(i, j int)) { 76 | if n <= 0 { 77 | return 78 | } 79 | i := n - 1 80 | for ; i > 1<<31-1-1; i-- { 81 | j := int(RandInt64n(int64(i + 1))) 82 | swap(i, j) 83 | } 84 | for ; i > 0; i-- { 85 | j := int(RandInt32n(int32(i + 1))) 86 | swap(i, j) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /global_variable.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "math" 5 | "math/big" 6 | "os" 7 | "sync" 8 | 9 | "github.com/nats-io/nats.go" 10 | ) 11 | 12 | var ( 13 | maxBigInt64Edge = big.NewInt(0).Add(big.NewInt(math.MaxInt64), big.NewInt(1)) 14 | baseString = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 15 | allForStopSignal int32 16 | logForStopSignal int32 17 | 18 | logDirPath string 19 | logFileMap map[logLv]*os.File 20 | logMutex sync.Mutex 21 | logCh chan *logObj 22 | logOnce sync.Once 23 | logNowLv logLv 24 | logLvNameMap = map[logLv]string{ 25 | Debug: "debug", 26 | Info: "info", 27 | Warn: "warn", 28 | Error: "error", 29 | Fatal: "fatal", 30 | } 31 | 32 | waitAllGroup sync.WaitGroup 33 | waitLogGroup sync.WaitGroup 34 | goCount int32 35 | goId uint64 36 | stopChanForGo = make(chan struct{}) 37 | stopChanForLog = make(chan struct{}) 38 | 39 | timerMutex sync.RWMutex 40 | oneMinuteFunc map[string]timerFunc 41 | fiveMinuteFunc map[string]timerFunc 42 | thirtyMinuteFunc map[string]timerFunc 43 | 44 | systemExitFunc []func() 45 | systemReloadFunc []func() 46 | 47 | tcpClientMap sync.Map 48 | udpClientMap sync.Map 49 | wsClientMap sync.Map 50 | tcpReceiveDataHeaderLen int32 51 | udpReceiveDataHeaderLen int32 52 | wsReceiveDataHeaderLen int32 53 | tcpHandlerReceiveFunc ClientCallBack 54 | udpHandlerReceiveFunc ClientCallBack 55 | wsHandlerReceiveFunc ClientCallBack 56 | tcpClientExpireHandleFunc ClientExpireCallBack 57 | wsClientExpireHandleFunc ClientExpireCallBack 58 | 59 | natChMap sync.Map 60 | natConn *nats.Conn 61 | 62 | mysqlCfg *Mysql 63 | redisCfg *Redis 64 | ) 65 | -------------------------------------------------------------------------------- /func_udp.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func StartUdpServer(addr string, handler ClientCallBack, headerLen int32) error { 8 | InfoLog("开始监听Udp地址:%v", addr) 9 | udpAddr, err := net.ResolveUDPAddr("udp", addr) 10 | if err != nil { 11 | ErrorLog("监听Udp地址:%v失败,错误信息:%v", addr, err) 12 | return err 13 | } 14 | conn, err1 := net.ListenUDP("udp", udpAddr) 15 | if err1 != nil { 16 | ErrorLog("监听Udp地址:%v失败,错误信息:%v", addr, err) 17 | return err1 18 | } 19 | 20 | // 监听 21 | listen(conn) 22 | 23 | // 注册回调方法 24 | udpHandlerReceiveFunc = handler 25 | 26 | // 记录头部数据长度 27 | udpReceiveDataHeaderLen = headerLen 28 | 29 | return nil 30 | } 31 | 32 | func listen(conn *net.UDPConn) { 33 | // 设置缓冲区 34 | _ = conn.SetReadBuffer(1024) 35 | _ = conn.SetWriteBuffer(1024) 36 | 37 | Go(func(Stop chan struct{}) { 38 | c := make(chan struct{}) 39 | Go(func(Stop1 chan struct{}) { 40 | select { 41 | case <-Stop1: 42 | case <-c: 43 | } 44 | _ = conn.Close() 45 | }) 46 | 47 | listenTrue(conn) 48 | 49 | close(c) 50 | }) 51 | } 52 | 53 | func listenTrue(conn *net.UDPConn) { 54 | data := make([]byte, 1024) 55 | for allForStopSignal == 0 { 56 | n, udpAddr, err := conn.ReadFromUDP(data) 57 | 58 | if err != nil { 59 | if err.(net.Error).Timeout() { 60 | continue 61 | } 62 | break 63 | } 64 | 65 | if n <= 0 { 66 | continue 67 | } 68 | 69 | addr := udpAddr.String() 70 | clientInfo, exists := udpClientMap.Load(addr) 71 | if !exists { 72 | client := newUdpClient(conn, udpAddr) 73 | client.start() 74 | udpClientMap.Store(addr, client) 75 | } 76 | 77 | client := clientInfo.(*UdpClient) 78 | client.receiveCh <- data 79 | } 80 | } 81 | 82 | func GetUdpClient(addr string) *UdpClient { 83 | client, exists := udpClientMap.Load(addr) 84 | if !exists { 85 | return nil 86 | } 87 | 88 | return client.(*UdpClient) 89 | } 90 | -------------------------------------------------------------------------------- /util_string.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "crypto/md5" 5 | "errors" 6 | "fmt" 7 | "runtime" 8 | "strings" 9 | "unicode" 10 | 11 | "github.com/satori/go.uuid" 12 | ) 13 | 14 | // GetRandomString 获取随机长度的字符串 15 | func GetRandomString(l int) string { 16 | bytes := []byte(baseString) 17 | var result []byte 18 | for i := 0; i < l; i++ { 19 | result = append(result, bytes[RandIntN(len(bytes))]) 20 | } 21 | return string(result) 22 | } 23 | 24 | // IsEmpty 检查一个字符串是否是空字符串 25 | func IsEmpty(content string) bool { 26 | if len(content) <= 0 { 27 | return true 28 | } 29 | 30 | return strings.IndexFunc(content, func(item rune) bool { 31 | return unicode.IsSpace(item) == false 32 | }) < 0 33 | } 34 | 35 | // GetNewLineString 根据不同平台获取换行符 36 | func GetNewLineString() string { 37 | switch os := runtime.GOOS; os { 38 | case "windows": 39 | return "\r\n" 40 | default: 41 | return "\n" 42 | } 43 | } 44 | 45 | // GetNewUUID 获取新的UUID字符串 46 | func GetNewUUID() string { 47 | return fmt.Sprintf("%v", uuid.Must(uuid.NewV4(), nil)) 48 | } 49 | 50 | // IsUUIDEmpty 判断UUID是否为空 51 | func IsUUIDEmpty(uuid string) bool { 52 | if uuid == "" || uuid == "00000000-0000-0000-0000-000000000000" { 53 | return true 54 | } 55 | 56 | return false 57 | } 58 | 59 | // IsUUIDEqual 比较UUID是否相等 60 | func IsUUIDEqual(uuid1, uuid2 string) bool { 61 | u1, err1 := uuid.FromString(uuid1) 62 | u2, err2 := uuid.FromString(uuid2) 63 | if err1 != nil || err2 != nil { 64 | return false 65 | } 66 | return uuid.Equal(u1, u2) 67 | } 68 | 69 | // Md5Bytes 对字符数组进行MD5加密,并且可以选择返回大、小写 70 | func Md5Bytes(b []byte, ifUpper bool) string { 71 | if len(b) == 0 { 72 | panic(errors.New("input []byte can't be empty")) 73 | } 74 | 75 | md5Instance := md5.New() 76 | md5Instance.Write(b) 77 | result := md5Instance.Sum([]byte("")) 78 | if ifUpper { 79 | return fmt.Sprintf("%X", result) 80 | } else { 81 | return fmt.Sprintf("%x", result) 82 | } 83 | } 84 | 85 | // Md5String 对字符串进行MD5加密,并且可以选择返回大、小写 86 | func Md5String(s string, ifUpper bool) string { 87 | if len(s) == 0 { 88 | panic(errors.New("input string can't be empty")) 89 | } 90 | 91 | return Md5Bytes([]byte(s), ifUpper) 92 | } 93 | -------------------------------------------------------------------------------- /func_web_server.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | type webRequestType string 6 | 7 | const ( 8 | GET webRequestType = "GET" 9 | POST webRequestType = "POST" 10 | DELETE webRequestType = "DELETE" 11 | PATCH webRequestType = "PATCH" 12 | PUT webRequestType = "PUT" 13 | OPTIONS webRequestType = "OPTIONS" 14 | HEAD webRequestType = "HEAD" 15 | ) 16 | 17 | type WebServer struct { 18 | router *gin.Engine // 路由实例 19 | serverIpAddress string 20 | isUseMiddleware bool 21 | registerFuncMap map[webRequestType]map[string]bool 22 | } 23 | 24 | func NewWebServer(ip string, isUseMiddleware bool) *WebServer { 25 | return &WebServer{ 26 | router: gin.New(), 27 | serverIpAddress: ip, 28 | isUseMiddleware: isUseMiddleware, 29 | registerFuncMap: make(map[webRequestType]map[string]bool), 30 | } 31 | } 32 | 33 | func (w *WebServer) StartWebServer() { 34 | // 启用中间件 35 | if w.isUseMiddleware { 36 | w.router.Use(gin.Recovery()) 37 | w.router.Use(gin.Logger()) 38 | } 39 | 40 | if w.serverIpAddress == "" { 41 | err := w.router.Run() 42 | if err != nil { 43 | ErrorLog("启动webServer时出错,错误信息:%v", err) 44 | } 45 | } else { 46 | err := w.router.Run(w.serverIpAddress) 47 | if err != nil { 48 | ErrorLog("启动webServer时出错,错误信息:%v", err) 49 | } 50 | } 51 | } 52 | 53 | func (w *WebServer) RegisterRequestHandleFunc(requestType webRequestType, url string, handleFunc gin.HandlerFunc) { 54 | _, mapExists := w.registerFuncMap[requestType] 55 | if mapExists { 56 | _, urlExists := w.registerFuncMap[requestType][url] 57 | if urlExists { 58 | ErrorLog("已注册相同URL路径,requestType:%v, url:%v", requestType, url) 59 | return 60 | } 61 | } else { 62 | w.registerFuncMap[requestType] = make(map[string]bool) 63 | } 64 | 65 | // 注册方法 66 | w.registerFuncMap[requestType][url] = true 67 | 68 | switch requestType { 69 | case GET: 70 | w.router.GET(url, handleFunc) 71 | case POST: 72 | w.router.POST(url, handleFunc) 73 | case DELETE: 74 | w.router.DELETE(url, handleFunc) 75 | case PATCH: 76 | w.router.PATCH(url, handleFunc) 77 | case PUT: 78 | w.router.PUT(url, handleFunc) 79 | case OPTIONS: 80 | w.router.OPTIONS(url, handleFunc) 81 | case HEAD: 82 | w.router.HEAD(url, handleFunc) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /func_udp_client.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | ) 7 | 8 | type UdpClient struct { 9 | addr *net.UDPAddr 10 | conn *net.UDPConn 11 | stop bool 12 | receiveCh chan []byte 13 | sendCh chan []byte 14 | mutex sync.RWMutex 15 | } 16 | 17 | func newUdpClient(conn *net.UDPConn, addr *net.UDPAddr) *UdpClient { 18 | return &UdpClient{ 19 | addr: addr, 20 | conn: conn, 21 | stop: false, 22 | receiveCh: make(chan []byte, 1024), 23 | sendCh: make(chan []byte, 1024), 24 | } 25 | } 26 | 27 | func (c *UdpClient) GetAddr() *net.UDPAddr { 28 | c.mutex.RLock() 29 | defer c.mutex.RUnlock() 30 | return c.addr 31 | } 32 | 33 | func (c *UdpClient) GetStop() bool { 34 | c.mutex.RLock() 35 | defer c.mutex.RUnlock() 36 | return c.stop 37 | } 38 | 39 | func (c *UdpClient) SetStop() { 40 | c.mutex.Lock() 41 | defer c.mutex.Unlock() 42 | c.stop = true 43 | } 44 | 45 | func (c *UdpClient) GetReceiveData(headerLen int32, data []byte) (message []byte, exists bool) { 46 | if len(data) < int(headerLen) { 47 | return 48 | } 49 | 50 | // 获取头部信息 51 | header := data[:headerLen] 52 | 53 | // 将头部数据转换为内容的长度 54 | contentLength := BytesToInt32(header, true) 55 | 56 | // 判断长度是否满足 57 | if len(data) < int(headerLen+contentLength) { 58 | return 59 | } 60 | 61 | // 提取消息内容 62 | message = data[headerLen : headerLen+contentLength] 63 | 64 | // 存在合理的数据 65 | exists = true 66 | 67 | return 68 | } 69 | 70 | func (c *UdpClient) SetSendQueue(data []byte) { 71 | c.mutex.Lock() 72 | defer c.mutex.Unlock() 73 | c.sendCh <- data 74 | } 75 | 76 | func (c *UdpClient) read() { 77 | Go(func(Stop chan struct{}) { 78 | defer func() { 79 | c.SetStop() 80 | }() 81 | 82 | for !c.GetStop() { 83 | select { 84 | case <-Stop: 85 | return 86 | case receiveData := <-c.receiveCh: 87 | Go2(func() { 88 | message, exists := c.GetReceiveData(udpReceiveDataHeaderLen, receiveData) 89 | if exists && udpHandlerReceiveFunc != nil { 90 | udpHandlerReceiveFunc(message, c.GetAddr().String()) 91 | } 92 | }) 93 | } 94 | } 95 | }) 96 | } 97 | 98 | func (c *UdpClient) write() { 99 | Go(func(Stop chan struct{}) { 100 | defer func() { 101 | c.SetStop() 102 | }() 103 | 104 | if !c.GetStop() { 105 | select { 106 | case <-Stop: 107 | return 108 | case sendData := <-c.sendCh: 109 | Go2(func() { 110 | _, _ = c.conn.WriteToUDP(sendData, c.GetAddr()) 111 | }) 112 | } 113 | } 114 | }) 115 | } 116 | 117 | func (c *UdpClient) start() { 118 | c.read() 119 | c.write() 120 | } 121 | -------------------------------------------------------------------------------- /func_system.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "os/signal" 8 | "path/filepath" 9 | "strings" 10 | "sync/atomic" 11 | "syscall" 12 | ) 13 | 14 | func WaitForSystemExit() { 15 | sign := make(chan os.Signal) 16 | signal.Notify(sign, os.Interrupt, os.Kill, syscall.SIGTERM) 17 | <-sign 18 | InfoLog("收到退出信号") 19 | systemExit() 20 | 21 | waitAllGroup.Wait() 22 | if !atomic.CompareAndSwapInt32(&logForStopSignal, 0, 1) { 23 | return 24 | } 25 | close(stopChanForLog) 26 | waitLogGroup.Wait() 27 | fmt.Println("服务器已关闭") 28 | } 29 | 30 | func RegisterSystemExitFunc(f func()) { 31 | systemExitFunc = append(systemExitFunc, f) 32 | } 33 | 34 | func RegisterSystemReloadFunc(f func()) { 35 | systemReloadFunc = append(systemReloadFunc) 36 | } 37 | 38 | func systemExit() { 39 | InfoLog("调用退出时方法") 40 | for _, f := range systemExitFunc { 41 | f() 42 | } 43 | InfoLog("更新停止信号") 44 | // 更新停止信号 45 | if !atomic.CompareAndSwapInt32(&allForStopSignal, 0, 1) { 46 | return 47 | } 48 | close(stopChanForGo) 49 | InfoLog("关闭所有连接") 50 | // 关闭所有tcp连接 51 | tcpClientMap.Range(func(key, value interface{}) bool { 52 | client := value.(*Client) 53 | client.SetStop() 54 | client.GetConn().Close() 55 | tcpClientMap.Delete(key) 56 | 57 | return true 58 | }) 59 | 60 | // 关闭所有udp连接 61 | udpClientMap.Range(func(key, value interface{}) bool { 62 | udpClientMap.Delete(key) 63 | return true 64 | }) 65 | 66 | // 关闭所有webSocket连接 67 | wsClientMap.Range(func(key, value interface{}) bool { 68 | client := value.(*WebSocketClient) 69 | client.SetStop() 70 | _ = client.GetConn().Close() 71 | wsClientMap.Delete(key) 72 | return true 73 | }) 74 | 75 | // 关闭mysql和redis连接 76 | InfoLog("关闭mysql和redis连接") 77 | if mysqlCfg != nil { 78 | _ = mysqlCfg.GetDb().Close() 79 | } 80 | if redisCfg != nil { 81 | _ = redisCfg.GetConnection().Close() 82 | } 83 | 84 | InfoLog("系统退出方法调用完成") 85 | } 86 | 87 | func systemReload() { 88 | for _, f := range systemReloadFunc { 89 | f() 90 | } 91 | } 92 | 93 | func Daemon(skip ...string) { 94 | if os.Getppid() != 1 { 95 | filePath, _ := filepath.Abs(os.Args[0]) 96 | newCmd := []string{os.Args[0]} 97 | add := 0 98 | for _, v := range os.Args[1:] { 99 | if add == 1 { 100 | add = 0 101 | continue 102 | } else { 103 | add = 0 104 | } 105 | for _, s := range skip { 106 | if strings.Contains(v, s) { 107 | if strings.Contains(v, "--") { 108 | add = 2 109 | } else { 110 | add = 1 111 | } 112 | break 113 | } 114 | } 115 | if add == 0 { 116 | newCmd = append(newCmd, v) 117 | } 118 | } 119 | InfoLog("后台运行参数:%v", newCmd) 120 | cmd := exec.Command(filePath) 121 | cmd.Args = newCmd 122 | _ = cmd.Start() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /func_timer.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import "time" 4 | 5 | type timerFunc func(time time.Time) 6 | 7 | func RegisterOneMinuteFunc(funcName string, f timerFunc) { 8 | timerMutex.Lock() 9 | defer timerMutex.Unlock() 10 | if _, exists := oneMinuteFunc[funcName]; exists { 11 | ErrorLog("方法名:%v的一分钟执行方法已注册") 12 | return 13 | } 14 | oneMinuteFunc[funcName] = f 15 | } 16 | 17 | func RegisterFiveMinuteFunc(funcName string, f timerFunc) { 18 | timerMutex.Lock() 19 | defer timerMutex.Unlock() 20 | if _, exists := fiveMinuteFunc[funcName]; exists { 21 | ErrorLog("方法名:%v的五分钟执行方法已注册") 22 | return 23 | } 24 | fiveMinuteFunc[funcName] = f 25 | } 26 | 27 | func RegisterThirtyMinuteFunc(funcName string, f timerFunc) { 28 | timerMutex.Lock() 29 | defer timerMutex.Unlock() 30 | if _, exists := thirtyMinuteFunc[funcName]; exists { 31 | ErrorLog("方法名:%v的三十分钟执行方法已注册") 32 | return 33 | } 34 | thirtyMinuteFunc[funcName] = f 35 | } 36 | 37 | func timerStart() { 38 | Go(func(Stop chan struct{}) { 39 | t := time.NewTicker(1 * time.Second) 40 | for allForStopSignal == 0 { 41 | select { 42 | case <-Stop: 43 | return 44 | case <-t.C: 45 | nowTime := time.Now() 46 | 47 | // 整分钟数开始执行 48 | if nowTime.Second() == 0 { 49 | // 执行一分钟方法 50 | callOneMinuteFunc(nowTime) 51 | 52 | if nowTime.Minute()%5 == 0 { 53 | // 执行五分钟方法 54 | callFiveMinuteFunc(nowTime) 55 | } 56 | 57 | if nowTime.Minute()%30 == 0 { 58 | // 执行三十分方法 59 | callThirtyMinuteFunc(nowTime) 60 | } 61 | 62 | if nowTime.Minute() == 0 { 63 | // 整理日志文件 64 | reorganizeLog(nowTime) 65 | } 66 | } 67 | } 68 | } 69 | }) 70 | } 71 | 72 | func callOneMinuteFunc(nowTime time.Time) { 73 | timerMutex.RLock() 74 | defer timerMutex.RUnlock() 75 | for name, f := range oneMinuteFunc { 76 | startTime := time.Now().UnixNano() 77 | InfoLog("开始执行%v方法", name) 78 | Try(func() { f(nowTime) }, nil) 79 | useTime := float64(time.Now().UnixNano() - startTime) 80 | InfoLog("%v方法执行完成,总耗时%v毫秒", name, useTime/float64(1000000)) 81 | } 82 | } 83 | 84 | func callFiveMinuteFunc(nowTime time.Time) { 85 | timerMutex.RLock() 86 | defer timerMutex.RUnlock() 87 | for name, f := range fiveMinuteFunc { 88 | startTime := time.Now().UnixNano() 89 | InfoLog("开始执行%v方法", name) 90 | Try(func() { f(nowTime) }, nil) 91 | useTime := float64(time.Now().UnixNano() - startTime) 92 | InfoLog("%v方法执行完成,总耗时%v毫秒", name, useTime/float64(1000000)) 93 | } 94 | } 95 | 96 | func callThirtyMinuteFunc(nowTime time.Time) { 97 | timerMutex.RLock() 98 | defer timerMutex.RUnlock() 99 | for name, f := range thirtyMinuteFunc { 100 | startTime := time.Now().UnixNano() 101 | InfoLog("开始执行%v方法", name) 102 | Try(func() { f(nowTime) }, nil) 103 | useTime := float64(time.Now().UnixNano() - startTime) 104 | InfoLog("%v方法执行完成,总耗时%v毫秒", name, useTime/float64(1000000)) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /func_nats.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/nats-io/nats.go" 8 | ) 9 | 10 | type NatCallBack func(result *NatResult) 11 | 12 | type NatResult struct { 13 | Message []byte 14 | Reply string 15 | } 16 | 17 | func StartNatConn(addr, natName string) { 18 | option := make([]nats.Option, 0) 19 | option = append(option, nats.Name(natName), 20 | nats.PingInterval(30*time.Second), 21 | nats.MaxPingsOutstanding(5), 22 | nats.ReconnectWait(3*time.Second), 23 | nats.MaxReconnects(200), 24 | nats.DisconnectErrHandler(func(conn *nats.Conn, e error) { 25 | ErrorLog("nat连接断开,等待重新连接时间:%v分钟", 10) 26 | }), 27 | nats.ReconnectHandler(func(conn *nats.Conn) { 28 | ErrorLog("nat重新连接,URL:%v", conn.ConnectedUrl()) 29 | }), 30 | nats.ClosedHandler(func(conn *nats.Conn) { 31 | ErrorLog("nat退出,err:%v", conn.LastError()) 32 | })) 33 | 34 | nc, err := nats.Connect(addr, option...) 35 | 36 | if err != nil { 37 | ErrorLog("连接nats服务器失败,错误信息:%v", err) 38 | panic(fmt.Errorf("连接nats服务器失败")) 39 | } 40 | 41 | natConn = nc 42 | } 43 | 44 | // 管道模式订阅 45 | func SubscribeChannel(channel string, channelCount int32, cb NatCallBack) { 46 | _, exists := natChMap.Load(channel) 47 | if exists { 48 | ErrorLog("%v通道已被订阅", channel) 49 | } 50 | 51 | ch := make(chan *nats.Msg, 64) 52 | sub, err := natConn.ChanSubscribe(channel, ch) 53 | if err != nil { 54 | ErrorLog("订阅%v错误,错误信息:%v", channel, err) 55 | return 56 | } 57 | 58 | natChMap.Store(channel, channelCount) 59 | 60 | for i := int32(0); i < channelCount; i++ { 61 | Go2(func() { 62 | defer func() { 63 | _ = sub.Unsubscribe() 64 | _ = sub.Drain() 65 | }() 66 | 67 | for allForStopSignal == 0 { 68 | msg := <-ch 69 | if cb != nil { 70 | result := &NatResult{ 71 | Message: msg.Data, 72 | Reply: msg.Reply, 73 | } 74 | 75 | cb(result) 76 | } 77 | } 78 | }) 79 | } 80 | } 81 | 82 | // 异步模式订阅 83 | func SubscribeAsync(channel string, cb NatCallBack) { 84 | _, err := natConn.Subscribe(channel, func(msg *nats.Msg) { 85 | Go2(func() { 86 | if cb != nil { 87 | result := &NatResult{ 88 | Message: msg.Data, 89 | Reply: msg.Reply, 90 | } 91 | 92 | cb(result) 93 | } 94 | }) 95 | }) 96 | 97 | if err != nil { 98 | ErrorLog("使用异步模式订阅%v错误,错误信息:%v", channel, err) 99 | } 100 | } 101 | 102 | // 队列模式订阅 103 | func SubscribeQueue(channel, queue string, cb NatCallBack) { 104 | _, err := natConn.QueueSubscribe(channel, queue, func(msg *nats.Msg) { 105 | Go2(func() { 106 | if cb != nil { 107 | result := &NatResult{ 108 | Message: msg.Data, 109 | Reply: msg.Reply, 110 | } 111 | 112 | cb(result) 113 | } 114 | }) 115 | }) 116 | 117 | if err != nil { 118 | ErrorLog("使用队列:%v订阅:%v出错,错误信息:%v", queue, channel, err) 119 | } 120 | } 121 | 122 | // 发布消息 123 | func Publish(channel string, data []byte) { 124 | err := natConn.Publish(channel, data) 125 | if err != nil { 126 | ErrorLog("发布消息出错,错误信息:%v", err) 127 | } 128 | 129 | err = natConn.Flush() 130 | if err != nil { 131 | ErrorLog("发布消息后刷新出错,错误信息:%v", err) 132 | } 133 | } 134 | 135 | // 远程rpc调用 136 | func RpcCall(channel string, data []byte, timeout int32) ([]byte, error) { 137 | msg, err := natConn.Request(channel, data, time.Duration(timeout)*time.Second) 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | return msg.Data, nil 143 | } 144 | -------------------------------------------------------------------------------- /func_redis.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | /* 4 | redis包对Redis的连接池进行了部分封装 5 | 对于未封装的方法有两种处理方式: 6 | 1、自行添加至代码并添加相应的注释说明 7 | 2、调用GetConnection方法,然后自己实现逻辑 8 | */ 9 | 10 | import ( 11 | "fmt" 12 | 13 | "github.com/go-redis/redis" 14 | ) 15 | 16 | type redisConfig struct { 17 | Addr string 18 | Pwd string 19 | db int 20 | } 21 | 22 | type Redis struct { 23 | client *redis.Client 24 | conf *redisConfig 25 | } 26 | 27 | func NewRedis(addr, pwd string, db int, poolSize int) *Redis { 28 | redisClient := &Redis{ 29 | client: redis.NewClient(&redis.Options{ 30 | Addr: addr, 31 | Password: pwd, 32 | DB: db, 33 | PoolSize: poolSize, 34 | }), 35 | conf: &redisConfig{ 36 | Addr: addr, 37 | Pwd: pwd, 38 | db: db, 39 | }, 40 | } 41 | 42 | // 验证redis链接成功 43 | pong, err := redisClient.client.Ping().Result() 44 | if err != nil { 45 | ErrorLog("redis连接失败,结果:%v 错误信息:%v", pong, err) 46 | panic(fmt.Errorf("redis连接失败,错误信息:%v", err)) 47 | } else { 48 | InfoLog("redis连接成功") 49 | } 50 | 51 | redisCfg = redisClient 52 | return redisClient 53 | } 54 | 55 | func (r *Redis) GetConnection() *redis.Client { 56 | return r.client 57 | } 58 | 59 | func (r *Redis) HGet(key, field string) string { 60 | result, err := r.client.HGet(key, field).Result() 61 | if err != nil { 62 | return "" 63 | } 64 | 65 | return result 66 | } 67 | 68 | func (r *Redis) HDel(key string, field ...string) int64 { 69 | result, err := r.client.HDel(key, field...).Result() 70 | if err != nil { 71 | return 0 72 | } 73 | 74 | return result 75 | } 76 | 77 | func (r *Redis) HExists(key, field string) bool { 78 | result, err := r.client.HExists(key, field).Result() 79 | if err != nil { 80 | return false 81 | } 82 | 83 | return result 84 | } 85 | 86 | func (r *Redis) HGetAll(key string) map[string]string { 87 | result, err := r.client.HGetAll(key).Result() 88 | if err != nil { 89 | return nil 90 | } 91 | 92 | return result 93 | } 94 | 95 | func (r *Redis) HIncrBy(key, field string, incr int64) int64 { 96 | result, err := r.client.HIncrBy(key, field, incr).Result() 97 | if err != nil { 98 | return 0 99 | } 100 | 101 | return result 102 | } 103 | 104 | func (r *Redis) HIncrByFloat(key, field string, incr float64) float64 { 105 | result, err := r.client.HIncrByFloat(key, field, incr).Result() 106 | if err != nil { 107 | return 0 108 | } 109 | 110 | return result 111 | } 112 | 113 | func (r *Redis) HKeys(key string) []string { 114 | result, err := r.client.HKeys(key).Result() 115 | if err != nil { 116 | return nil 117 | } 118 | 119 | return result 120 | } 121 | 122 | func (r *Redis) HLen(key string) int64 { 123 | result, err := r.client.HLen(key).Result() 124 | if err != nil { 125 | return 0 126 | } 127 | 128 | return result 129 | } 130 | 131 | func (r *Redis) HMGet(key string, field ...string) []interface{} { 132 | result, err := r.client.HMGet(key, field...).Result() 133 | if err != nil { 134 | return nil 135 | } 136 | 137 | return result 138 | } 139 | 140 | func (r *Redis) HMSet(key string, field map[string]interface{}) string { 141 | result, err := r.client.HMSet(key, field).Result() 142 | if err != nil { 143 | return "" 144 | } 145 | 146 | return result 147 | } 148 | 149 | func (r *Redis) HSet(key, field string, value interface{}) bool { 150 | result, err := r.client.HSet(key, field, value).Result() 151 | if err != nil { 152 | return false 153 | } 154 | 155 | return result 156 | } 157 | 158 | func (r *Redis) HSetNX(key string, field string, value interface{}) bool { 159 | result, err := r.client.HSetNX(key, field, value).Result() 160 | if err != nil { 161 | return false 162 | } 163 | 164 | return result 165 | } 166 | 167 | func (r *Redis) HVals(key string) []string { 168 | result, err := r.client.HVals(key).Result() 169 | if err != nil { 170 | return nil 171 | } 172 | 173 | return result 174 | } 175 | -------------------------------------------------------------------------------- /func_ws_client.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | type WebSocketClient struct { 11 | conn *websocket.Conn 12 | stop bool 13 | activeTime int64 14 | sendCh chan []byte 15 | mutex sync.RWMutex 16 | } 17 | 18 | func newWebSocketClient(conn *websocket.Conn) *WebSocketClient { 19 | return &WebSocketClient{ 20 | conn: conn, 21 | stop: false, 22 | activeTime: time.Now().Unix(), 23 | sendCh: make(chan []byte, 1024), 24 | } 25 | } 26 | 27 | func (c *WebSocketClient) GetConn() *websocket.Conn { 28 | c.mutex.RLock() 29 | defer c.mutex.RUnlock() 30 | return c.conn 31 | } 32 | 33 | func (c *WebSocketClient) GetStop() bool { 34 | c.mutex.RLock() 35 | defer c.mutex.RUnlock() 36 | return c.stop 37 | } 38 | 39 | func (c *WebSocketClient) SetStop() { 40 | c.mutex.Lock() 41 | defer c.mutex.Unlock() 42 | c.stop = true 43 | } 44 | 45 | func (c *WebSocketClient) GetActiveTime() int64 { 46 | c.mutex.RLock() 47 | defer c.mutex.RUnlock() 48 | return c.activeTime 49 | } 50 | 51 | func (c *WebSocketClient) SetActiveTime() { 52 | c.mutex.Lock() 53 | defer c.mutex.Unlock() 54 | c.activeTime = time.Now().Unix() 55 | } 56 | 57 | func (c *WebSocketClient) GetReceiveData(headerLen int32, data []byte) (message []byte, exists bool) { 58 | c.mutex.Lock() 59 | defer c.mutex.Unlock() 60 | 61 | if len(data) < int(headerLen) { 62 | return 63 | } 64 | 65 | // 获取头部信息 66 | header := data[:headerLen] 67 | 68 | // 将头部数据转换为内容的长度 69 | contentLength := BytesToInt32(header, true) 70 | 71 | // 判断长度是否满足 72 | if len(data) < int(headerLen+contentLength) { 73 | return 74 | } 75 | 76 | // 提取消息内容 77 | message = data[headerLen : headerLen+contentLength] 78 | 79 | // 存在合理的数据 80 | exists = true 81 | 82 | return 83 | } 84 | 85 | func (c *WebSocketClient) AppendSendQueue(message []byte) { 86 | c.sendCh <- message 87 | } 88 | 89 | func (c *WebSocketClient) start() { 90 | Go(func(Stop chan struct{}) { 91 | defer func() { 92 | _ = c.GetConn().Close() 93 | c.SetStop() 94 | }() 95 | for !c.GetStop() { 96 | _, data, err := c.GetConn().ReadMessage() 97 | if err != nil { 98 | ErrorLog("读取消息错误:%v", err) 99 | break 100 | } 101 | 102 | Go2(func() { 103 | message, exists := c.GetReceiveData(wsReceiveDataHeaderLen, data) 104 | if exists && wsHandlerReceiveFunc != nil { 105 | wsHandlerReceiveFunc(message, c.GetConn().RemoteAddr().String()) 106 | } 107 | }) 108 | } 109 | }) 110 | Go(func(Stop chan struct{}) { 111 | for !c.GetStop() { 112 | select { 113 | case message := <-c.sendCh: 114 | Go2(func() { 115 | err := c.GetConn().WriteMessage(websocket.BinaryMessage, message) 116 | if err != nil { 117 | ErrorLog("向客户端:%v发送数据出错,错误信息:%v", c.GetConn().RemoteAddr().String(), err) 118 | _ = c.GetConn().Close() 119 | c.SetStop() 120 | } 121 | }) 122 | case <-Stop: 123 | return 124 | } 125 | } 126 | }) 127 | } 128 | 129 | func registerWebSocketClient(c *WebSocketClient) { 130 | wsClientMap.Store(c.GetConn().RemoteAddr().String(), c) 131 | } 132 | 133 | func clearExpireWebSocketClient() { 134 | Go(func(Stop chan struct{}) { 135 | for allForStopSignal == 0 { 136 | t := time.NewTicker(5 * time.Second) 137 | <-t.C 138 | removeClient := make([]string, 0) 139 | wsClientMap.Range(func(key, value interface{}) bool { 140 | client := value.(*WebSocketClient) 141 | if client.GetActiveTime()+clientExpireTime <= time.Now().Unix() { 142 | removeClient = append(removeClient, key.(string)) 143 | } 144 | 145 | return true 146 | }) 147 | 148 | // 移除过期的客户端 149 | callBackList := make([]string, 0) 150 | for _, key := range removeClient { 151 | value, exists := wsClientMap.Load(key) 152 | if !exists { 153 | continue 154 | } 155 | 156 | // 再次判断是否过期,防止将要移除时有发生通信的事件 157 | client := value.(*WebSocketClient) 158 | if client.GetActiveTime()+clientExpireTime > time.Now().Unix() { 159 | continue 160 | } 161 | 162 | // 移除过期客户端 163 | client.SetStop() 164 | _ = client.GetConn().Close() 165 | wsClientMap.Delete(key) 166 | callBackList = append(callBackList, key) 167 | } 168 | 169 | if len(callBackList) > 0 && wsClientExpireHandleFunc != nil { 170 | wsClientExpireHandleFunc(callBackList) 171 | } 172 | } 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /func_log.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | type logObj struct { 13 | lv logLv 14 | logInfo string 15 | } 16 | 17 | func logStart() { 18 | goForLog(func(Stop chan struct{}) { 19 | for logForStopSignal == 0 { 20 | select { 21 | case <-Stop: 22 | return 23 | case logItem := <-logCh: 24 | writeLog(logItem) 25 | } 26 | } 27 | }) 28 | } 29 | 30 | func writeLog(log *logObj) { 31 | logMutex.Lock() 32 | defer logMutex.Unlock() 33 | 34 | // 日志级别不存在 35 | _, logLvExists := logLvNameMap[log.lv] 36 | if !logLvExists { 37 | return 38 | } 39 | 40 | // 日志文件未开启 41 | file, fileExists := logFileMap[log.lv] 42 | if !fileExists || file == nil { 43 | return 44 | } 45 | 46 | // 记录日志 47 | _, _ = file.WriteString(log.logInfo) 48 | fmt.Printf("%v", log.logInfo) 49 | } 50 | 51 | func reorganizeLog(nowTime time.Time) { 52 | logMutex.Lock() 53 | defer logMutex.Unlock() 54 | 55 | for _, file := range logFileMap { 56 | // 优先获取文件名 57 | fileName := file.Name() 58 | 59 | // 将文件流关闭 60 | _ = file.Close() 61 | file = nil 62 | 63 | // 重命名文件 64 | nameList := strings.Split(fileName, ".") 65 | newFileName := fmt.Sprintf("%v_%v.log", nameList[0], ToDateTimeString(nowTime.Add(-1*time.Hour))) 66 | err := os.Rename(fileName, newFileName) 67 | if err != nil { 68 | ErrorLog("重命名文件:%v失败,错误信息:%v", fileName, err) 69 | } 70 | } 71 | 72 | // 重新开启日志文件流 73 | for logLv, logName := range logLvNameMap { 74 | if logLv < logNowLv { 75 | continue 76 | } 77 | // 得到最终的文件绝对路径 78 | fileName := fmt.Sprintf("%v.log", logName) 79 | fileAbsolutePath := filepath.Join(logDirPath, fileName) 80 | 81 | // 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。) 82 | f, err := os.OpenFile(fileAbsolutePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary) 83 | if err != nil { 84 | fmt.Printf("打开%v日志文件错误,错误信息:%v", fileName, err) 85 | } 86 | 87 | // 将文件流保存 88 | logFileMap[logLv] = f 89 | } 90 | } 91 | 92 | func StartLog(dirPatch string, lv logLv) { 93 | logOnce.Do(func() { 94 | logNowLv = lv 95 | logDirPath = dirPatch 96 | // 文件夹路径不存在就创建 97 | if !IsDirExists(dirPatch) { 98 | if err := os.MkdirAll(dirPatch, os.ModePerm|os.ModeTemporary); err != nil { 99 | fmt.Printf("创建日志文件夹错误,错误信息:%v", err) 100 | } 101 | } 102 | 103 | for logLv, logName := range logLvNameMap { 104 | // 低于当前等级的日志不打开文件 105 | if logLv < lv { 106 | continue 107 | } 108 | 109 | // 得到最终的文件绝对路径 110 | fileName := fmt.Sprintf("%v.log", logName) 111 | fileAbsolutePath := filepath.Join(dirPatch, fileName) 112 | 113 | // 打开文件(如果文件存在就以写模式打开,并追加写入;如果文件不存在就创建,然后以写模式打开。) 114 | f, err := os.OpenFile(fileAbsolutePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm|os.ModeTemporary) 115 | if err != nil { 116 | fmt.Printf("打开%v日志文件错误,错误信息:%v", fileName, err) 117 | } 118 | 119 | // 将文件流保存 120 | logFileMap[logLv] = f 121 | } 122 | }) 123 | } 124 | 125 | func Stack() { 126 | buf := make([]byte, 1<<12) 127 | log(Error, string(buf[:runtime.Stack(buf, false)])) 128 | } 129 | 130 | func SimpleTack() string { 131 | _, file, line, _ := runtime.Caller(2) 132 | i := strings.LastIndex(file, "/") + 1 133 | i = strings.LastIndex((string)(([]byte(file))[:i-1]), "/") + 1 134 | 135 | return fmt.Sprintf("%s:%d", (string)(([]byte(file))[i:]), line) 136 | } 137 | 138 | func log(lv logLv, v ...interface{}) { 139 | if lv < logNowLv { 140 | return 141 | } 142 | 143 | // 判断日志文件是否存在 144 | lvName, exists := logLvNameMap[lv] 145 | if !exists { 146 | return 147 | } 148 | 149 | // 记录日志 150 | _, file, line, ok := runtime.Caller(3) 151 | if !ok { 152 | return 153 | } 154 | 155 | i := strings.LastIndex(file, "/") + 1 156 | logContent := fmt.Sprintf("[%s][%s][%s:%d]:", lvName, time.Now().Format("2006-01-02 15:04:05"), (string)(([]byte(file))[i:]), line) 157 | if len(v) > 1 { 158 | logContent += fmt.Sprintf(v[0].(string), v[1:]...) 159 | } else { 160 | logContent += fmt.Sprint(v[0]) 161 | } 162 | logContent += GetNewLineString() 163 | 164 | logCh <- &logObj{ 165 | lv: lv, 166 | logInfo: logContent, 167 | } 168 | } 169 | 170 | func DebugLog(v ...interface{}) { 171 | debugLog(v...) 172 | } 173 | 174 | func InfoLog(v ...interface{}) { 175 | infoLog(v...) 176 | } 177 | 178 | func WarnLog(v ...interface{}) { 179 | warnLog(v...) 180 | } 181 | 182 | func ErrorLog(v ...interface{}) { 183 | errorLog(v...) 184 | } 185 | 186 | func FatalLog(v ...interface{}) { 187 | fatalLog(v...) 188 | } 189 | 190 | func debugLog(v ...interface{}) { 191 | log(Debug, v...) 192 | } 193 | 194 | func infoLog(v ...interface{}) { 195 | log(Info, v...) 196 | } 197 | 198 | func warnLog(v ...interface{}) { 199 | log(Warn, v...) 200 | } 201 | 202 | func errorLog(v ...interface{}) { 203 | log(Error, v...) 204 | } 205 | 206 | func fatalLog(v ...interface{}) { 207 | log(Fatal, v...) 208 | } 209 | -------------------------------------------------------------------------------- /starGo_test.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func TestInfoLog(t *testing.T) { 14 | StartLog("log", Debug) 15 | 16 | InfoLog("qwe") 17 | go func() { 18 | time.Sleep(61 * time.Second) 19 | stopChanForLog <- struct{}{} 20 | 21 | for i := int32(0); i < goCount; i++ { 22 | stopChanForGo <- struct{}{} 23 | } 24 | //atomic.CompareAndSwapInt32(&allForStopSignal, 0, 1) 25 | }() 26 | go func() { 27 | for { 28 | InfoLog("输出当前时间:%v", time.Now()) 29 | InfoLog("输出当前时间:%v", time.Now()) 30 | WarnLog("输出当前时间:%v", time.Now()) 31 | ErrorLog("输出当前时间:%v", time.Now()) 32 | FatalLog("输出当前时间:%v", time.Now()) 33 | time.Sleep(1 * time.Second) 34 | } 35 | }() 36 | WaitForSystemExit() 37 | } 38 | 39 | func TestCsv_UnMarshalFile(t *testing.T) { 40 | StartLog("log", Debug) 41 | type abc struct { 42 | Id int32 43 | Name string 44 | Year string 45 | } 46 | inf := make([]abc, 0) 47 | cs := NewCsvReader() 48 | cs.UnMarshalFile("csv/test.csv", &inf) 49 | InfoLog(inf) 50 | WaitForSystemExit() 51 | } 52 | 53 | func TestNatPublish(t *testing.T) { 54 | StartNatConn("127.0.0.1:4222", "testNat") 55 | StartLog("log", Debug) 56 | 57 | //SubscribeQueue("hello", "h1", func(message []byte) { 58 | // InfoLog("这是队列模式h1.1收到的消息,消息:%v", string(message)) 59 | // //fmt.Println(fmt.Sprintf("这是队列模式h1.1收到的消息,消息:%v", string(message))) 60 | //}) 61 | // 62 | //SubscribeQueue("hello", "h1", func(message []byte) { 63 | // InfoLog("这是队列模式h1.2收到的消息,消息:%v", string(message)) 64 | // //fmt.Println(fmt.Sprintf("这是队列模式h1.2收到的消息,消息:%v", string(message))) 65 | //}) 66 | // 67 | //SubscribeQueue("hello", "h1", func(message []byte) { 68 | // InfoLog("这是队列模式h1.3收到的消息,消息:%v", string(message)) 69 | // //fmt.Println(fmt.Sprintf("这是队列模式h1.3收到的消息,消息:%v", string(message))) 70 | //}) 71 | // 72 | //SubscribeQueue("hello", "h2", func(message []byte) { 73 | // //fmt.Println(fmt.Sprintf("这是队列模式h2.1收到的消息,消息:%v", string(message))) 74 | // InfoLog("这是队列模式h2.1收到的消息,消息:%v", string(message)) 75 | //}) 76 | // 77 | //SubscribeQueue("hello", "h2", func(message []byte) { 78 | // //fmt.Println(fmt.Sprintf("这是队列模式h2.2收到的消息,消息:%v", string(message))) 79 | // InfoLog("这是队列模式h2.2收到的消息,消息:%v", string(message)) 80 | //}) 81 | // 82 | //SubscribeQueue("hello", "h2", func(message []byte) { 83 | // //fmt.Println(fmt.Sprintf("这是队列模式h2.3收到的消息,消息:%v", string(message))) 84 | // InfoLog("这是队列模式h2.3收到的消息,消息:%v", string(message)) 85 | //}) 86 | // 87 | //SubscribeAsync("hello", func(message []byte) { 88 | // //fmt.Println(fmt.Sprintf("这是异步模式收到的消息,消息:%v", string(message))) 89 | // InfoLog("这是异步模式收到的消息,消息:%v", string(message)) 90 | //}) 91 | //SubscribeChannel("hello", 1, func(message []byte) { 92 | // //fmt.Println(fmt.Sprintf("这是管道模式收到的消息,消息:%v", string(message))) 93 | // InfoLog("这是管道模式收到的消息,消息:%v", string(message)) 94 | //}) 95 | 96 | //Publish("hello", []byte("你好呀")) 97 | 98 | // Replies 99 | SubscribeAsync("help", func(messag *NatResult) { 100 | //Publish("help", []byte("I can help!")) 101 | Publish(messag.Reply, []byte("I can help!")) 102 | }) 103 | 104 | // Requests 105 | //msg, err := RpcCall("help", []byte("help me"), 10) 106 | //DebugLog(string(msg), err) 107 | Publish("help", []byte("你好呀")) 108 | 109 | go func() { 110 | time.Sleep(10 * time.Second) 111 | stopChanForLog <- struct{}{} 112 | 113 | //stopChanForGo <- struct{}{} 114 | 115 | for i := int32(0); i < goCount; i++ { 116 | fmt.Println(i) 117 | stopChanForGo <- struct{}{} 118 | } 119 | }() 120 | WaitForSystemExit() 121 | } 122 | 123 | func TestChannel(t *testing.T) { 124 | var wg sync.WaitGroup 125 | c := make(chan struct{}) 126 | fmt.Println(1) 127 | wg.Add(1) 128 | go func() { 129 | <-c 130 | wg.Done() 131 | fmt.Println(2) 132 | }() 133 | 134 | go func() { 135 | time.Sleep(3 * time.Second) 136 | close(c) 137 | }() 138 | fmt.Println(3) 139 | wg.Wait() 140 | } 141 | 142 | func TestWebSocketClient(t *testing.T) { 143 | StartLog("log", Debug) 144 | 145 | err := StartTcpServer("127.0.0.1:9999", nil, nil, 4) 146 | if err != nil { 147 | ErrorLog(err) 148 | return 149 | } 150 | 151 | //Start() 152 | 153 | //go func() { 154 | // time.Sleep(5 * time.Second) 155 | // fmt.Println("开始关闭通道") 156 | // systemExit() 157 | //}() 158 | 159 | WaitForSystemExit() 160 | } 161 | 162 | func TestGo(t *testing.T) { 163 | gin.SetMode(gin.ReleaseMode) 164 | web := NewWebServer("0.0.0.0:9020", true) 165 | web.RegisterRequestHandleFunc(GET, "/hello", func(context *gin.Context) { 166 | //firstname := c.DefaultQuery("firstname", "Guest") 167 | //lastname := c.Query("lastname") // 是 c.Request.URL.Query().Get("lastname") 的简写 168 | //context.String(http.StatusOK, fmt.Sprintln(gin.H{"data": "默认请求"})) 169 | context.String(http.StatusOK, "hello,你好呀") 170 | }) 171 | web.StartWebServer() 172 | WaitForSystemExit() 173 | //testing.Benchmark() 174 | } 175 | -------------------------------------------------------------------------------- /func_tcp_client.go: -------------------------------------------------------------------------------- 1 | package starGo 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type ClientCallBack func(message []byte, addr string) 11 | 12 | type ClientExpireCallBack func(addr []string) 13 | 14 | type Client struct { 15 | conn net.Conn 16 | stop bool 17 | activeTime int64 18 | receiveQueue []byte 19 | sendCh chan []byte 20 | mutex sync.RWMutex 21 | } 22 | 23 | func newTcpClient(conn net.Conn) *Client { 24 | return &Client{ 25 | conn: conn, 26 | stop: false, 27 | activeTime: time.Now().Unix(), 28 | receiveQueue: make([]byte, 0), 29 | sendCh: make(chan []byte, 1024), 30 | } 31 | } 32 | 33 | func (c *Client) GetConn() net.Conn { 34 | c.mutex.RLock() 35 | defer c.mutex.RUnlock() 36 | return c.conn 37 | } 38 | 39 | func (c *Client) GetStop() bool { 40 | c.mutex.RLock() 41 | defer c.mutex.RUnlock() 42 | return c.stop 43 | } 44 | 45 | func (c *Client) SetStop() { 46 | c.mutex.Lock() 47 | defer c.mutex.Unlock() 48 | c.stop = true 49 | } 50 | 51 | func (c *Client) GetActiveTime() int64 { 52 | c.mutex.RLock() 53 | defer c.mutex.RUnlock() 54 | return c.activeTime 55 | } 56 | 57 | func (c *Client) SetActiveTime() { 58 | c.mutex.Lock() 59 | defer c.mutex.Unlock() 60 | c.activeTime = time.Now().Unix() 61 | } 62 | 63 | func (c *Client) GetReceiveData(headerLen int32) (message []byte, exists bool) { 64 | c.mutex.Lock() 65 | defer c.mutex.Unlock() 66 | 67 | if len(c.receiveQueue) < int(headerLen) { 68 | return 69 | } 70 | 71 | // 获取头部信息 72 | header := c.receiveQueue[:headerLen] 73 | 74 | // 将头部数据转换为内容的长度 75 | contentLength := BytesToInt32(header, true) 76 | 77 | // 判断长度是否满足 78 | if len(c.receiveQueue) < int(headerLen+contentLength) { 79 | return 80 | } 81 | 82 | // 提取消息内容 83 | message = c.receiveQueue[headerLen : headerLen+contentLength] 84 | 85 | // 将对应的数据截断,以得到新的内容 86 | c.receiveQueue = c.receiveQueue[headerLen+contentLength:] 87 | 88 | // 存在合理的数据 89 | exists = true 90 | 91 | return 92 | } 93 | 94 | func (c *Client) AppendReceiveQueue(message []byte) { 95 | c.mutex.Lock() 96 | defer c.mutex.Unlock() 97 | c.receiveQueue = append(c.receiveQueue, message...) 98 | } 99 | 100 | func (c *Client) AppendSendQueue(message []byte) { 101 | c.sendCh <- message 102 | } 103 | 104 | func (c *Client) start() { 105 | Go(func(Stop chan struct{}) { 106 | defer func() { 107 | _ = c.GetConn().Close() 108 | c.SetStop() 109 | }() 110 | for !c.GetStop() { 111 | readBytes := make([]byte, 1024) 112 | n, err := c.GetConn().Read(readBytes) 113 | if err != nil { 114 | if err != io.EOF { 115 | ErrorLog("读取消息错误:%s,本次读取的字节数为:%d", err, n) 116 | } 117 | break 118 | } 119 | c.AppendReceiveQueue(readBytes[:n]) 120 | 121 | Go2(func() { 122 | message, exists := c.GetReceiveData(tcpReceiveDataHeaderLen) 123 | if exists && tcpHandlerReceiveFunc != nil { 124 | tcpHandlerReceiveFunc(message, c.GetConn().RemoteAddr().String()) 125 | } 126 | }) 127 | } 128 | }) 129 | Go(func(Stop chan struct{}) { 130 | for !c.GetStop() { 131 | select { 132 | case message := <-c.sendCh: 133 | Go2(func() { 134 | header := Int32ToBytes(int32(len(message)), true) 135 | header = append(header, message...) 136 | _, err := c.GetConn().Write(header) 137 | if err != nil { 138 | ErrorLog("向客户端:%v发送数据出错,错误信息:%v", c.GetConn().RemoteAddr().String(), err) 139 | _ = c.GetConn().Close() 140 | c.SetStop() 141 | } 142 | }) 143 | case <-Stop: 144 | return 145 | } 146 | } 147 | }) 148 | } 149 | 150 | func registerTcpClient(c *Client) { 151 | tcpClientMap.Store(c.GetConn().RemoteAddr().String(), c) 152 | } 153 | 154 | func clearExpireTcpClient() { 155 | Go(func(Stop chan struct{}) { 156 | t := time.NewTicker(5 * time.Second) 157 | for allForStopSignal == 0 { 158 | <-t.C 159 | removeClient := make([]string, 0) 160 | tcpClientMap.Range(func(key, value interface{}) bool { 161 | client := value.(*Client) 162 | if client.GetActiveTime()+clientExpireTime <= time.Now().Unix() { 163 | removeClient = append(removeClient, key.(string)) 164 | } 165 | 166 | return true 167 | }) 168 | 169 | // 移除过期的客户端 170 | callBackList := make([]string, 0) 171 | for _, key := range removeClient { 172 | value, exists := tcpClientMap.Load(key) 173 | if !exists { 174 | continue 175 | } 176 | 177 | // 再次判断是否过期,防止将要移除时有发生通信的事件 178 | client := value.(*Client) 179 | if client.GetActiveTime()+clientExpireTime > time.Now().Unix() { 180 | continue 181 | } 182 | 183 | // 移除过期客户端 184 | client.SetStop() 185 | _ = client.GetConn().Close() 186 | tcpClientMap.Delete(key) 187 | callBackList = append(callBackList, key) 188 | } 189 | 190 | if len(callBackList) > 0 { 191 | InfoLog("移除过期客户端连接:%v", callBackList) 192 | if tcpClientExpireHandleFunc != nil { 193 | tcpClientExpireHandleFunc(callBackList) 194 | } 195 | } 196 | } 197 | }) 198 | } 199 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= 4 | cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= 7 | github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= 8 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 9 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 10 | github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= 11 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 12 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 13 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= 14 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 15 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 16 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 17 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 18 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 19 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= 24 | github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= 25 | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= 26 | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= 27 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 28 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= 29 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 30 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 31 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 32 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 33 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 34 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 35 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 36 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 37 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 38 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 39 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 40 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 41 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 42 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 43 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 44 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 45 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 46 | github.com/go-redis/redis v6.15.6+incompatible h1:H9evprGPLI8+ci7fxQx6WNZHJSb7be8FqJQRhdQZ5Sg= 47 | github.com/go-redis/redis v6.15.6+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 48 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 49 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 50 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 51 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 52 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 53 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 54 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 55 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 56 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 57 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 58 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 59 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 60 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 61 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 62 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 63 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 64 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 65 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 66 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 67 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 68 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 69 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 70 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 71 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 72 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 73 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 74 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 75 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 76 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 77 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 78 | github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= 79 | github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= 80 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 81 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 82 | github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= 83 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 84 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 85 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 86 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 87 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 88 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 89 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 90 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 91 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 92 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 93 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 94 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 95 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 96 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 97 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 98 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 99 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 100 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 101 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 102 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 103 | github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= 104 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 105 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 106 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 107 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 108 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 109 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 110 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 111 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 112 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 113 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 114 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 115 | github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo= 116 | github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= 117 | github.com/nats-io/nats-server/v2 v2.1.0 h1:Yi0+ZhRPtPAGeIxFn5erIeJIV9wXA+JznfSxK621Fbk= 118 | github.com/nats-io/nats-server/v2 v2.1.0/go.mod h1:r5y0WgCag0dTj/qiHkHrXAcKQ/f5GMOZaEGdoxxnJ4I= 119 | github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ= 120 | github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= 121 | github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= 122 | github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4= 123 | github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= 124 | github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= 125 | github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= 126 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 127 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 128 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 129 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 130 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 131 | github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= 132 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 133 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 134 | github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 135 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 136 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 137 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 138 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 139 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 140 | github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= 141 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 142 | github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 143 | github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 144 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 145 | github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 146 | github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 147 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 148 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 149 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 150 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 151 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 152 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 153 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 154 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 155 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 156 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 157 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 158 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 159 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 160 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 161 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 162 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 163 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 164 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 165 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 166 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 167 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= 168 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 169 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 170 | github.com/zhnxin/csvreader v0.0.0-20190606083136-f9e31b1bce61 h1:IlIZkbWOgd/gc3AAy1Ijg9wR/iof7sYvvkbsbXbkiS4= 171 | github.com/zhnxin/csvreader v0.0.0-20190606083136-f9e31b1bce61/go.mod h1:wLx3mI1DsLmRffH+Cyh8ADWCjgrHoEc21PzbtJfOCNM= 172 | go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 173 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= 174 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 175 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 176 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 177 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 178 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 179 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 180 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 181 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 182 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 183 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 184 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 185 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 186 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 187 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 188 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 189 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 190 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 191 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 192 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 193 | golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 194 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 195 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 196 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 197 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 198 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 199 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 200 | golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= 201 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 202 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 203 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 204 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 205 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 206 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 207 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 208 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 209 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 210 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 211 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 212 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 213 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 214 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 215 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 216 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 217 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 218 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 221 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 222 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 223 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 224 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 225 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 226 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 227 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 228 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 229 | golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= 230 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 231 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 232 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 233 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 234 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 235 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 236 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 237 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 238 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 239 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 240 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 241 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 242 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 243 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 244 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 245 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 246 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 247 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 248 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 249 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 250 | google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 251 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 252 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 253 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 254 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 255 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 256 | google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 257 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 258 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 259 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 260 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 261 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 262 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 263 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 264 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 265 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 266 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 267 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 268 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 269 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 270 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 271 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 272 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 273 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 274 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 275 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 276 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 277 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 278 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 279 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 280 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 281 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 282 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 283 | --------------------------------------------------------------------------------