Skip to main content

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?"}
编写HTTP交互层(go-gin)
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?"}