Golang 动态调用方法 (JSON参数)
做过的后端 HTTP 业务一般用 JSON 来交互。到达服务层主动调用经常会用到 Plain Object 作参数。
像下表的规格:
JSON入参 | <方法名> | JSON出参 |
将 HTTP边际层 和 业务层 抽离,使得业务层可以专注处理入参出参,从而允许其他边际层(如: 命令行)调用服务,实现业务层松耦合。
业务层服务入参出参大部分是一个 JSON 对象参数,这样就可以利用 golang 的反射机制,通过方法名和入参JSON来调用方法,再将结果JSON返回。
Snippet
func CallJsonFunc(val interface{}, fnName string, jsonIn []byte) (jsonOut []byte) {
var EmptyReturn = []byte{}
valValue := reflect.ValueOf(val)
// get method
method := valValue.MethodByName(fnName)
if !method.IsValid() {
return EmptyReturn
}
// get method in
methodNumIn := method.Type().NumIn()
inValues := []reflect.Value{}
for index := 0; index < methodNumIn; index++ {
inType := method.Type().In(index)
inValueType := inType.Elem()
inValuePointer := reflect.New(inValueType)
if index == 0 { // ctor from json
_ = json.Unmarshal(jsonIn, inValuePointer.Interface())
} // else new zero value
inValues = append(inValues, inValuePointer)
}
// call method
outValues := method.Call(inValues)
if len(outValues) == 0 {
return EmptyReturn
}
// marshal out
outValue := outValues[0]
jsonOut, _ = json.Marshal(outValue.Interface())
return
}
示例
type AccountService struct{}
type AiSatuRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Social struct {
From string `json:"from"`
} `json:"social"`
}
type AiSatuResponse struct {
Code int `json:"code"`
Result string `json:"result"`
}
func (*AccountService) AiSatu(req *AiSatuRequest) *AiSatuResponse {
return &AiSatuResponse{
Code: 0,
Result: fmt.Sprintf("HI, %s, what were you do at %s just now?", req.Username, req.Social.From),
}
}
func main() {
svc := new(AccountService) // 新建用户服务
FUNC := os.Getenv("FUNC") // 读环境变量 FUNC 表示方法名
CTOR := os.Getenv("CTOR") // 读环境变量 CTOR 表示入参
fmt.Println(string(CallJsonFunc(svc, FUNC, []byte(CTOR))))
}
# 用命令行访问
FUNC=AiSatu CTOR='{ "username": "luo", "social": { "from": "Twitter" } }' go run main.go
{"code":0,"result":"HI, luo, what were you do at Twitter just now?"}
func main() {
svc := new(AccountService)
// github.com/gin-gonic/gin
server := gin.New()
server.POST("/aisatu", func(ctx *gin.Context) {
// http handle
var req AiSatuRequest
ctx.ShouldBindJSON(&req)
// call svc by plain object
var resp *AiSatuResponse = svc.AiSatu(&req)
// http write
ctx.JSON(http.StatusOK, resp)
})
server.Run(":9662")
}
# 用 HTTP 访问
$ curl -XPOST \
-H 'Content-Type: application/json' \
-d '{ "username": "luo", "social": { "from": "Twitter" } }' \
--url localhost:9662/aisatu
{"code":0,"result":"HI, luo, what were you do at Twitter just now?"}