diff --git a/server/network.go b/server/network.go index 7fadec24..6351ef1c 100644 --- a/server/network.go +++ b/server/network.go @@ -16,6 +16,22 @@ import ( "time" ) +// Network 服务器运行的网络模式 +// - 根据不同的网络模式,服务器将会产生不同的行为,该类型将在服务器创建时候指定 +// +// 服务器支持的网络模式如下: +// - NetworkNone 该模式下不监听任何网络端口,仅开启消息队列,适用于纯粹的跨服服务器等情况 +// - NetworkTcp 该模式下将会监听 TCP 协议的所有地址,包括 IPv4 和 IPv6 +// - NetworkTcp4 该模式下将会监听 TCP 协议的 IPv4 地址 +// - NetworkTcp6 该模式下将会监听 TCP 协议的 IPv6 地址 +// - NetworkUdp 该模式下将会监听 UDP 协议的所有地址,包括 IPv4 和 IPv6 +// - NetworkUdp4 该模式下将会监听 UDP 协议的 IPv4 地址 +// - NetworkUdp6 该模式下将会监听 UDP 协议的 IPv6 地址 +// - NetworkUnix 该模式下将会监听 Unix 协议的地址 +// - NetworkHttp 该模式下将会监听 HTTP 协议的地址 +// - NetworkWebsocket 该模式下将会监听 Websocket 协议的地址 +// - NetworkKcp 该模式下将会监听 KCP 协议的地址 +// - NetworkGRPC 该模式下将会监听 GRPC 协议的地址 type Network string const ( @@ -321,7 +337,16 @@ func (n Network) websocketMode(state chan<- error, srv *Server) { }((&listener{srv: srv, Listener: l, state: state}).init(), mux) } -// IsSocket 返回当前服务器的网络模式是否为 Socket 模式 +// IsSocket 返回当前服务器的网络模式是否为 Socket 模式,目前为止仅有如下几种模式为 Socket 模式: +// - NetworkTcp +// - NetworkTcp4 +// - NetworkTcp6 +// - NetworkUdp +// - NetworkUdp4 +// - NetworkUdp6 +// - NetworkUnix +// - NetworkKcp +// - NetworkWebsocket func (n Network) IsSocket() bool { return collection.KeyInMap(socketNetworks, n) } diff --git a/server/options_example_test.go b/server/options_example_test.go new file mode 100644 index 00000000..c9166033 --- /dev/null +++ b/server/options_example_test.go @@ -0,0 +1,51 @@ +package server_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/server" + "github.com/kercylan98/minotaur/utils/times" + "time" +) + +// 服务器在启动时将阻塞 1s,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 +// - 该示例中,将在收到慢消息时关闭服务器 +func ExampleWithLowMessageDuration() { + srv := server.New(server.NetworkNone, + server.WithLowMessageDuration(time.Second), + ) + srv.RegStartFinishEvent(func(srv *server.Server) { + time.Sleep(time.Second) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // 1 +} + +// 服务器在启动时将发布一条阻塞 1s 的异步消息,模拟了慢消息的过程,这时候如果通过 RegMessageLowExecEvent 函数注册过慢消息事件,将会收到该事件的消息 +// - 该示例中,将在收到慢消息时关闭服务器 +func ExampleWithAsyncLowMessageDuration() { + srv := server.New(server.NetworkNone, + server.WithAsyncLowMessageDuration(time.Second), + ) + srv.RegStartFinishEvent(func(srv *server.Server) { + srv.PushAsyncMessage(func() error { + time.Sleep(time.Second) + return nil + }, nil) + }) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + srv.Shutdown() + fmt.Println(times.GetSecond(cost)) + }) + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // 1 +} diff --git a/server/options_test.go b/server/options_test.go new file mode 100644 index 00000000..aea3e50f --- /dev/null +++ b/server/options_test.go @@ -0,0 +1,109 @@ +package server_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/server" + "github.com/kercylan98/minotaur/utils/random" + "testing" + "time" +) + +func TestWithLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{ + {name: "TestWithLowMessageDuration", duration: server.DefaultLowMessageDuration}, + {name: "TestWithLowMessageDuration_Zero", duration: 0}, + {name: "TestWithLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, + server.WithLowMessageDuration(c.duration), + ) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + time.Sleep(server.DefaultLowMessageDuration) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = "addr" + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} + +func TestWithAsyncLowMessageDuration(t *testing.T) { + var cases = []struct { + name string + duration time.Duration + }{ + {name: "TestWithAsyncLowMessageDuration", duration: time.Millisecond * 100}, + {name: "TestWithAsyncLowMessageDuration_Zero", duration: 0}, + {name: "TestWithAsyncLowMessageDuration_Negative", duration: -server.DefaultAsyncLowMessageDuration}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + networks := server.GetNetworks() + for i := 0; i < len(networks); i++ { + low := false + network := networks[i] + srv := server.New(network, + server.WithAsyncLowMessageDuration(c.duration), + ) + srv.RegMessageLowExecEvent(func(srv *server.Server, message *server.Message, cost time.Duration) { + low = true + srv.Shutdown() + }) + srv.RegStartFinishEvent(func(srv *server.Server) { + if c.duration <= 0 { + srv.Shutdown() + return + } + srv.PushAsyncMessage(func() error { + time.Sleep(c.duration) + return nil + }, nil) + }) + var lis string + switch network { + case server.NetworkNone, server.NetworkUnix: + lis = fmt.Sprintf("%s%d", "addr", random.Int(0, 9999)) + default: + lis = fmt.Sprintf(":%d", random.UsablePort()) + } + if err := srv.Run(lis); err != nil { + t.Fatalf("%s run error: %s", network, err) + } + if !low && c.duration > 0 { + t.Fatalf("%s low message not exec", network) + } + } + }) + } +} diff --git a/server/server.go b/server/server.go index ea537204..19234862 100644 --- a/server/server.go +++ b/server/server.go @@ -149,7 +149,7 @@ func (srv *Server) Run(addr string) (err error) { return nil } -// IsSocket 是否是 Socket 模式 +// IsSocket 通过执行 Network.IsSocket 函数检查该服务器是否是 Socket 模式 func (srv *Server) IsSocket() bool { return srv.network.IsSocket() } diff --git a/server/server_example_test.go b/server/server_example_test.go index a7c2cb21..acc679c4 100644 --- a/server/server_example_test.go +++ b/server/server_example_test.go @@ -1,30 +1,53 @@ package server_test import ( + "fmt" "github.com/kercylan98/minotaur/server" "time" ) +// 该案例将创建一个简单的 WebSocket 服务器,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 +// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 +// +// 该案例的输出结果为 true func ExampleNew() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) - } + fmt.Println(srv != nil) + // Output: + // true +} +// 该案例将创建两个不同类型的服务器,其中 WebSocket 是一个 Socket 服务器,而 Http 是一个非 Socket 服务器 +// +// 可知案例输出结果为: +// - true +// - false +func ExampleServer_IsSocket() { + srv1 := server.New(server.NetworkWebsocket) + fmt.Println(srv1.IsSocket()) + srv2 := server.New(server.NetworkHttp) + fmt.Println(srv2.IsSocket()) // Output: + // true + // false } +// 该案例将创建一个简单的 WebSocket 服务器并启动监听 `:9999/` 作为 WebSocket 监听地址,如果需要更多的服务器类型可参考 [` Network `](#struct_Network) 部分 +// - 当服务器启动失败后,将会返回错误信息并触发 panic +// - server.WithLimitLife(time.Millisecond) 通常不是在正常开发应该使用的,在这里只是为了让服务器在启动完成后的 1 毫秒后自动关闭 func ExampleServer_Run() { srv := server.New(server.NetworkWebsocket, server.WithLimitLife(time.Millisecond)) - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) if err := srv.Run(":9999"); err != nil { panic(err) } + // Output: +} +// RunNone 函数并没有特殊的意义,该函数内部调用了 `srv.Run("")` 函数,仅是一个语法糖,用来表示服务器不需要监听任何地址 +func ExampleServer_RunNone() { + srv := server.New(server.NetworkNone) + if err := srv.RunNone(); err != nil { + panic(err) + } // Output: } diff --git a/server/server_test.go b/server/server_test.go index 7e8fe1f0..074f3d56 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1,81 +1,101 @@ package server_test import ( - "fmt" "github.com/kercylan98/minotaur/server" - "github.com/kercylan98/minotaur/server/client" - "github.com/kercylan98/minotaur/utils/times" + "github.com/kercylan98/minotaur/utils/super" + "runtime/debug" "testing" "time" ) +// 该单元测试用于测试以不同的基本参数创建服务器是否存在异常 func TestNew(t *testing.T) { - srv := server.New(server.NetworkWebsocket, server.WithPProf()) - srv.RegStartBeforeEvent(func(srv *server.Server) { - fmt.Println("启动前") - }) - srv.RegStartFinishEvent(func(srv *server.Server) { - fmt.Println("启动完成") - }) - srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { - fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) - }) + var cases = []struct { + name string + network server.Network + addr string + shouldPanic bool + }{ + {name: "TestNew_Unknown", addr: "", network: "Unknown", shouldPanic: true}, + {name: "TestNew_None", addr: "", network: server.NetworkNone, shouldPanic: false}, + {name: "TestNew_None_Addr", addr: "addr", network: server.NetworkNone, shouldPanic: false}, + {name: "TestNew_Tcp_AddrEmpty", addr: "", network: server.NetworkTcp, shouldPanic: true}, + {name: "TestNew_Tcp_AddrIllegal", addr: "addr", network: server.NetworkTcp, shouldPanic: true}, + {name: "TestNew_Tcp_Addr", addr: ":9999", network: server.NetworkTcp, shouldPanic: false}, + {name: "TestNew_Tcp4_AddrEmpty", addr: "", network: server.NetworkTcp4, shouldPanic: true}, + {name: "TestNew_Tcp4_AddrIllegal", addr: "addr", network: server.NetworkTcp4, shouldPanic: true}, + {name: "TestNew_Tcp4_Addr", addr: ":9999", network: server.NetworkTcp4, shouldPanic: false}, + {name: "TestNew_Tcp6_AddrEmpty", addr: "", network: server.NetworkTcp6, shouldPanic: true}, + {name: "TestNew_Tcp6_AddrIllegal", addr: "addr", network: server.NetworkTcp6, shouldPanic: true}, + {name: "TestNew_Tcp6_Addr", addr: ":9999", network: server.NetworkTcp6, shouldPanic: false}, + {name: "TestNew_Udp_AddrEmpty", addr: "", network: server.NetworkUdp, shouldPanic: true}, + {name: "TestNew_Udp_AddrIllegal", addr: "addr", network: server.NetworkUdp, shouldPanic: true}, + {name: "TestNew_Udp_Addr", addr: ":9999", network: server.NetworkUdp, shouldPanic: false}, + {name: "TestNew_Udp4_AddrEmpty", addr: "", network: server.NetworkUdp4, shouldPanic: true}, + {name: "TestNew_Udp4_AddrIllegal", addr: "addr", network: server.NetworkUdp4, shouldPanic: true}, + {name: "TestNew_Udp4_Addr", addr: ":9999", network: server.NetworkUdp4, shouldPanic: false}, + {name: "TestNew_Udp6_AddrEmpty", addr: "", network: server.NetworkUdp6, shouldPanic: true}, + {name: "TestNew_Udp6_AddrIllegal", addr: "addr", network: server.NetworkUdp6, shouldPanic: true}, + {name: "TestNew_Udp6_Addr", addr: ":9999", network: server.NetworkUdp6, shouldPanic: false}, + {name: "TestNew_Unix_AddrEmpty", addr: "", network: server.NetworkUnix, shouldPanic: true}, + {name: "TestNew_Unix_AddrIllegal", addr: "addr", network: server.NetworkUnix, shouldPanic: true}, + {name: "TestNew_Unix_Addr", addr: "addr", network: server.NetworkUnix, shouldPanic: false}, + {name: "TestNew_Websocket_AddrEmpty", addr: "", network: server.NetworkWebsocket, shouldPanic: true}, + {name: "TestNew_Websocket_AddrIllegal", addr: "addr", network: server.NetworkWebsocket, shouldPanic: true}, + {name: "TestNew_Websocket_Addr", addr: ":9999/ws", network: server.NetworkWebsocket, shouldPanic: false}, + {name: "TestNew_Http_AddrEmpty", addr: "", network: server.NetworkHttp, shouldPanic: true}, + {name: "TestNew_Http_AddrIllegal", addr: "addr", network: server.NetworkHttp, shouldPanic: true}, + {name: "TestNew_Http_Addr", addr: ":9999", network: server.NetworkHttp, shouldPanic: false}, + {name: "TestNew_Kcp_AddrEmpty", addr: "", network: server.NetworkKcp, shouldPanic: true}, + {name: "TestNew_Kcp_AddrIllegal", addr: "addr", network: server.NetworkKcp, shouldPanic: true}, + {name: "TestNew_Kcp_Addr", addr: ":9999", network: server.NetworkKcp, shouldPanic: false}, + {name: "TestNew_GRPC_AddrEmpty", addr: "", network: server.NetworkGRPC, shouldPanic: true}, + {name: "TestNew_GRPC_AddrIllegal", addr: "addr", network: server.NetworkGRPC, shouldPanic: true}, + {name: "TestNew_GRPC_Addr", addr: ":9999", network: server.NetworkGRPC, shouldPanic: false}, + } - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + defer func() { + if err := super.RecoverTransform(recover()); err != nil && !c.shouldPanic { + debug.PrintStack() + t.Fatal("not should panic, err:", err) + } + }() + if err := server.New(c.network, server.WithLimitLife(time.Millisecond*10)).Run(""); err != nil { + panic(err) + } + }) } } -func TestNew2(t *testing.T) { - srv := server.New(server.NetworkWebsocket, server.WithPProf()) - srv.RegStartBeforeEvent(func(srv *server.Server) { - fmt.Println("启动前") - }) - srv.RegStartFinishEvent(func(srv *server.Server) { - fmt.Println("启动完成") - }) - srv.RegConnectionClosedEvent(func(srv *server.Server, conn *server.Conn, err any) { - fmt.Println("关闭", conn.GetID(), err, "IncrCount", srv.GetOnlineCount()) - }) - - srv.RegConnectionReceivePacketEvent(func(srv *server.Server, conn *server.Conn, packet []byte) { - conn.Write(packet) - }) - if err := srv.Run(":9999"); err != nil { - panic(err) +// 这个测试检查了各个类型的服务器是否为 Socket 模式。如需查看为 Socket 模式的网络类型,请参考 [` Network.IsSocket` ](#struct_Network_IsSocket) +func TestServer_IsSocket(t *testing.T) { + var cases = []struct { + name string + network server.Network + expect bool + }{ + {name: "TestServer_IsSocket_None", network: server.NetworkNone, expect: false}, + {name: "TestServer_IsSocket_Tcp", network: server.NetworkTcp, expect: true}, + {name: "TestServer_IsSocket_Tcp4", network: server.NetworkTcp4, expect: true}, + {name: "TestServer_IsSocket_Tcp6", network: server.NetworkTcp6, expect: true}, + {name: "TestServer_IsSocket_Udp", network: server.NetworkUdp, expect: true}, + {name: "TestServer_IsSocket_Udp4", network: server.NetworkUdp4, expect: true}, + {name: "TestServer_IsSocket_Udp6", network: server.NetworkUdp6, expect: true}, + {name: "TestServer_IsSocket_Unix", network: server.NetworkUnix, expect: true}, + {name: "TestServer_IsSocket_Http", network: server.NetworkHttp, expect: false}, + {name: "TestServer_IsSocket_Websocket", network: server.NetworkWebsocket, expect: true}, + {name: "TestServer_IsSocket_Kcp", network: server.NetworkKcp, expect: true}, + {name: "TestServer_IsSocket_GRPC", network: server.NetworkGRPC, expect: false}, } -} -func TestNewClient(t *testing.T) { - count := 500 - for i := 0; i < count; i++ { - fmt.Println("启动", i+1) - cli := client.NewWebsocket("ws://172.29.5.138:9999") - cli.RegConnectionReceivePacketEvent(func(conn *client.Client, wst int, packet []byte) { - fmt.Println(time.Now().Unix(), "收到", string(packet)) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + s := server.New(c.network) + if s.IsSocket() != c.expect { + t.Fatalf("expect: %v, got: %v", c.expect, s.IsSocket()) + } }) - cli.RegConnectionClosedEvent(func(conn *client.Client, err any) { - fmt.Println("关闭", err) - }) - cli.RegConnectionOpenedEvent(func(conn *client.Client) { - go func() { - for i < count { - time.Sleep(time.Second) - } - for { - for i := 0; i < 10; i++ { - cli.WriteWS(2, []byte("hello")) - } - } - }() - }) - if err := cli.Run(); err != nil { - panic(err) - } } - - time.Sleep(times.Week) } diff --git a/server/service.go b/server/service.go index 54abe6ca..722f9233 100644 --- a/server/service.go +++ b/server/service.go @@ -5,7 +5,8 @@ import ( "reflect" ) -// Service 兼容传统 service 设计模式的接口 +// Service 兼容传统 service 设计模式的接口,通过该接口可以实现更简洁、更具有可读性的服务绑定 +// - 在这之前,我们在实现功能上会将 Server 进行全局存储,之后通过 init 函数进行初始化,这样的顺序是不可控的。 type Service interface { // OnInit 初始化服务,该方法将会在 Server 初始化时执行 // - 通常来说,该阶段发生任何错误都应该 panic 以阻止 Server 启动 diff --git a/server/service_example_test.go b/server/service_example_test.go new file mode 100644 index 00000000..88817115 --- /dev/null +++ b/server/service_example_test.go @@ -0,0 +1,44 @@ +package server_test + +import ( + "github.com/kercylan98/minotaur/server" + "time" +) + +// 这个案例中我们将 `TestService` 绑定到了 `srv` 服务器中,当服务器启动时,将会对 `TestService` 进行初始化 +// +// 其中 `TestService` 的定义如下: +// ```go +// +// type TestService struct{} +// +// func (ts *TestService) OnInit(srv *server.Server) { +// srv.RegStartFinishEvent(onStartFinish) +// +// srv.RegStopEvent(func(srv *server.Server) { +// fmt.Println("server stop") +// }) +// } +// +// func (ts *TestService) onStartFinish(srv *server.Server) { +// fmt.Println("server start finish") +// } +// +// ``` +// +// 可以看出,在服务初始化时,该服务向服务器注册了启动完成事件及停止事件。这是我们推荐的编码方式,这样编码有以下好处: +// - 具备可控制的初始化顺序,避免 init 产生的各种顺序导致的问题,如配置还未加载完成,即开始进行数据库连接等操作 +// - 可以方便的将不同的服务拆分到不同的包中进行管理 +// - 当不需要某个服务时,可以直接删除该服务的绑定,而不需要修改其他代码 +// - ... +func ExampleBindService() { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) + server.BindService(srv, new(TestService)) + + if err := srv.RunNone(); err != nil { + panic(err) + } + // Output: + // server start finish + // server stop +} diff --git a/server/service_test.go b/server/service_test.go index 1ffb7104..caafa7bc 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -20,24 +20,19 @@ func (ts *TestService) OnInit(srv *server.Server) { } func TestBindService(t *testing.T) { - srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) - - server.BindService(srv, new(TestService)) - - if err := srv.RunNone(); err != nil { - t.Fatal(err) + var cases = []struct { + name string + }{ + {name: "TestBindService"}, } -} -func ExampleBindService() { - srv := server.New(server.NetworkNone, server.WithLimitLife(time.Second)) - server.BindService(srv, new(TestService)) - - if err := srv.RunNone(); err != nil { - panic(err) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + srv := server.New(server.NetworkNone, server.WithLimitLife(time.Millisecond)) + server.BindService(srv, new(TestService)) + if err := srv.RunNone(); err != nil { + t.Fatal(err) + } + }) } - - // Output: - // server start finish - // server stop } diff --git a/utils/generator/astgo/comment.go b/utils/generator/astgo/comment.go index 68e7767f..9e7527c3 100644 --- a/utils/generator/astgo/comment.go +++ b/utils/generator/astgo/comment.go @@ -5,7 +5,7 @@ import ( "strings" ) -func newComment(cg *ast.CommentGroup) *Comment { +func newComment(name string, cg *ast.CommentGroup) *Comment { c := &Comment{} if cg == nil { return c @@ -14,9 +14,10 @@ func newComment(cg *ast.CommentGroup) *Comment { c.Comments = append(c.Comments, comment.Text) cc := strings.TrimPrefix(strings.Replace(comment.Text, "// ", "//", 1), "//") if i == 0 { - s := strings.SplitN(cc, " ", 2) - if len(s) == 2 { - cc = s[1] + tsc := strings.TrimSpace(cc) + if strings.HasPrefix(tsc, name) { + s := strings.TrimSpace(strings.TrimPrefix(tsc, name)) + cc = s } } c.Clear = append(c.Clear, cc) diff --git a/utils/generator/astgo/field.go b/utils/generator/astgo/field.go index 1c007896..da2a5e26 100644 --- a/utils/generator/astgo/field.go +++ b/utils/generator/astgo/field.go @@ -10,7 +10,7 @@ func newField(field *ast.Field) []*Field { return []*Field{{ Anonymous: true, Type: newType(field.Type), - Comments: newComment(field.Comment), + Comments: newComment("", field.Comment), }} } else { var fs []*Field @@ -22,7 +22,7 @@ func newField(field *ast.Field) []*Field { Anonymous: false, Name: name.String(), Type: newType(field.Type), - Comments: newComment(field.Comment), + Comments: newComment(name.String(), field.Comment), }) } return fs diff --git a/utils/generator/astgo/file.go b/utils/generator/astgo/file.go index 233b4a16..ff4c03ca 100644 --- a/utils/generator/astgo/file.go +++ b/utils/generator/astgo/file.go @@ -16,7 +16,7 @@ func newFile(owner *Package, filePath string) (*File, error) { af: af, owner: owner, FilePath: filePath, - Comment: newComment(af.Doc), + Comment: newComment("Package", af.Doc), } for _, decl := range af.Decls { switch typ := decl.(type) { diff --git a/utils/generator/astgo/function.go b/utils/generator/astgo/function.go index 40bf9e7b..7613476c 100644 --- a/utils/generator/astgo/function.go +++ b/utils/generator/astgo/function.go @@ -12,7 +12,7 @@ func newFunction(astFunc *ast.FuncDecl) *Function { f := &Function{ decl: astFunc, Name: astFunc.Name.String(), - Comments: newComment(astFunc.Doc), + Comments: newComment(astFunc.Name.String(), astFunc.Doc), } f.IsTest = strings.HasPrefix(f.Name, "Test") f.IsBenchmark = strings.HasPrefix(f.Name, "Benchmark") diff --git a/utils/generator/astgo/package.go b/utils/generator/astgo/package.go index 681564dd..1491e946 100644 --- a/utils/generator/astgo/package.go +++ b/utils/generator/astgo/package.go @@ -91,7 +91,7 @@ func (p *Package) Structs() []*Struct { } func (p *Package) FileComments() *Comment { - var comment = newComment(nil) + var comment = newComment("", nil) for _, file := range p.Files { for _, c := range file.Comment.Comments { comment.Comments = append(comment.Comments, c) diff --git a/utils/generator/astgo/struct.go b/utils/generator/astgo/struct.go index 263e9d11..5fba9dbc 100644 --- a/utils/generator/astgo/struct.go +++ b/utils/generator/astgo/struct.go @@ -8,7 +8,7 @@ func newStruct(astGen *ast.GenDecl) *Struct { astTypeSpec := astGen.Specs[0].(*ast.TypeSpec) s := &Struct{ Name: astTypeSpec.Name.String(), - Comments: newComment(astGen.Doc), + Comments: newComment(astTypeSpec.Name.String(), astGen.Doc), } s.Internal = s.Name[0] >= 97 && s.Name[0] <= 122 if astTypeSpec.TypeParams != nil { diff --git a/utils/generator/genreadme/builder.go b/utils/generator/genreadme/builder.go index 435c5adf..a15f662a 100644 --- a/utils/generator/genreadme/builder.go +++ b/utils/generator/genreadme/builder.go @@ -288,6 +288,7 @@ func (b *Builder) genStructs() { if function.Internal || function.Test { continue } + b.newLine(fmt.Sprintf(``, structInfo.Name, function.Name)).newLine() b.title(4, strings.TrimSpace(fmt.Sprintf("func (%s%s) %s%s %s", super.If(function.Struct.Type.IsPointer, "*", ""), structInfo.Name, @@ -311,6 +312,7 @@ func (b *Builder) genStructs() { for _, comment := range function.Comments.Clear { b.quote(comment) } + b.newLine() if example := b.p.GetExampleTest(function); example != nil { b.newLine("**示例代码:**").newLine() if len(example.Comments.Clear) > 0 { diff --git a/utils/random/ip.go b/utils/random/ip.go index 952552fd..862af974 100644 --- a/utils/random/ip.go +++ b/utils/random/ip.go @@ -15,6 +15,20 @@ func Port() int { return Int(1, 65535) } +// UsablePort 随机返回一个可用的端口号,如果没有可用端口号则返回 -1 +func UsablePort() int { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + return -1 + } + cli, err := net.ListenTCP("tcp", addr) + if err != nil { + return -1 + } + defer func() { _ = cli.Close() }() + return cli.Addr().(*net.TCPAddr).Port +} + // IPv4 返回一个随机产生的IPv4地址。 func IPv4() string { return fmt.Sprintf("%d.%d.%d.%d", Int(1, 255), Int(0, 255), Int(0, 255), Int(0, 255))