ssgo能以非常简单的方式快速部署成为微服务群
如果您的电脑go version >= 1.11,使用以下命令初始化依赖自定义sshow:
go mod init sshow go mod tidy
1、下载并安装s
go get -u github.com/ssgo/s
2、在项目代码中导入它
import "github.com/ssgo/s"start.go:
package main import "github.com/ssgo/s" func main() { s.Restful(0, "GET", "/hello", func() string{ return "Hello ssgo\n" }) s.Start() }
即可快速构建出一个8080端口可访问的服务:
export service_listen=:8080 export service_httpversion=1 go run start.go
windows下使用:
set service_listen=:8080 set service_httpversion=1 go run start.go
环境变量设置不区分大小写
服务默认使用随机端口启动,若要指定端口可设置环境变量,或start.go目录下配置文件service.json
{
"listen":":8081"
}开发时可以使用配置文件
部署推荐使用容器技术设置环境变量
框架服务发现机制基于redis实现,所以使用discover之前,先要准备一个redis服务
默认使用127.0.0.1:6379,db默认为15,密码默认为空,也可以在项目根目录自定义配置redis.json
如果您的redis的密码如果不为空,需要使用AES加密后将密文放在配置文件password字段上,保障密码不泄露
使用github.com/ssgo/tool中的sskey来生成密码:
cd ssgo/tool/sskey # 如果以前没有编译过 go build sskey.go # 创建并保存秘钥 sskey -c sshow # 输入原始密码 sskey -e sshow
得到AES加密后的密码放入discover.json中
{
"registry":"127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg=="
}也可以通过环境变量来设置:
export DISCOVER_REGISTRY="127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg=="
windows下:
set discover_registry="127.0.0.1:6379:1:upvNALgTxwS/xUp2Cie4tg=="
discover_registry的设置代表:
redis主机:端口号:数据库:res加密后的密码
package main import "github.com/ssgo/s" func getFullName(in struct{ Name string }) (out struct{ FullName string }) { out.FullName = in.Name + " Lee" return } func main() { s.Register(1, "/{name}/fullName", getFullName) s.Start() }
export discover_app=s1 export service_accesstokens='{"s1token":1}' go run service.go
windows下使用:
set discover_app=s1 set service_accesstokens={"s1token":1} go run service.go
Register第一个参数值为1表示该服务工作在认证级别1上,派发了一个令牌 “s1token”,不带该令牌的请求将被拒绝
s.Start()将会工作在 HTTP/2.0 No SSL 协议上(服务间通讯默认都使用 HTTP/2.0 No SSL 协议)
并且自动连接本机默认的redis服务,并注册一个叫 s1 的服务(如需使用其他可以参考redis的配置)
可运行多个实例,调用方访问时将会自动负载均衡到某一个节点
package main import ( "github.com/ssgo/s" "github.com/ssgo/discover" ) func getInfo(in struct{ Name string }, c *discover.Caller) (out struct{ FullName string }) { c.Get("s1", "/"+in.Name+"/fullName", nil).To(&out) return } func main() { s.Register(0, "/{name}", getInfo) s.Start() }
export discover_app=g1 export service_httpversion=1 export service_listen=:8091 export discover_calls='{"s1":"s1token"}' go run controller.go &
windows下使用:
set discover_app=g1 set service_httpversion=1 set service_listen=:8091 set discover_calls={"s1":"s1token"} go run controller.go
该服务工作在认证级别0上,工作在 HTTP/1.1 协议上,可以直接访问
getInfo 方法中调用 s1 时会根据 redis 中注册的节点信息负载均衡到某一个节点
所有调用 s1 服务的请求都会自动带上 "sltoken" 这个令牌以获得相应等级的访问权限
// 注册服务 func Register(authLevel uint, name string, serviceFunc any) {} // 注册以正则匹配的服务 func RegisterByRegex(name string, service any){} // 设置前置过滤器 func SetInFilter(filter func(in *map[string]any, request *http.Request, response *http.ResponseWriter) (out any)) {} // 设置后置过滤器 func SetOutFilter(filter func(in *map[string]any, request *http.Request, response *http.ResponseWriter, out any) (newOut any, isOver bool)) {} // 注册身份认证模块 func SetAuthChecker(authChecker func(authLevel uint, url *string, request *map[string]any) bool) {} // 设置panic错误处理方法 func SetErrorHandle(myErrorHandle func(err any, request *http.Request, response *http.ResponseWriter) any) // 默认启动HTTP/2.0服务(若未配置证书将工作在No SSL模式) // 如果设置了httpVersion=1则启动HTTP/1.1服务 func Start() {} // 默认异步方式启动HTTP/2.0服务() // 如果设置了httpVersion=1则启动HTTP/1.1服务 func AsyncStart() *AsyncServer {} // 停止以异步方式启动的服务后等待各种子线程结束 func (as *AsyncServer) Stop() {} // 调用异步方式启动的服务 func (as *AsyncServer) Get(path string, headers ... string) *Result {} func (as *AsyncServer) Post(path string, data any, headers ... string) *Result {} func (as *AsyncServer) Put(path string, data any, headers ... string) *Result {} func (as *AsyncServer) Head(path string, data any, headers ... string) *Result {} func (as *AsyncServer) Delete(path string, data any, headers ... string) *Result {} func (as *AsyncServer) Do(path string, data any, headers ... string) *Result {}
package main import ( "github.com/ssgo/s" "net/http" "os" ) type actionIn struct { Aaa int Bbb string Ccc string } func restAct(req *http.Request, in actionIn) actionIn { return in } func showFullName(in struct{ Name string }) (out struct{ FullName string }) { out.FullName = in.Name + " Lee." return } func main() { //http://127.0.0.1:8301/api/echo?aaa=1&bbb=2&ccc=3 s.Restful(0, "GET", "/api/echo", restAct) s.Restful(0, "POST", "/api/echo", restAct) s.Restful(0, "PUT", "/api/echo", restAct) //HEAD和GET本质一样,区别在于HEAD不含呈现数据,仅仅是HTTP头信息 s.Restful(0, "HEAD", "/api/echo", restAct) s.Restful(0, "DELETE", "/api/echo", restAct) s.Restful(0, "OPTIONS", "/api/echo", restAct) //传参 //http://127.0.0.1:8301/full_name/david s.Restful(0, "GET", "/full_name/{name}", showFullName) //访问设置header content-type:application/json params:{"name":"jim"} s.Restful(0, "PUT", "/full_name", showFullName) s.Start() }
| 环境变量 | 值 |
|---|---|
| service_listen | :8301 |
| service_httpVersion | 1 |
请求例子
POST http://127.0.0.1:8301/api/echo HTTP/1.1
Content-Type: application/x-www-form-urlencoded
aaa=12&bbb=hello&ccc=world
PUT http://127.0.0.1:8301/api/echo HTTP/1.1
Content-Type: application/json
{
"aaa": 12,
"bbb": "hello",
"ccc": "world"
}
配置https服务需要在原来配置基础上增加两个环境变量
export service_certfile="your cert file path" export service_keyfile="your key file path"
windows下:
set service_certfile=D:/server/ssl/your.pem set service_keyfile=D:/server/ssl/your.key
对于上面的restful实例,如果设置为https服务:
请求例子
POST https://127.0.0.1:8301/api/echo HTTP/1.1
Content-Type: application/x-www-form-urlencoded
aaa=12&bbb=hello&ccc=world
package main import ( "github.com/ssgo/s" "net/http" "os" ) func headerTest(request *http.Request, response http.ResponseWriter) (token string) { token = "Get header token:" + request.Header.Get("token") response.Header().Set("resToken", "Hello world") return } //输入参数放在前放在后都可以 func label(in struct{ Enter string }, request *http.Request, response http.ResponseWriter) (out struct{ Label string}) { prefix := request.Header.Get("prefix") out.Label = prefix + in.Enter response.Header().Set("accept", "application/json") return } func main() { //header s.Restful(0, "GET", "/header_test", headerTest) s.Restful(0, "POST", "/label", label) s.Start() ////设置service_httpVersion=1 }
使用go标准库自带的response
s.Register(1, "/ssdesign", func(response http.ResponseWriter) string { response.WriteHeader(504) return "controller timeout" })
文件上传使用标准包自带功能
// 处理/upload 逻辑 func upload(w http.ResponseWriter, r *http.Request) { r.ParseMultipartForm(32 << 20) file, handler, err := r.FormFile("uploadfile") if err != nil { fmt.Println(err) return } defer file.Close() fmt.Fprintf(w, "%v", handler.Header) f, err := os.OpenFile("./upload/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) if err != nil { fmt.Println(err) return } defer f.Close() io.Copy(f, file) }
执行先后顺序为:前置过滤器、身份认证、后置过滤器
package main import ( "github.com/ssgo/s" "net/http" "os" ) type actionFilter struct { Aaa int Bbb string Ccc string Filter1 string Filter2 int } func authTest(in actionFilter) actionFilter { return in } func main() { s.Restful(0, "GET", "/auth_test", authTest) s.Restful(1, "POST", "/auth_test", authTest) s.Restful(2, "PUT", "/auth_test", authTest) //前置过滤器 s.SetInFilter(func(in *map[string]any, request *http.Request, response *http.ResponseWriter) any { (*in)["Filter1"] = "see" (*in)["filter2"] = 100 (*response).Header().Set("content-type", "application/json") return nil }) //身份认证 s.SetAuthChecker(func(authLevel uint, url *string, in *map[string]any, request *http.Request) bool { token := request.Header.Get("Token") switch authLevel { case 1: return token == "dev" || token == "develop" case 2: return token == "dev" } return false }) //后置过滤器 s.SetOutFilter(func(in *map[string]any, request *http.Request, response *http.ResponseWriter, result any) (any, bool) { data := result.(actionFilter) data.Filter2 = data.Filter2 + 100 return data, false }) s.Start() ////设置service_httpVersion=1 }
SetAuthChecker方法return false时,请求会返回403状态码,禁止访问
实现对url的重写
func main() { s.Register(0, "/show", func(in struct{ S1, S2 string }) string { return in.S1 + " " + in.S2 }) s.Register(0, "/show/{s1}", func(in struct{ S1, S2 string }) string { return in.S1 + " " + in.S2 }) s.Rewrite("/r1", "/show") //get http://127.0.0.1:8305/r2/123?s2=456 --> http://127.0.0.1:8305/r2/123?s2=456 s.Rewrite("/r2/(\\w+?)\\?.*?", "/show/$1") //post http://127.0.0.1:8305/r3?name=123 s2=456 s.Rewrite("/r3\\?name=(\\w+)", "/show/$1") s.Start() //设置service_httpVersion=1 }
启动异步服务与异步服务的调用:
package main import ( "fmt" "github.com/ssgo/s" "net/http" "os" ) type actIn struct { Aaa int Bbb string Ccc string } func act(req *http.Request, in actIn) actIn { return in } func main() { s.ResetAllSets() //http://127.0.0.1:8301/api/echo?aaa=1&bbb=2&ccc=3 s.Restful(0, "GET", "/act/echo", act) s.Restful(1, "POST", "/act/echo", act) //s.Restful(2, "PUT", "/act/echo", act) as := s.AsyncStart() defer as.Stop() asyncPost := as.Post("/act/echo?aaa=hello&bbb=hi", s.Map{ "ccc": "welcome", }, "Cid", "demo-post").Map() asyncPut := as.Put("/act/echo", s.Map{ "aaa": "hello", "bbb": "hi", "ccc": "welcome", }, "Cid", "demo-put").Map() asyncGet := as.Get("/act/echo?aaa=11&bbb=222&ccc=333").Map() fmt.Println("asyncPut:", asyncPut) fmt.Println("asyncPost:", asyncPost) fmt.Println("asyncGet", asyncGet) }
将服务代理为自定义服务,支持正则表达式
func main() { s.Register(1, "/serv/provide", func() (out struct{ Name string }) { out.Name = "server here" return }) //调用注册的服务 s.Register(2, "/serv/gate_get", func(c *discover.Caller) string { r := struct{ Name string }{} c.Get("e1", "/serv/provide").To(&r) return "gate get " + r.Name }) s.Proxy("/proxy/(.+?)", "e1", "/serv/$1") os.Setenv("LOG_FILE", os.DevNull) os.Setenv("SERVICE_ACCESSTOKENS", `{"e1_level1": 1, "e1_level2": 2, "e1_level3":3}`) os.Setenv("DISCOVER_CALLS", `{"e1":"5000:e1_level3:1"}`) //一定要做reset,不然手动设置的环境变量不可被加载**** config.ResetConfigEnv() as := s.AsyncStart() fmt.Println("/serv/provide:") fmt.Println(as.Get("/serv/provide", "Access-Token", "e1_level1")) fmt.Println("/serv/gate_get:") fmt.Println(as.Get("/serv/gate_get", "Access-Token", "e1_level2")) fmt.Println("/proxy/provide:") fmt.Println(as.Get("/proxy/provide", "Access-Token", "e1_level2")) fmt.Println("/proxy/gate_get:") fmt.Println(as.Get("/proxy/gate_get", "Access-Token", "e1_level2")) defer as.Stop() }
注册服务指定authLevel为0,启动的服务被调用时不需要鉴权,可以直接访问
可以为一个服务指定多个授权等级(1,2,3,4,5,6……),具备对应授权等级的accessToken的客户端,有权限访问服务
calls客户端使用服务具备的高等级的访问token,可以访问以低授权等级启动的服务
如果accessToken错误,或者具备小于服务启动授权等级的token,访问服务会被拒绝,返回403
例如:
客户端具备6等级accessToken,对于应用下注册启动的1,2,3,4,5,6服务默认具备访问权限
客户端仅具备1等级accessToken,对于应用下注册启动的2,3,4,5,6服务的访问会被拒绝
s.Static("/", "resource/") s.Start()
注意:resource结尾一定要有/
启动服务可以访问站点resource目录下的静态资源
controller可以通过proxy来实现多个静态服务的负载代理:
s.Proxy("/proxy/(.+?)", "k1", "/$1") s.Start()
一个以Action为处理单位的 Websocket 封装
// 注册Websocket服务 func RegisterWebsocket(authLevel uint, name string, updater *websocket.Upgrader, onOpen any, onClose any, decoder func(data any) (action string, request *map[string]any, err error), encoder func(action string, data any) any) *ActionRegister {} // 注册Websocket Action func (ar *ActionRegister) RegisterAction(authLevel uint, actionName string, action any) {} // 注册针对 Action 的认证模块 func SetActionAuthChecker(authChecker func(authLevel uint, url *string, action *string, request *map[string]any, sess any) bool) {}
使用websocket:
ws := s.RegisterWebsocket(1, "/dc/ws", updater, open, close, decode, encode) ws.RegisterAction(0, "hello", func(in struct{ Name string }) (out struct{ Name string }) { out.Name = in.Name + "!" return }) c, _, err := websocket.DefaultDialer.Dial("ws://"+addr2+"/proxy/ws", nil) err = c.WriteJSON(s.Map{"action": "hello", "name": "aaa"}) err = c.ReadJSON(&r) c.Close()
cookie可以使用go标准包http提供的方法,cookie发送给浏览器,即添加一个cookie
func hadler(w http.ResponseWriter) { cookieName := http.Cookie{ Name: "name", Value: "jim", HttpOnly: true, } cookieToken := http.Cookie{ Name: "token", Value: "asd123dsa", HttpOnly: true, MaxAge: 60,//设置有效期为60s } w.Header().Set("Set-Cookie", cookieName.String()) w.Header().Add("Set-Cookie", cookieToken.String()) }
使用http的setCookie也可以
func handler2(w http.ResponseWriter) { cookieName := http.Cookie{ Name: "name", Value: "jim", HttpOnly: true, } cookieToken := http.Cookie{ Name: "token", Value: "asd123dsa", HttpOnly: true, } http.SetCookie(w, &cookieName) http.SetCookie(w, &cookieToken) }
读取Cookie
func readCookie(w http.ResponseWriter, r *http.Request) { cookies := r.Header["Cookie"] nameCookie, _ := r.Cookie("name") }
// 设置 SessionKey,自动在 Header 中产生,AsyncStart 的客户端支持自动传递 func SetSessionKey(inSessionKey string) {} // 获取 SessionKey func GetSessionKey() string {} // 设置一个生命周期在 Request 中的对象,请求中可以使用对象类型注入参数方便调用 func SetSessionInject(request *http.Request, obj any) {} // 获取本生命周期中指定类型的 Session 对象 func GetSessionInject(request *http.Request, dataType reflect.Type) any {}
基于 Http Header 传递 SessionId(不推荐使用Cookie)
s.Restful(2, "PUT", "/api/echo", action) s.SetSessionKey("name") s.Start() func showFullName(in struct{ Name string },req *http.Request) (out struct{ FullName string }) { out.FullName = in.Name + " Lee." + s.GetSessionId(req) return }
使用 SetSession 设置的对象可以在服务方法中直接使用相同类型获得对象,一般是在 AuthChecker 或者 InFilter 中设置
session对象注入
aiValue := actionIn{2, "so", "cool"} s.SetSessionInject(req, aiValue) ai := s.GetSessionInject(req, reflect.TypeOf(actionIn{})).(actionIn)
// 设置一个注入对象,请求中可以使用对象类型注入参数方便调用 func SetInject(obj any) {} // 获取一个注入对象 func GetInject(dataType reflect.Type) any {}
注入对象可以跨请求体
type actionIn struct { Aaa int Bbb string Ccc string } func showInject(in struct{ Name string }) (out struct{ FullName string }) { ai := s.GetInject(reflect.TypeOf(actionIn{})).(actionIn) out.FullName = in.Name + " Lee." + " " + ai.Ccc return } func main() { //…… aiValue := actionIn{2, "so", "cool"} s.SetInject(aiValue) //…… }
接受服务方法主动panic的处理,可自定义SetErrorHandle
如果没有自定义errorHandle,可以走框架默认的处理方式,建议自定义SetErrorHandle,设置自身的服务方法的统一panic处理
func panicFunc() { panic(errors.New("s panic test")) } func main() { s.ResetAllSets() s.Register(0, "/panic_test", panicFunc) s.SetErrorHandle(func(err any, req *http.Request, rsp *http.ResponseWriter) any { return s.Map{"msg": "defined", "code": "30889", "panic": fmt.Sprintf("%s", err)} }) as := s.AsyncStart() defer as.Stop() r := as.Get("/panic_test") panicArr := r.Map() fmt.Println(panicArr) }
可在应用根目录放置一个 service.json
{
"listen": ":8081",
"httpVersion": 2,
"rwTimeout": 5000,
"keepaliveTimeout": 15000,
"rewriteTimeout": 10000,
"noLogGets": false,
"noLogHeaders": "Accept,Accept-Encoding,Cache-Control,Pragma,Connection",
"noLogInputFields": false,
"logInputArrayNum": 0,
"logOutputFields": "code,message",
"logOutputArrayNum": 2,
"logWebsocketAction": false,
"compress": true,
"certFile": "",
"keyFile": "",
"accessTokens": {
"hasfjlkdlasfsa": 1,
"fdasfsadfdsa": 2,
"9ifjjabdsadsa": 2
}
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| listen | string | :8081 | 服务绑定的端口号 |
| httpVersion | int | 2 | 服务的http版本 |
| rwTimeout | int 毫秒 |
10000 | 服务读写超时时间 |
| keepaliveTimeout | int 毫秒 |
10000 | keepalived激活时连接允许空闲的最大时间 如果未设置,默认为15秒 |
| rewriteTimeout | int 毫秒 |
5000 | rewrite、proxy操作的超时时间 |
| noLogGets | bool | false | 为true时屏蔽Get网络请求日志 |
| noLogHeaders | string | Accept,Accept-Encoding | 日志请求头和响应头屏蔽header头指定字段输出 可设置为false |
| noLogInputFields | string | accessToken | 日志过滤输入的字段,目前未启用 为false代表所有字段都日志打印 |
| logInputArrayNum | int | 2 | 输入字段子元素(数组)日志打印个数限制 默认为0, s.Arr{1, 2, 3}会输出为float64 (3) |
| logOutputFields | string | code,message | 日志输出的字段白名单 默认为false,代表不限制 |
| logOutputArrayNum | int | 3 | 输出字段子元素(数组)日志打印个数限制 默认为0 |
| logWebsocketAction | bool | false | 是否展示websocket的WSACTION请求日志 |
| compress | bool | false | 是否开启响应gzip压缩(包含静态资源) |
| compressMinSize | int | 1024 | 设置响应内容gzip压缩满足的最小尺寸 默认为1024Bytes |
| compressMaxSize | int | 4096000 | 设置响应内容gzip压缩满足的最大尺寸 默认为4096000Bytes |
| certFile | string | https签名证书文件路径 | |
| keyFile | string | https私钥证书文件路径 | |
| accessTokens | map | {"ad2dc32cde9" : 1} | 当前服务访问授权码,可以根据不同的授权等级设置多个 |
| acceptXRealIpWithoutRequestId | bool | false | 在没有X-Request-ID的情况下是否忽略 X-Real-IP false代表忽略 |
可在应用根目录放置一个 discover.json
{
"registry": "127.0.0.1:6379:15",
"app": "",
"weight": 1,
"calls": {
"s1": "5000:hasfjlkdlasfsa:2:s"
}
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| registry | string | 127.0.0.1:6379:15 | 服务发现redis的host、端口、数据库、密码、超时时间配置 用于服务注册与注销 |
| app | string | s1 | 可被发现的服务应用名 |
| weight | int | 2 | 负载均衡服务权重 |
| calls | string | 客户端访问服务的配置 {"s1":"5000:adfad"} {"s1":"5000:s"} {"s1":"adfad:s"} |
|
| callRetryTimes | int | 10 | 客户端访问服务失败重试次数 |
calls中包含:
timeout:accessToken:httpVersion:SSL
没有固定前后顺序,自动检查配置型
| 配置项 | 类型 | 不填默认 | 样例数据 | 说明 |
|---|---|---|---|---|
| timeout | int | 5000ms | 5000 | 如果未设置默认为10000(10秒),比如:":s1token" ":s1token:2" |
| accessToken | string | 空 | 5000 | 比如:"5000" |
| httpVersion | int | http 2.0 no ssl | 2 | 调用服务使用的http协议:1代表http1.1 2代表http2 |
| SSL | string | 不使用ssl | s | 是否使用https ssl 这里的值可以不填或者为s |
calls检查顺序:
- 1或2代表http 1.1 2.0 默认为2
- s代表https 没有s 表示不使用ssl
- 字符串代表token
- int类型代表超时的ms
- 其他类型为token
可在应用根目录放置一个 log.json
{
"level": "info",
"truncations": ["github.com/", "/ssgo/"],
"sensitive": ["password", "secure", "token", "accessToken"],
"regexSensitive": ["(^|[^\\d])(1\\d{10})([^\\d]|$)", "\\[(\\w+)\\]"],
"sensitiveRule": ["11:3*3", "7:2*2", "3:1*1", "2:1*0"]
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| level | string | info | 指定的日志输出级别 debug,info,warning,error |
| file | string | /dev/null | 日志文件 设置为nil,不展示日志 可以指定日志文件路径 不设置默认打向控制台 |
| truncations | []string | ["github.com/", "/ssgo/"] | 程序调用栈callStack字段忽略的目录 |
| sensitive | []string | ["password","token"] | 敏感字段 |
| regexSensitive | []string | ["\[(\w+)\]"] | 日志敏感信息正则匹配 |
| sensitiveRule | []string | ["11:33", "7:22", "3:11", "2:10"] | 敏感字段展示规则 |
可在应用根目录放置一个 redis.json
{
"test": {
"host": "127.0.0.1:6379",
"password": "",
"db": 1,
"maxActive": 100,
"maxIdles": 30,
"idleTimeout": 0,
"connTimeout": 3000,
"readTimeout": 0,
"writeTimeout": 0
},
"dev": {
"…":"…"
}
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| host | string | 127.0.0.1:6379 | host配置 |
| password | string | AES加密后的密码 | |
| db | int | 1 | 选择的数据库 |
| maxActive | int | 1 | 最大连接数 默认为0,代表不限制 |
| maxIdles | int | 10 | 最大空闲连接数,默认为0表示不限制 |
| idleTimeout | int 毫秒 |
10000 | keepalived激活时连接允许空闲的最大时间 默认为0,代表不限制 |
| connTimeout | int 毫秒 |
10000 | 连接超时时间,默认为10s |
| readTimeout | int 毫秒 |
10000 | 读超时时间,默认为10s |
| writeTimeout | int 毫秒 |
10000 | 写超时时间,默认为10s |
可以在项目根目录放置一个db.json来做mysql数据库的配置
{
"test": {
"type": "mysql",
"user": "test",
"password": "34RVCy0rQBSQmLX64xjoyg==",
"host": "/tmp/mysql.sock",
"db": "test",
"maxOpens": 100,
"maxIdles": 30,
"maxLifeTime": 0
}
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| type | string | mysql | 数据库类型 |
| user | string | test | 数据库名 |
| password | string | 经过AES加密的数据库密码 | |
| host | string | 可为sock文件路径或者mysql的地址、端口号 | |
| db | string | test | 数据库名 |
| maxOpens | int | 100 | 最大连接数,0表示不限制 |
| maxIdles | int | 30 | 最大空闲连接,0表示不限制 |
| maxLifeTime | int | 0 | 每个连接的存活时间,0表示永远 |
数据库密码加密可以保障不泄露,和redis加密方法完全相同
也可以以其他方式只要在 init 函数中调用 db.SetEncryptKeys 设置匹配的 key和iv即可
可以在项目根目录放置一个gateway.json来做网关代理配置
{
"checkInterval": 1,
"proxies": {
"/abc": "k1",
"/def":"g1",
"/cce":"g1"
}
}| 配置项 | 类型 | 样例数据 | 说明 |
|---|---|---|---|
| checkInterval | int |
10 | 每隔配置的秒数到redis中获取最新数据 最小配置值为3 |
| proxies | map | {"/abc": "k1"} | 路由到应用名的映射 |
proxies可以从环境变量、配置文件、redis中来获取。其中redis配置是动态配置,获取redis中_proxies的值,动态更新到gateway应用上。
可以在应用根目录使用env.json综合配置(service+discover+log+gateway+redis+db)服务:
{
"redis":{
"test":{
"host":"127.0.0.1:6379",
"password":"upvNALgTxwS/xUp2Cie4tg==",
"db":1
}
},
"service":{
"listen":":8081"
},
"discover": {
"app":"e1"
},
"log": {
"level": "info"
},
"db":{
"test": {
"type": "mysql",
"user": "root",
"password": "8wv3Kie3Y4nLArmSWs+hng==",
"host": "127.0.0.1:3306",
"db": "test"
}
}
}env.json的优先级高于其他配置文件。
如果同级目录下同时出现env和server配置文件,env的配置会对server配置进行覆盖。
以下是服务配置
export discover='{"app": "c1", "calls": {"s1":"5000:asfews:1"}}' export discover_app='c1' export discover_calls_s1='5000:asfews:2'
windows下:
set discover={"app": "c1", "calls": {"s1":"5000:asfews"}} set discover_app=c1 set discover_calls_s1=5000:asfews:2
以下是服务发现的redis配置
export discover='{"REGISTRY":"127.0.0.1:6379:1"}' export discover_registry='127.0.0.1:6379:1:udigzs+oTp2Kau3Gs20xXQ=='
windows下:
set discover={"registry":"127.0.0.1:6379:1"} set discover_registry=127.0.0.1:6379:udigzs+oTp2Kau3Gs20xXQ==
环境变量单项配置优先级大于总体配置
export service_calls='{"k1": {"accessToken": "s1token"}}' export service_calls_k1_accesstokens='s1-token'
service_calls_k1_accesstokens的配置会覆盖service_calls对k1服务accessToken的配置
cli设置环境变量(set/export) > 配置文件
服务调用客户端模式
// 调用已注册的服务,根据权重负载均衡 func (caller *Caller) Get(app, path string, headers ... string) *Result {} func (caller *Caller) Post(app, path string, data any, headers ... string) *Result {} func (caller *Caller) Put(app, path string, data any, headers ... string) *Result {} func (caller *Caller) Head(app, path string, data any, headers ... string) *Result {} func (caller *Caller) Delete(app, path string, data any, headers ... string) *Result {} func (caller *Caller) Do(app, path string, data any, headers ... string) *Result {}
// 指定节点调用已注册的服务,并返回本次使用的节点 func (caller *Caller) DoWithNode(method, app, withNode, path string, data any, headers ... string) (*Result, string) {} // 设置一个负载均衡算法 func SetLoadBalancer(lb LoadBalancer) {} type LoadBalancer interface { // 每个请求完成后提供信息 Response(node *NodeInfo, err error, response *http.Response, responseTimeing int64) // 请求时根据节点的得分取最小值发起请求 Next(nodes []*NodeInfo, request *http.Request) *NodeInfo }
使用json格式输出日志
func Debug(logType string, data Map) {} func Info(logType string, data Map) {} func Warning(logType string, data Map) {} func Error(logType string, data Map) {} func Log(logLevel LogLevelType, logType string, data Map) {} func TraceLog(logLevel LogLevelType, logType string, data Map) {}
// 生成文档数据 func MakeDocument() []Api {} // 生成文档并存储到 json 文件中 func MakeJsonDocumentFile(file string) { // 生成文档并存储到 html 文件中,使用默认html模版 func MakeHtmlDocumentFile(title, toFile string) string {} // 生成文档并存储到 html 文件中,使用指定html模版 func MakeHtmlDocumentFromFile(title, toFile, fromFile string) string {}
针对注册好的服务,可轻松实现文档的生成
s.Register(0, "/show1", show1) s.Register(0, "/show2", show2) s.Register(0, "/show3", show3) s.Register(0, "/show4", show4) s.MakeHtmlDocumentFile("测试文档", "doc.html")
生成html文档默认使用s框架根目录下的DocTpl.html作为模板,内部采用{{ }}标识语法
DocTpl.html可以作为新建模板的参考
假设编译好的文件为 ./server
// 直接输出 json 格式文档 ./server doc // 生成 json 格式文档 ./server doc xxx.json // 生成 html 格式文档,使用默认html模版 ./server doc xxx.html // 生成 html 格式文档,使用指定html模版 ./server doc xxx.html tpl.html