diff --git a/.gitignore b/.gitignore index 7e3dc02db28f7c7ab91e222b676a27cafe8b654e..6bc22006c24f5c36ff3e964c66e76a9a63ef21ac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ go.sum __debug_bin +**/vendor/** diff --git a/examples/grafanaalertwebhook/go.mod b/examples/grafanaalertwebhook/go.mod index 641f4cbc0266627efc42c4f731cc89c50d84c12a..ab074bb23c2f3454205052349805d704aa06fb48 100644 --- a/examples/grafanaalertwebhook/go.mod +++ b/examples/grafanaalertwebhook/go.mod @@ -3,8 +3,8 @@ module grafanaalertwebhook go 1.16 require ( - github.com/yoyofxteam/dependencyinjection v1.0.0 github.com/yoyofx/yoyogo v0.0.0 + gopkg.in/go-playground/assert.v1 v1.2.1 ) replace github.com/yoyofx/yoyogo => ../../ diff --git a/examples/grafanaalertwebhook/tests/unit_test.go b/examples/grafanaalertwebhook/tests/unit_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bd8457a2fb8fee2968cdaf3c4260fa64aae1ce7d --- /dev/null +++ b/examples/grafanaalertwebhook/tests/unit_test.go @@ -0,0 +1,32 @@ +package tests + +import ( + "encoding/json" + "gopkg.in/go-playground/assert.v1" + "grafanaalertwebhook/wechatrequests" + "testing" +) + +func TestMessage(t *testing.T) { + + var message *wechatrequests.MarkdownMessage + message = &wechatrequests.MarkdownMessage{ + Markdown: struct { + Content string `json:"content" gorm:"column:content"` + }{ + Content: "## " + "test" + ",请相关同事注意。\n" + + " > [报警信息] : " + "test" + "\n" + + " > [报警次数] : " + "0" + "次" + "\n" + + " > [报警明细] : (" + "app" + ")\n", + }, + Msgtype: "markdown", + } + + msg, _ := json.Marshal(message) + msgStr := string(msg) + + //return sendUrl + msgStr + dd := wechatrequests.PostWechatMessage("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=93fb0726-9794-49f5-b10f-dac3c85396da", msgStr) + + assert.Equal(t, dd != "", true) +} diff --git a/examples/grafanaalertwebhook/wechatrequests/qiyewechat.go b/examples/grafanaalertwebhook/wechatrequests/qiyewechat.go index 34e366befeb41b3d9999c05be38c2e2c6de617bc..c3ac64006a1592792d49b4a3bdd9ed61106c9ef0 100644 --- a/examples/grafanaalertwebhook/wechatrequests/qiyewechat.go +++ b/examples/grafanaalertwebhook/wechatrequests/qiyewechat.go @@ -10,7 +10,7 @@ import ( "net/http" ) -func postWechatMessage(sendUrl, msg string) string { +func PostWechatMessage(sendUrl, msg string) string { client := &http.Client{} req, _ := http.NewRequest("POST", sendUrl, bytes.NewBuffer([]byte(msg))) req.Header.Set("Content-Type", "application/json") @@ -61,5 +61,5 @@ func SendTxtMessage(request GrafanaAlertRequest, config abstractions.IConfigurat logger.Info("send message:%s", msgStr) //return sendUrl + msgStr - return postWechatMessage(sendUrl, msgStr) + return PostWechatMessage(sendUrl, msgStr) } diff --git a/examples/simpleweb/Dockerfile b/examples/simpleweb/Dockerfile index 772633cbb9adc1d6b95ad443697edc56270eba9f..f83491d50dacb0366765ab779fd6655ecd2c8890 100644 --- a/examples/simpleweb/Dockerfile +++ b/examples/simpleweb/Dockerfile @@ -16,8 +16,10 @@ COPY . . WORKDIR /build/examples/simpleweb RUN ls -a # 将我们的代码编译成二进制可执行文件 app -RUN go mod download -RUN go build -o app . + +RUN go mod download \ + && go mod tidy \ + && go build -o app . ################### # 接下来创建一个小镜像 diff --git a/examples/simpleweb/Dockerfile_NoCompile b/examples/simpleweb/Dockerfile_NoCompile new file mode 100644 index 0000000000000000000000000000000000000000..4bd71519ce3bdec86c6c42fd5f25f449ca31d191 --- /dev/null +++ b/examples/simpleweb/Dockerfile_NoCompile @@ -0,0 +1,22 @@ +FROM alpine + +#更新Alpine的软件源为国内源,提高下载速度 +RUN echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.4/main/" > /etc/apk/repositories + +RUN apk update \ + && apk upgrade \ + && apk add --no-cache bash \ + bash-doc \ + bash-completion \ + && rm -rf /var/cache/apk/* \ + && /bin/bash +# 设置时区为上海 +RUN apk -U add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && echo "Asia/Shanghai" > /etc/timezone \ + && apk del tzdata + +COPY ./app / +COPY ./config_dev.yml / + + +ENTRYPOINT ["/app"] \ No newline at end of file diff --git a/examples/simpleweb/config_dev.yml b/examples/simpleweb/config_dev.yml index 1c5f0090b342b07b9333a80106b42b29a2b7dccf..8d1994687352bfa320e6e295f349edb2a7cb2334 100644 --- a/examples/simpleweb/config_dev.yml +++ b/examples/simpleweb/config_dev.yml @@ -46,17 +46,17 @@ yoyogo: strategy: "round-robin" # round-robin , weight-time , random type: "nacos" metadata: - url: "nacos.yoyogo.run" - port: 80 + url: "47.100.213.49" + port: 8848 namespace: "public" group: "DEFAULT_GROUP" cluster: "" configserver: dataId: "simple_demo" auth: - enable: true + enable: false username: "nacos" - password: "nacos" + password: "P@ssW0rd" endpoint: "" regionId: "" accessKey: "" diff --git a/examples/simpleweb/contollers/usercontroller.go b/examples/simpleweb/contollers/usercontroller.go index 4be9bb692c2b409853cc8f8b4b77c5853edd5aa9..191aaa64b6fe920aeda4d07bc4092ed8074dc357 100644 --- a/examples/simpleweb/contollers/usercontroller.go +++ b/examples/simpleweb/contollers/usercontroller.go @@ -1,7 +1,6 @@ package contollers import ( - "fmt" "github.com/yoyofx/yoyogo/abstractions/servicediscovery" "github.com/yoyofx/yoyogo/web/actionresult" "github.com/yoyofx/yoyogo/web/binding" @@ -23,22 +22,25 @@ func NewUserController(userAction models.IUserAction, sd servicediscovery.IServi } type RegisterRequest struct { - mvc.RequestBody - UserName string `param:"UserName"` - Password string `param:"Password"` + mvc.RequestBody `route:"/{id}/{tenant}"` + + UserName string `uri:"userName"` + Password string `uri:"password"` } func (controller UserController) Register(ctx *context.HttpContext, request *RegisterRequest) mvc.ApiResult { return mvc.ApiResult{Success: true, Message: "ok", Data: request} } -func (controller UserController) GetUserName(ctx *context.HttpContext, request *RegisterRequest) actionresult.IActionResult { - result := mvc.ApiResult{Success: true, Message: "ok", Data: request} - fmt.Println("hello world") - return actionresult.Json{Data: result} +type PostUserInfoRequest struct { + mvc.RequestBody //`route:"/{id}"` + + UserName string `form:"userName" json:"userName"` + Password string `form:"password" json:"password"` + Token string `header:"Authorization" json:"token"` } -func (controller UserController) PostUserInfo(ctx *context.HttpContext, request *RegisterRequest) actionresult.IActionResult { +func (controller UserController) PostUserInfo(ctx *context.HttpContext, request *PostUserInfoRequest) actionresult.IActionResult { return actionresult.Json{Data: mvc.ApiResult{Success: true, Message: "ok", Data: context.H{ "user": ctx.GetUser(), "request": request, diff --git a/examples/simpleweb/main.go b/examples/simpleweb/main.go index 28df4305097f82d31e03f17159aebe4e02f7a5f2..d3925b05608600778ccb28ec21e7259d3345fafb 100644 --- a/examples/simpleweb/main.go +++ b/examples/simpleweb/main.go @@ -6,7 +6,6 @@ import ( "github.com/yoyofx/yoyogo/abstractions" "github.com/yoyofx/yoyogo/abstractions/xlog" "github.com/yoyofx/yoyogo/pkg/configuration" - nacosconfig "github.com/yoyofx/yoyogo/pkg/configuration/nacos" _ "github.com/yoyofx/yoyogo/pkg/datasources/mysql" _ "github.com/yoyofx/yoyogo/pkg/datasources/redis" "github.com/yoyofx/yoyogo/pkg/servicediscovery/nacos" @@ -45,8 +44,9 @@ func main() { //* Create the builder of Web host func CreateCustomBuilder() *abstractions.HostBuilder { - config := nacosconfig.RemoteConfig("config") + //config := nacosconfig.RemoteConfig("config") //config := apollo.RemoteConfig("config") + config := configuration.LocalConfig("config") return web.NewWebHostBuilder(). UseConfiguration(config). Configure(func(app *web.ApplicationBuilder) { diff --git a/pkg/configuration/localconfig.go b/pkg/configuration/localconfig.go new file mode 100644 index 0000000000000000000000000000000000000000..cc03f06824a50e1d83d9a7eb8b4475b3d7da5ce6 --- /dev/null +++ b/pkg/configuration/localconfig.go @@ -0,0 +1,7 @@ +package configuration + +import "github.com/yoyofx/yoyogo/abstractions" + +func LocalConfig(configPath string) *abstractions.Configuration { + return abstractions.NewConfigurationBuilder().AddEnvironment().AddYamlFile(configPath).Build() +} diff --git a/web/binding/query_binding.go b/web/binding/query_binding.go index be23e80965b0afe4f5e742e8ad2efae2f724f5df..949308c6bace4f5dbda5cbe6fa4f423a7736657b 100644 --- a/web/binding/query_binding.go +++ b/web/binding/query_binding.go @@ -14,7 +14,7 @@ func (queryBinding) Name() string { func (queryBinding) Bind(req *http.Request, obj interface{}) error { values := req.URL.Query() fmt.Println(values) - if err := mapForm(obj, values); err != nil { + if err := mapUri(obj, values); err != nil { return err } return validate(obj) diff --git a/web/mvc/action_method_executor.go b/web/mvc/action_method_executor.go index 35885ca4ab2f4fd44a2df3dd00d5a7bb418f31fb..d167fac96f68da72f4d71f411b3b513c9a5467fa 100644 --- a/web/mvc/action_method_executor.go +++ b/web/mvc/action_method_executor.go @@ -2,7 +2,6 @@ package mvc import ( "errors" - "fmt" "github.com/yoyofx/yoyogo/abstractions/xlog" "github.com/yoyofx/yoyogo/web/context" "github.com/yoyofxteam/reflectx" @@ -20,7 +19,7 @@ func NewActionMethodExecutor() ActionMethodExecutor { func (actionExecutor ActionMethodExecutor) Execute(ctx *ActionExecutorContext) interface{} { if ctx.Controller != nil { methodInfo := ctx.ActionDescriptor.MethodInfo - values := getParamValues(methodInfo.Parameters, ctx.Context) + values := actionExecutor.getParamValues(methodInfo.Parameters, ctx.Context) returns := methodInfo.InvokeWithValue(reflect.ValueOf(ctx.Controller), values...) if len(returns) > 0 { responseData := returns[0] @@ -31,7 +30,7 @@ func (actionExecutor ActionMethodExecutor) Execute(ctx *ActionExecutorContext) i return nil } -func getParamValues(paramList []reflectx.MethodParameterInfo, ctx *context.HttpContext) []reflect.Value { +func (actionExecutor ActionMethodExecutor) getParamValues(paramList []reflectx.MethodParameterInfo, ctx *context.HttpContext) []reflect.Value { if len(paramList) == 0 { return nil } @@ -41,36 +40,51 @@ func getParamValues(paramList []reflectx.MethodParameterInfo, ctx *context.HttpC continue } val, err := requestParamTypeConvertFunc(index, param, ctx) - if err == nil { - values[index-1] = val + if err != nil { + actionExecutor.logger.Error(err.Error()) // throw binding error } + values[index-1] = val } - return values } func requestParamTypeConvertFunc(index int, parameter reflectx.MethodParameterInfo, ctx *context.HttpContext) (reflect.Value, error) { var value reflect.Value var err error = nil + paramType := parameter.ParameterType if paramType.Kind() == reflect.Ptr { paramType = paramType.Elem() } if paramType.Kind() == reflect.Struct { - switch paramType.Name() { - case "HttpContext": - value = reflect.ValueOf(ctx) - default: - if paramType.NumField() > 0 && paramType.Field(0).Name == "RequestBody" { - reqBindingData := reflect.New(paramType).Interface() - bindErr := ctx.Bind(reqBindingData) - if bindErr != nil { - fmt.Println(bindErr) - } - value = reflect.ValueOf(reqBindingData) - } + // Mapping * struct type -> parameter.Name , paramType.Name() ,paramType, ctx + mappingFunc, hasMapping := RequestMppingFuncs[paramType.Name()] + if hasMapping { + value, err = mappingFunc(parameter.Name, paramType.Name(), paramType, ctx) + } else { + value, err = RequestMppingFuncs["Default"](parameter.Name, paramType.Name(), paramType, ctx) } + + //switch paramType.Name() { + //case "HttpContext": + // value = reflect.ValueOf(ctx) + //default: + // reqBindingData := reflect.New(paramType).Interface() + // if paramType.NumField() > 0 && paramType.Field(0).Name == "RequestBody" { + // bindErr := ctx.Bind(reqBindingData) + // bindErr2:= ctx.BindWith(reqBindingData, binding.Query) + // if bindErr != nil || bindErr2!=nil { + // panic(bindErr.Error() + bindErr2.Error()) + // } + // } else { + // err = errors.New("Can't bind non mvc.RequestBody!") + // } + // value = reflect.ValueOf(reqBindingData) + //} return value, err + } else { + // normal type , such as int ,string, float } + return value, errors.New("the type not support") } diff --git a/web/mvc/action_parameters_mapping_func.go b/web/mvc/action_parameters_mapping_func.go new file mode 100644 index 0000000000000000000000000000000000000000..74c60f31aff436ad71f9bb6b285c13d7a07eb6dc --- /dev/null +++ b/web/mvc/action_parameters_mapping_func.go @@ -0,0 +1,62 @@ +package mvc + +import ( + "errors" + "github.com/yoyofx/yoyogo/web/binding" + "github.com/yoyofx/yoyogo/web/context" + "reflect" +) + +var RequestMppingFuncs map[string]ActionParametersMappingFunc = map[string]ActionParametersMappingFunc{ + "HttpContext": httpContextMappingMapping, + "Default": requestBodyMappingMapping, +} + +type ActionParametersMappingFunc func(paramName string, paramTypeName string, paramType reflect.Type, sourceContext *context.HttpContext) (reflect.Value, error) + +func httpContextMappingMapping(paramName string, paramTypeName string, paramType reflect.Type, sourceContext *context.HttpContext) (reflect.Value, error) { + var value reflect.Value + var err error + if paramTypeName == "HttpContext" { + value = reflect.ValueOf(sourceContext) + } else { + err = errors.New("not HttpContext ") + } + return value, err +} + +/** + 绑定 +form-data/multipart-form , json , uri , header +tags: json , form , uri ,header +*/ +func requestBodyMappingMapping(paramName string, paramTypeName string, paramType reflect.Type, sourceContext *context.HttpContext) (reflect.Value, error) { + var value reflect.Value + var err error + reqBindingData := reflect.New(paramType).Interface() + + fmTags := map[string]bool{"header": false, "uri": false} + for fi := 0; fi < paramType.NumField(); fi++ { + for key, _ := range fmTags { + _, inTag := paramType.Field(fi).Tag.Lookup(key) + if inTag { + fmTags[key] = inTag + continue + } + } + } + + if paramType.NumField() > 0 && paramType.Field(0).Name == "RequestBody" { + err = sourceContext.Bind(reqBindingData) + if fmTags["uri"] { + _ = sourceContext.BindWith(reqBindingData, binding.Query) + } else if fmTags["header"] { + _ = sourceContext.BindWith(reqBindingData, binding.Header) + } + + } else { + err = errors.New("Can't bind non mvc.RequestBody!") + } + value = reflect.ValueOf(reqBindingData) + return value, err +}