Compare commits

..

No commits in common. "main" and "day04.03" have entirely different histories.

17 changed files with 8 additions and 715 deletions

View File

@ -1,7 +1,7 @@
[app]
name = "devcloud"
description = "app desc"
address = "http://127.0.0.1:8080"
address = "localhost"
encrypt_key = "defualt app encrypt key"
[datasource]
@ -11,7 +11,7 @@
database = "devcloud_go18"
username = "root"
password = "123456"
auto_migrate = false
auto_migrate = true
debug = true
[http]

View File

@ -33,28 +33,3 @@ type Service interface {
}
```
## 接口的实现
```go
func TestIssueToken(t *testing.T) {
req := token.NewIssueTokenRequest()
req.IssueByPassword("admin", "123456")
req.Source = token.SOURCE_WEB
set, err := svc.IssueToken(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(set)
}
func TestQueryToken(t *testing.T) {
req := token.NewQueryTokenRequest()
set, err := svc.QueryToken(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(set)
}
```

View File

@ -1,72 +1 @@
package api
import (
_ "embed"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
)
func init() {
ioc.Api().Registry(&TokenRestfulApiHandler{})
}
type TokenRestfulApiHandler struct {
ioc.ObjectImpl
// 依赖控制器
svc token.Service
}
func (h *TokenRestfulApiHandler) Name() string {
return token.APP_NAME
}
//go:embed docs/login.md
var loginApiDocNotes string
func (h *TokenRestfulApiHandler) Init() error {
h.svc = token.GetService()
tags := []string{"登录管理"}
ws := gorestful.ObjectRouter(h)
ws.Route(ws.POST("").To(h.Login).
Doc("颁发令牌(登录)").
Notes(loginApiDocNotes).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(token.IssueTokenRequest{}).
Writes(token.Token{}).
Returns(200, "OK", token.Token{}))
ws.Route(ws.POST("/validate").To(h.ValiateToken).
Doc("校验令牌").
// Metadata(permission.Auth(true)).
// Metadata(permission.Permission(false)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(token.ValiateTokenRequest{}).
Writes(token.Token{}).
Returns(200, "OK", token.Token{}))
// ws.Route(ws.POST("/change_namespace").To(h.ChangeNamespce).
// Doc("切换令牌访问空间").
// // Metadata(permission.Auth(true)).
// // Metadata(permission.Permission(false)).
// Metadata(restfulspec.KeyOpenAPITags, tags).
// Reads(token.ChangeNamespceRequest{}).
// Writes(token.Token{}).
// Returns(200, "OK", token.Token{}))
ws.Route(ws.DELETE("").To(h.Logout).
Doc("撤销令牌(退出)").
// Metadata(permission.Auth(true)).
// Metadata(permission.Permission(false)).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(token.IssueTokenRequest{}).
Writes(token.Token{}).
Returns(200, "OK", token.Token{}).
Returns(404, "Not Found", nil))
return nil
}

View File

@ -1,8 +0,0 @@
登录接口
```json
{
"username": "admin",
"password": "123456"
}
```

View File

@ -1,127 +1 @@
package api
import (
"net/http"
"net/url"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"github.com/emicklei/go-restful/v3"
"github.com/infraboard/mcube/v2/http/restful/response"
"github.com/infraboard/mcube/v2/ioc/config/application"
)
func (h *TokenRestfulApiHandler) Login(r *restful.Request, w *restful.Response) {
// 1. 获取用户的请求参数, 参数在Body里面
req := token.NewIssueTokenRequest()
// 获取用户通过body传入的参数
err := r.ReadEntity(req)
if err != nil {
response.Failed(w, err)
return
}
// 设置当前调用者的Token
// Private 用户自己的Token
// 如果你是user/password 这种方式token 直接放到body
switch req.Issuer {
case token.ISSUER_PRIVATE_TOKEN:
req.Parameter.SetAccessToken(token.GetAccessTokenFromHTTP(r.Request))
}
// 2. 执行逻辑
tk, err := h.svc.IssueToken(r.Request.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
// access_token 通过SetCookie 直接写到浏览器客户端(Web)
http.SetCookie(w, &http.Cookie{
Name: token.ACCESS_TOKEN_COOKIE_NAME,
Value: url.QueryEscape(tk.AccessToken),
MaxAge: 0,
Path: "/",
Domain: application.Get().Domain(),
SameSite: http.SameSiteDefaultMode,
Secure: false,
HttpOnly: true,
})
// 在Header头中也添加Token
w.Header().Set(token.ACCESS_TOKEN_RESPONSE_HEADER_NAME, tk.AccessToken)
// 3. Body中返回Token对象
response.Success(w, tk)
}
// func (h *TokenRestulApiHandler) ChangeNamespce(r *restful.Request, w *restful.Response) {
// // 1. 获取用户的请求参数, 参数在Body里面
// req := token.NewChangeNamespceRequest()
// err := r.ReadEntity(req)
// if err != nil {
// response.Failed(w, err)
// return
// }
// tk := token.GetTokenFromCtx(r.Request.Context())
// req.UserId = tk.UserId
// // 2. 执行逻辑
// tk, err = h.svc.ChangeNamespce(r.Request.Context(), req)
// if err != nil {
// response.Failed(w, err)
// return
// }
// // 3. Body中返回Token对象
// response.Success(w, tk)
// }
// Logout HandleFunc
func (h *TokenRestfulApiHandler) Logout(r *restful.Request, w *restful.Response) {
req := token.NewRevolkTokenRequest(
token.GetAccessTokenFromHTTP(r.Request),
token.GetRefreshTokenFromHTTP(r.Request),
)
tk, err := h.svc.RevolkToken(r.Request.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
// access_token 通过SetCookie 直接写到浏览器客户端(Web)
http.SetCookie(w, &http.Cookie{
Name: token.ACCESS_TOKEN_COOKIE_NAME,
Value: "",
MaxAge: 0,
Path: "/",
Domain: application.Get().Domain(),
SameSite: http.SameSiteDefaultMode,
Secure: false,
HttpOnly: true,
})
// 3. 返回响应
response.Success(w, tk)
}
func (h *TokenRestfulApiHandler) ValiateToken(r *restful.Request, w *restful.Response) {
// 1. 获取用户的请求参数, 参数在Body里面
req := token.NewValiateTokenRequest("")
err := r.ReadEntity(req)
if err != nil {
response.Failed(w, err)
return
}
// 2. 执行逻辑
tk, err := h.svc.ValiateToken(r.Request.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
// 3. Body中返回Token对象
response.Success(w, tk)
}

View File

@ -22,7 +22,6 @@ func GetAccessTokenFromHTTP(r *http.Request) string {
if err != nil {
return ""
}
// ?token=xxxx
tk, _ = url.QueryUnescape(cookie.Value)
} else {
// 处理 带格式: Bearer <Your API key>

View File

@ -65,27 +65,3 @@ func (u *User) CheckPassword(password string) error {
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
}
```
### 接口设计
```go
ws.Route(ws.GET("").To(h.QueryUser).
Doc("用户列表查询").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
Writes(Set{}).
Returns(200, "OK", Set{}))
```
其中 user字段里面的
```json
{
"password": "$2a$10$GoEjC.vFlgJ..BCvaMu6YurdVgyx4p6S4LFRXiqXESiVY4lokL496"
}
// 需要脱敏: "*"
{
"password": "****"
}
```

View File

@ -1,48 +1 @@
package api
import (
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
)
func init() {
ioc.Api().Registry(&UserRestfulApiHandler{})
}
type UserRestfulApiHandler struct {
ioc.ObjectImpl
// 依赖控制器
svc user.Service
}
func (h *UserRestfulApiHandler) Name() string {
return "users"
}
func (h *UserRestfulApiHandler) Init() error {
h.svc = user.GetService()
tags := []string{"用户登录"}
ws := gorestful.ObjectRouter(h)
ws.Route(ws.GET("").To(h.QueryUser).
Doc("用户列表查询").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
Writes(Set{}).
Returns(200, "OK", Set{}))
return nil
}
// *types.Set[*User]
// 返回的泛型, API Doc这个工具 不支持泛型
type Set struct {
Total int64 `json:"total"`
Items []user.User `json:"items"`
}

View File

@ -1,40 +0,0 @@
package api
import (
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"github.com/emicklei/go-restful/v3"
"github.com/gin-gonic/gin/binding"
"github.com/infraboard/mcube/v2/http/restful/response"
)
func (h *UserRestfulApiHandler) QueryUser(r *restful.Request, w *restful.Response) {
// 获取用户通过API传入的参数
req := user.NewQueryUserRequest()
// r.QueryParameter("page_size")
// r.QueryParameter("page_number")
// url bind, url parameter <---> obj form:"page_size" form:"page_number"
// url?
// gin bind 的具体实现:非简单结构: json user_ids = [] user_id=1&user_id=2
if err := binding.Query.Bind(r.Request, req); err != nil {
response.Failed(w, err)
return
}
set, err := h.svc.QueryUser(r.Request.Context(), req)
if err != nil {
response.Failed(w, err)
return
}
// 专门做脱敏处理
// for user.password = "" json: omitempty
// 每个接口 都需要定制化的写这些逻辑
// 为对象实现一个脱名方法: Densence, 断言这个对象实现了这个方法
// 定义一个接口,断言这个对象 满足这个接口, 这个能解决80%的问题
// 对象嵌套, 你需要自己 去调用嵌套对象的 Densence
// 能不能直接通过JSON标签 这样方式来完成脱敏: must:"3,4" (181*****4777)
// 不能每次都调用吧,因此这个脱敏逻辑 放到 Rsep函数内进行处理
response.Success(w, set)
}

View File

@ -1,52 +0,0 @@
<mxfile host="65bd71144e">
<diagram id="DRQy-UvMks4-KcdwQQVb" name="第 1 页">
<mxGraphModel dx="892" dy="370" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="130" y="230" width="410" height="120" as="geometry"/>
</mxCell>
<mxCell id="3" value="user" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="200" y="260" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="15" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="4" target="2">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="130" y="110" width="410" height="70" as="geometry"/>
</mxCell>
<mxCell id="6" value="API" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="30" y="130" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="7" value="接口" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="40" y="270" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="9" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="8" target="3">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="8" value="token" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="400" y="260" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="13" style="edgeStyle=none;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="1" source="10" target="11">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="10" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxGeometry x="630" y="10" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="14" style="edgeStyle=none;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="11" target="4">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="130" y="10" width="410" height="70" as="geometry"/>
</mxCell>
<mxCell id="12" value="UI" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="40" y="25" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="16" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;Heading&lt;/h1&gt;&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.&lt;/p&gt;" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
<mxGeometry x="580" y="100" width="180" height="120" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -33,13 +33,13 @@ type Service interface {
func NewQueryUserRequest() *QueryUserRequest {
return &QueryUserRequest{
PageRequest: *request.NewDefaultPageRequest(),
PageRequest: request.NewDefaultPageRequest(),
UserIds: []uint64{},
}
}
type QueryUserRequest struct {
request.PageRequest
*request.PageRequest
UserIds []uint64 `form:"user" json:"user"`
}

View File

@ -65,7 +65,7 @@ type CreateUserRequest struct {
// 用户名
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;uniqueIndex" description:"用户名"`
// 密码(Hash过后的)
Password string `json:"password,omitempty" gorm:"column:password;type:varchar(200);not null" description:"用户密码" mask:",3,4"`
Password string `json:"password" gorm:"column:password;type:varchar(200);not null" description:"用户密码"`
// 用户描述
Description string `json:"description" gorm:"column:description;type:varchar(200);not null" description:"用户描述"`
// 用户类型

View File

@ -1,18 +0,0 @@
package main
import (
"github.com/infraboard/mcube/v2/ioc/server/cmd"
// 加载的业务对象
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
// 非功能性模块
_ "github.com/infraboard/mcube/v2/ioc/apps/apidoc/restful"
_ "github.com/infraboard/mcube/v2/ioc/apps/health/restful"
_ "github.com/infraboard/mcube/v2/ioc/apps/metric/restful"
)
func main() {
// 启动
cmd.Start()
}

4
go.mod
View File

@ -4,14 +4,13 @@ go 1.24.1
require (
github.com/caarlos0/env/v6 v6.10.1
github.com/emicklei/go-restful-openapi/v2 v2.11.0
github.com/emicklei/go-restful/v3 v3.12.2
github.com/gin-gonic/gin v1.10.0
github.com/infraboard/mcube/v2 v2.0.59
github.com/infraboard/modules v0.0.12
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.26.0
@ -77,7 +76,6 @@ require (
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 // indirect
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect

31
go.sum
View File

@ -18,17 +18,11 @@ github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful-openapi/v2 v2.11.0 h1:Ur+yGxoOH/7KRmcj/UoMFqC3VeNc9VOe+/XidumxTvk=
github.com/emicklei/go-restful-openapi/v2 v2.11.0/go.mod h1:4CTuOXHFg3jkvCpnXN+Wkw5prVUnP8hIACssJTYorWo=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
@ -44,18 +38,12 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@ -111,20 +99,14 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -142,7 +124,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -175,7 +156,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -193,14 +173,10 @@ github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/Oa
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0 h1:jvVaMcOzU4abk6o8VCJJN5wRoFQxe3Afnc8fPcYbO0I=
go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.60.0/go.mod h1:NMt0e4UA3rY+CV+xyjE4wLhz/6BiydSOZhMeevBDZB0=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4=
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 h1:DpwKW04LkdFRFCIgM3sqwTJA/QREHMeMHYPWP1WeaPQ=
go.opentelemetry.io/contrib/propagators/b3 v1.35.0/go.mod h1:9+SNxwqvCWo1qQwUpACBY5YKNVxFJn5mlbXg/4+uKBg=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
@ -223,6 +199,8 @@ golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
@ -243,16 +221,11 @@ google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3i
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=

View File

@ -1,181 +0,0 @@
# gorestful Web框架
[go-restful](https://github.com/emicklei/go-restful)
```go
package main
import (
"fmt"
"log"
"net/http"
"time"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
)
// UserResource is the REST layer to the User domain
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
// WebService creates a new service that can handle REST requests for User resources.
func (u UserResource) WebService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well
tags := []string{"users"}
ws.Route(ws.GET("/").To(u.findAllUsers).
// docs
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(User{}). // on the response
Returns(200, "OK", User{}).
Returns(404, "Not Found", nil))
ws.Route(ws.PUT("/{user-id}").To(u.upsertUser).
// docs
Doc("update a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.POST("").To(u.createUser).
// docs
Doc("create a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
return ws
}
// GET http://localhost:8080/users
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
log.Println("findAllUsers")
list := []User{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}
// GET http://localhost:8080/users/1
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
log.Println("findUser")
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.ID) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
func (u *UserResource) upsertUser(request *restful.Request, response *restful.Response) {
log.Println("upsertUser")
usr := User{ID: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa</Name></User>
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
log.Println("createUser")
usr := User{ID: fmt.Sprintf("%d", time.Now().Unix())}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// DELETE http://localhost:8080/users/1
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
log.Println("removeUser")
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
u := UserResource{map[string]User{}}
restful.DefaultContainer.Add(u.WebService())
config := restfulspec.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
APIPath: "/apidocs.json",
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "UserService",
Description: "Resource for managing Users",
Contact: &spec.ContactInfo{
ContactInfoProps: spec.ContactInfoProps{
Name: "john",
Email: "john@doe.rp",
URL: "http://johndoe.org",
},
},
License: &spec.License{
LicenseProps: spec.LicenseProps{
Name: "MIT",
URL: "http://mit.org",
},
},
Version: "1.0.0",
},
}
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
Name: "users",
Description: "Managing users"}}}
}
// User is just a sample type
type User struct {
ID string `xml:"id" json:"id" description:"identifier of the user"`
Name string `xml:"name" json:"name" description:"name of the user" default:"john"`
Age int `xml:"age" json:"age" description:"age of the user" default:"21"`
}
```

View File

@ -1,85 +0,0 @@
<mxfile host="65bd71144e">
<diagram id="EsWSgBRg6HmPYJxvZoKw" name="第 1 页">
<mxGraphModel dx="812" dy="419" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="*gin.Engine&lt;div&gt;Server&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="140" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="Gin" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="40" y="90" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="4" value="GoRestful" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="650" y="100" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="5" value="&lt;div&gt;&lt;font color=&quot;#000000&quot;&gt;restful.DefaultContainer&lt;/font&gt;&lt;/div&gt;&lt;div&gt;Server&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="414" y="140" width="210" height="60" as="geometry"/>
</mxCell>
<mxCell id="6" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="230" width="210" height="100" as="geometry"/>
</mxCell>
<mxCell id="7" value="Group" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="50" y="230" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="8" value="group&lt;div&gt;/books&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="140" y="250" width="80" height="60" as="geometry"/>
</mxCell>
<mxCell id="9" value="group&lt;div&gt;/comments&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="240" y="250" width="80" height="60" as="geometry"/>
</mxCell>
<mxCell id="10" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="414" y="230" width="210" height="100" as="geometry"/>
</mxCell>
<mxCell id="11" value="WebService" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="650" y="230" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="12" value="ws&lt;div&gt;/books&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="440" y="250" width="80" height="60" as="geometry"/>
</mxCell>
<mxCell id="13" value="ws&lt;div&gt;/comments&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="530" y="250" width="80" height="60" as="geometry"/>
</mxCell>
<mxCell id="14" value="restful.DefaultContainer.Add(u.WebService())" style="text;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="557" y="50" width="270" height="40" as="geometry"/>
</mxCell>
<mxCell id="15" value="root.Group(&quot;&quot;)" style="text;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="100" y="50" width="270" height="40" as="geometry"/>
</mxCell>
<mxCell id="16" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="360" width="210" height="100" as="geometry"/>
</mxCell>
<mxCell id="17" value="Route" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="50" y="360" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="18" value="route&lt;div&gt;get(&#39;/&#39;)&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="150" y="380" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="19" value="route&lt;div&gt;get(&#39;/&#39;)&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="245" y="380" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="20" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="414" y="360" width="210" height="100" as="geometry"/>
</mxCell>
<mxCell id="21" value="Route" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="660" y="360" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="22" value="ws.GET(&quot;/&quot;).To(u.findAllUsers)" style="text;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="627" y="330" width="200" height="40" as="geometry"/>
</mxCell>
<mxCell id="23" value="route&lt;div&gt;ws.GET(&#39;/&#39;)&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="430" y="380" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="24" value="route&lt;div&gt;ws.POST(&#39;/&#39;)&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="540" y="380" width="70" height="40" as="geometry"/>
</mxCell>
<mxCell id="25" value="findAllUsers(request *restful.Request, response *restful.Response)" style="text;whiteSpace=wrap;" vertex="1" parent="1">
<mxGeometry x="400" y="20" width="390" height="40" as="geometry"/>
</mxCell>
<mxCell id="26" value="&lt;div style=&quot;color: #f8f8f2;background-color: #272822;font-family: &#39;Cascadia Code NF&#39;, Menlo, Monaco, &#39;Courier New&#39;, monospace, Menlo, Monaco, &#39;Courier New&#39;, monospace;font-weight: normal;font-size: 12px;line-height: 18px;white-space: pre;&quot;&gt;&lt;div&gt;&lt;span style=&quot;color: #a6e22e;&quot;&gt;queryBook&lt;/span&gt;&lt;span style=&quot;color: #f8f8f2;&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color: #fd971f;font-style: italic;&quot;&gt;ctx&lt;/span&gt;&lt;span style=&quot;color: #f8f8f2;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #f92672;&quot;&gt;*&lt;/span&gt;&lt;span style=&quot;color: #a6e22e;text-decoration: underline;&quot;&gt;gin&lt;/span&gt;&lt;span style=&quot;color: #f8f8f2;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #a6e22e;text-decoration: underline;&quot;&gt;Context&lt;/span&gt;&lt;span style=&quot;color: #f8f8f2;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="text;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="100" y="20" width="220" height="40" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>