├── .github └── workflows │ └── go.yml ├── LICENSE ├── action └── action.go ├── generate ├── bean.go ├── bean_test.go ├── converter.go ├── dir.go ├── generate.go ├── generate_test.go ├── iservice.go ├── iservice_test.go ├── service.go └── service_test.go ├── go.mod ├── goctl-android.go ├── readme.md └── template ├── bean.go ├── iservice.go └── service.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.13 20 | 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v2 23 | 24 | - name: Get dependencies 25 | run: | 26 | go get -v -t -d ./... 27 | if [ -f Gopkg.toml ]; then 28 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 29 | dep ensure 30 | fi 31 | 32 | - name: Build 33 | run: go build -v ./... 34 | 35 | - name: Test 36 | run: go test -v ./... 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 zeromicro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /action/action.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package action 16 | 17 | import ( 18 | "encoding/json" 19 | "io/ioutil" 20 | "os" 21 | 22 | "github.com/urfave/cli/v2" 23 | "github.com/zeromicro/goctl-android/generate" 24 | ) 25 | 26 | func Android(ctx *cli.Context) error { 27 | pkg := ctx.String("package") 28 | std, err := ioutil.ReadAll(os.Stdin) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | var plugin generate.Plugin 34 | plugin.ParentPackage = pkg 35 | err = json.Unmarshal(std, &plugin) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return generate.Do(plugin) 41 | } 42 | -------------------------------------------------------------------------------- /generate/bean.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "fmt" 19 | "path/filepath" 20 | "strings" 21 | 22 | "github.com/tal-tech/go-zero/tools/goctl/util" 23 | "github.com/tal-tech/go-zero/tools/goctl/util/stringx" 24 | "github.com/zeromicro/goctl-android/template" 25 | ) 26 | 27 | type ( 28 | Bean struct { 29 | ParentPackage string 30 | Name stringx.String 31 | Import string 32 | Members []*Member 33 | PathTag []string 34 | FormTag []string 35 | JsonTag []string 36 | } 37 | Member struct { 38 | Name stringx.String 39 | TypeName string 40 | Comment string 41 | Doc string 42 | } 43 | ) 44 | 45 | func (b *Bean) IsJsonRequest() bool { 46 | return len(b.JsonTag) > 0 47 | } 48 | 49 | func (b *Bean) IsFormRequest() bool { 50 | return len(b.FormTag) > 0 51 | } 52 | 53 | func (b *Bean) HavePath() bool { 54 | return len(b.PathTag) > 0 55 | } 56 | 57 | func (b *Bean) GetQuery() string { 58 | var query []string 59 | for _, item := range b.FormTag { 60 | m := b.GetMember(item) 61 | if m == nil { 62 | continue 63 | } 64 | query = append(query, fmt.Sprintf(`@Query("%s") %s %s`, item, m.TypeName, m.Name.Untitle())) 65 | } 66 | return strings.Join(query, ", ") 67 | } 68 | 69 | func (b *Bean) GetMember(name string) *Member { 70 | for _, item := range b.Members { 71 | if item.Name.Lower() == strings.ToLower(name) { 72 | return item 73 | } 74 | } 75 | return nil 76 | } 77 | 78 | func generateBean(dir string, bean Bean) error { 79 | filename := filepath.Join(dir, bean.Name.ToCamel()+".java") 80 | base := filepath.Dir(filename) 81 | err := util.MkdirIfNotExist(base) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | return util.With("bean").Parse(template.Bean).SaveTo(bean, filename, true) 87 | } 88 | -------------------------------------------------------------------------------- /generate/bean_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "github.com/tal-tech/go-zero/tools/goctl/util/stringx" 22 | ) 23 | 24 | func TestGenerateBean(t *testing.T) { 25 | err := generateBean(t.TempDir(), Bean{ 26 | ParentPackage: "com.tal", 27 | Name: stringx.From("user"), 28 | Members: []*Member{ 29 | { 30 | Name: stringx.From("id"), 31 | TypeName: "String", 32 | }, 33 | { 34 | Name: stringx.From("name"), 35 | TypeName: "String", 36 | Comment: "// 姓名", 37 | Doc: "// 姓名", 38 | }, 39 | { 40 | Name: stringx.From("age"), 41 | TypeName: "int", 42 | Doc: "// 年龄", 43 | }, 44 | { 45 | Name: stringx.From("birthday"), 46 | TypeName: "String", 47 | Doc: "// 生日", 48 | }, 49 | }, 50 | PathTag: []string{"id"}, 51 | JsonTag: []string{"name", "age", "birthday"}, 52 | }) 53 | assert.Nil(t, err) 54 | } 55 | -------------------------------------------------------------------------------- /generate/converter.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "sort" 21 | "strings" 22 | 23 | "github.com/tal-tech/go-zero/core/collection" 24 | sx "github.com/tal-tech/go-zero/core/stringx" 25 | "github.com/tal-tech/go-zero/tools/goctl/api/spec" 26 | annotation "github.com/tal-tech/go-zero/tools/goctl/api/util" 27 | "github.com/tal-tech/go-zero/tools/goctl/util" 28 | "github.com/tal-tech/go-zero/tools/goctl/util/stringx" 29 | ) 30 | 31 | type ( 32 | Plugin struct { 33 | Api *spec.ApiSpec 34 | Style string 35 | Dir string 36 | ParentPackage string 37 | } 38 | Spec struct { 39 | Beans []*Bean 40 | Service IService 41 | } 42 | 43 | Tag struct { 44 | tp string 45 | name string 46 | } 47 | Type struct { 48 | Name string 49 | } 50 | ) 51 | 52 | func (p *Plugin) SetParentPackage(parentPackage string) { 53 | p.ParentPackage = parentPackage 54 | } 55 | 56 | func (p *Plugin) Convert() (*Spec, error) { 57 | var ( 58 | ret Spec 59 | beans = make(map[string]*Bean) 60 | ) 61 | 62 | for _, each := range p.Api.Types { 63 | list, err := getBean(p.ParentPackage, each) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | for _, bean := range list { 69 | beans[bean.Name.Lower()] = bean 70 | } 71 | } 72 | var r = make(map[string]spec.Route) 73 | for _, each := range p.Api.Service.Routes() { 74 | r[fmt.Sprintf("%v://%v", each.Method, each.Path)] = each 75 | } 76 | 77 | for _, each := range p.Api.Service.Groups { 78 | for _, each := range each.Routes { 79 | r[fmt.Sprintf("%v://%v", each.Method, each.Path)] = each 80 | } 81 | } 82 | var list []spec.Route 83 | for _, item := range r { 84 | list = append(list, item) 85 | } 86 | 87 | imports, routes := getRoute(list, beans) 88 | ret.Service = IService{ 89 | ParentPackage: p.ParentPackage, 90 | Import: strings.Join(trimList(imports), util.NL), 91 | Routes: routes, 92 | } 93 | 94 | for _, each := range beans { 95 | ret.Beans = append(ret.Beans, each) 96 | } 97 | 98 | sort.Slice(ret.Beans, func(i, j int) bool { 99 | return ret.Beans[i].Name.Source() < ret.Beans[j].Name.Source() 100 | }) 101 | 102 | sort.Slice(ret.Service.Routes, func(i, j int) bool { 103 | return ret.Service.Routes[i].Path < ret.Service.Routes[j].Path 104 | }) 105 | 106 | return &ret, nil 107 | } 108 | 109 | func trimList(list []string) []string { 110 | var ret []string 111 | for _, each := range list { 112 | tmp := strings.TrimSpace(each) 113 | if len(tmp) == 0 { 114 | continue 115 | } 116 | ret = append(ret, tmp) 117 | } 118 | return ret 119 | } 120 | 121 | func getRoute(in []spec.Route, m map[string]*Bean) ([]string, []*Route) { 122 | var list []*Route 123 | var imports []string 124 | 125 | for _, each := range in { 126 | handlerName, ok := annotation.GetAnnotationValue(each.Annotations, "server", "handler") 127 | if !ok { 128 | continue 129 | } 130 | 131 | doc, _ := annotation.GetAnnotationValue(each.Annotations, "doc", "summary") 132 | if len(doc) > 0 { 133 | doc = strings.ReplaceAll(doc, "'", "") 134 | doc = strings.ReplaceAll(doc, "`", "") 135 | doc = strings.ReplaceAll(doc, `"`, "") 136 | doc = "// " + doc 137 | } 138 | 139 | path, ids, idsExpr := parsePath(each.Path) 140 | bean := m[strings.ToLower(each.RequestType.Name)] 141 | 142 | var queryId []string 143 | var queryExpr, pathIdExpr string 144 | var showRequestBody bool 145 | if bean != nil { 146 | imports = append(imports, bean.Import) 147 | for _, query := range bean.FormTag { 148 | queryId = append(queryId, "in.get"+stringx.From(query).ToCamel()+"()") 149 | } 150 | queryExpr = bean.GetQuery() 151 | showRequestBody = len(bean.JsonTag) > 0 152 | if showRequestBody { 153 | queryExpr = queryExpr + ", " 154 | } 155 | pathIdExpr = toRetrofitPath(ids, bean) 156 | if len(queryId) > 0 { 157 | pathIdExpr = pathIdExpr + ", " 158 | } 159 | } 160 | 161 | list = append(list, &Route{ 162 | MethodName: stringx.From(handlerName).Untitle(), 163 | Method: strings.ToUpper(each.Method), 164 | Path: path, 165 | RequestBeanName: stringx.From(each.RequestType.Name).Title(), 166 | ResponseBeanName: stringx.From(each.ResponseType.Name).Title(), 167 | HasRequest: len(each.RequestType.Name) > 0, 168 | ShowRequestBody: showRequestBody, 169 | HasResponse: len(each.ResponseType.Name) > 0, 170 | HavePath: len(ids) > 0, 171 | PathId: strings.Join(idsExpr, ","), 172 | PathIdExpr: pathIdExpr, 173 | QueryId: strings.Join(queryId, ","), 174 | HaveQuery: len(queryId) > 0, 175 | QueryIdExpr: queryExpr, 176 | Doc: doc, 177 | }) 178 | } 179 | return imports, list 180 | } 181 | 182 | func parsePath(path string) (string, []string, []string) { 183 | p := strings.Split(path, "/") 184 | var list, ids, idsExpr []string 185 | for _, each := range p { 186 | if strings.Contains(each, ":") { 187 | id := strings.ReplaceAll(each, ":", "") 188 | list = append(list, "{"+id+"}") 189 | ids = append(ids, id) 190 | idsExpr = append(idsExpr, "in.get"+stringx.From(id).ToCamel()+"()") 191 | continue 192 | } 193 | 194 | list = append(list, each) 195 | } 196 | return strings.Join(list, "/"), ids, idsExpr 197 | } 198 | 199 | func toRetrofitPath(ids []string, bean *Bean) string { 200 | if bean == nil { 201 | return "" 202 | } 203 | var list []string 204 | for _, each := range ids { 205 | m := bean.GetMember(each) 206 | if m == nil { 207 | continue 208 | } 209 | 210 | list = append(list, fmt.Sprintf(`@Path("%s") %s %s`, each, m.TypeName, each)) 211 | } 212 | return strings.Join(list, ", ") 213 | } 214 | 215 | func getBean(parentPackage string, tp spec.Type) ([]*Bean, error) { 216 | var bean Bean 217 | var list []*Bean 218 | bean.Name = stringx.From(tp.Name) 219 | bean.ParentPackage = parentPackage 220 | 221 | for _, m := range tp.Members { 222 | externalBeans, err := getBeans(parentPackage, m, &bean) 223 | if err != nil { 224 | return nil, err 225 | } 226 | 227 | list = append(list, externalBeans...) 228 | } 229 | return list, nil 230 | } 231 | 232 | func getBeans(parentPackage string, member spec.Member, bean *Bean) ([]*Bean, error) { 233 | beans, imports, typeName, err := getTypeName(parentPackage, member.Expr, member.Type == "interface{}") 234 | if err != nil { 235 | return nil, err 236 | } 237 | 238 | tag := NewTag(member.Tag) 239 | name := tag.GetTag() 240 | if tag.IsJson() { 241 | bean.JsonTag = append(bean.JsonTag, name) 242 | } 243 | if tag.IsPath() { 244 | bean.PathTag = append(bean.PathTag, name) 245 | } 246 | if tag.IsForm() { 247 | bean.FormTag = append(bean.FormTag, name) 248 | } 249 | 250 | bean.Import = strings.Join(imports, util.NL) 251 | comment := strings.Join(member.Comments, " ") 252 | doc := strings.Join(member.Docs, util.NL) 253 | if len(comment) > 0 { 254 | comment = "// " + comment 255 | } 256 | if len(doc) > 0 { 257 | doc = "// " + doc 258 | } 259 | bean.Members = append(bean.Members, &Member{ 260 | Name: stringx.From(name), 261 | TypeName: typeName, 262 | Comment: comment, 263 | Doc: doc, 264 | }) 265 | beans = append(beans, bean) 266 | return beans, nil 267 | } 268 | 269 | func getTypeName(parentPackage string, expr interface{}, inter bool) ([]*Bean, []string, string, error) { 270 | set := collection.NewSet() 271 | switch v := expr.(type) { 272 | case map[string]interface{}: 273 | return getTypeName(parentPackage, unJsonMarshal(expr, inter), false) 274 | case spec.BasicType: 275 | imp, typeName := toJavaType(parentPackage, v.Name) 276 | set.AddStr(imp) 277 | return nil, set.KeysStr(), typeName, nil 278 | case spec.PointerType: 279 | return getTypeName(parentPackage, v.Star, false) 280 | case spec.MapType: 281 | set.AddStr("import java.util.HashMap;") 282 | beans, imports, typeName, err := toJavaMap(parentPackage, v) 283 | if err != nil { 284 | return nil, nil, "", err 285 | } 286 | 287 | set.AddStr(imports...) 288 | return beans, set.KeysStr(), typeName, nil 289 | case spec.ArrayType: 290 | set.AddStr("import java.util.ArrayList;") 291 | beans, imports, typeName, err := toJavaArray(parentPackage, v) 292 | if err != nil { 293 | return nil, nil, "", err 294 | } 295 | 296 | set.AddStr(imports...) 297 | return beans, set.KeysStr(), typeName, nil 298 | case spec.InterfaceType: 299 | return nil, nil, "Object", nil 300 | case spec.Type: 301 | beans, err := getBean(parentPackage, v) 302 | if err != nil { 303 | return nil, nil, "", err 304 | } 305 | 306 | imp, typeName := toJavaType(parentPackage, v.Name) 307 | set.AddStr(imp) 308 | return beans, set.KeysStr(), typeName, nil 309 | case Type: 310 | return nil, nil, v.Name, nil 311 | default: 312 | return nil, nil, "", fmt.Errorf("unsupported type: %v", v) 313 | } 314 | } 315 | 316 | func unJsonMarshal(expr interface{}, inter bool) interface{} { 317 | m := expr.(map[string]interface{}) 318 | data, err := json.Marshal(expr) 319 | if err != nil { 320 | return expr 321 | } 322 | 323 | var basicType spec.BasicType 324 | var pointerType spec.PointerType 325 | var mapType spec.MapType 326 | var arrayType spec.ArrayType 327 | var interfaceType spec.InterfaceType 328 | var tpType Type 329 | 330 | var keys []string 331 | for key := range m { 332 | keys = append(keys, key) 333 | } 334 | 335 | if sx.Contains(keys, "StringExpr") && sx.Contains(keys, "Name") && len(keys) == 2 { 336 | _ = json.Unmarshal(data, &basicType) 337 | return basicType 338 | } 339 | if sx.Contains(keys, "StringExpr") && sx.Contains(keys, "Star") && len(keys) == 2 { 340 | _ = json.Unmarshal(data, &pointerType) 341 | return pointerType 342 | } 343 | if sx.Contains(keys, "StringExpr") && sx.Contains(keys, "Key") && sx.Contains(keys, "Value") && len(keys) == 3 { 344 | _ = json.Unmarshal(data, &mapType) 345 | return mapType 346 | } 347 | if sx.Contains(keys, "StringExpr") && sx.Contains(keys, "ArrayType") && len(keys) == 2 { 348 | _ = json.Unmarshal(data, &arrayType) 349 | return arrayType 350 | } 351 | if sx.Contains(keys, "StringExpr") && len(keys) == 1 { 352 | if inter { 353 | _ = json.Unmarshal(data, &interfaceType) 354 | return interfaceType 355 | } 356 | tpType.Name = fmt.Sprintf("%v", m[keys[0]]) 357 | return tpType 358 | } 359 | 360 | return expr 361 | } 362 | 363 | func toJavaArray(parentPackage string, a spec.ArrayType) ([]*Bean, []string, string, error) { 364 | beans, imports, typeName, err := getTypeName(parentPackage, a.ArrayType, false) 365 | if err != nil { 366 | return nil, nil, "", err 367 | } 368 | 369 | return beans, imports, fmt.Sprintf("ArrayList<%s>", typeName), nil 370 | } 371 | 372 | func toJavaMap(parentPackage string, m spec.MapType) ([]*Bean, []string, string, error) { 373 | beans, imports, typeName, err := getTypeName(parentPackage, m.Value, false) 374 | if err != nil { 375 | return nil, nil, "", err 376 | } 377 | 378 | return beans, imports, fmt.Sprintf("HashMap", typeName), nil 379 | } 380 | 381 | func toJavaType(parentPackage, goType string) (string, string) { 382 | switch goType { 383 | case "bool": 384 | return "", "boolean" 385 | case "uint8", "uint16", "uint32", "int8", "int16", "int32", "int", "uint", "byte": 386 | return "", "int" 387 | case "uint64", "int64": 388 | return "", "long" 389 | case "float32": 390 | return "", "float" 391 | case "float64": 392 | return "", "double" 393 | case "string": 394 | return "", "String" 395 | default: 396 | return fmt.Sprintf("import %s.bean.%s;", parentPackage, goType), goType 397 | } 398 | } 399 | 400 | func NewTag(tagExpr string) *Tag { 401 | tagExpr = strings.ReplaceAll(tagExpr, "`", "") 402 | tagExpr = strings.ReplaceAll(tagExpr, `"`, "") 403 | commaIndex := strings.Index(tagExpr, ",") 404 | if commaIndex > 0 { 405 | tagExpr = tagExpr[:commaIndex] 406 | } 407 | splits := strings.Split(tagExpr, ":") 408 | var ( 409 | tp, name string 410 | ) 411 | if len(splits) == 2 { 412 | tp = splits[0] 413 | name = splits[1] 414 | } 415 | 416 | return &Tag{ 417 | tp: tp, 418 | name: name, 419 | } 420 | } 421 | 422 | func (t *Tag) IsJson() bool { 423 | return t.tp == "json" 424 | } 425 | 426 | func (t *Tag) IsPath() bool { 427 | return t.tp == "path" 428 | } 429 | 430 | func (t *Tag) IsForm() bool { 431 | return t.tp == "form" 432 | } 433 | 434 | func (t *Tag) GetTag() string { 435 | return t.name 436 | } 437 | -------------------------------------------------------------------------------- /generate/dir.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "path/filepath" 19 | 20 | "github.com/tal-tech/go-zero/tools/goctl/util" 21 | ) 22 | 23 | const ( 24 | dirBean = "bean" 25 | dirService = "service" 26 | ) 27 | 28 | func mkDir(target string) (map[string]string, error) { 29 | m := map[string]string{ 30 | dirBean: filepath.Join(target, "bean"), 31 | dirService: filepath.Join(target, "service"), 32 | } 33 | 34 | for _, each := range m { 35 | abs, err := filepath.Abs(each) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | err = util.MkdirIfNotExist(abs) 41 | if err != nil { 42 | return nil, err 43 | } 44 | } 45 | return m, nil 46 | } 47 | -------------------------------------------------------------------------------- /generate/generate.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/tal-tech/go-zero/tools/goctl/util/console" 22 | ) 23 | 24 | func Do(in Plugin) error { 25 | log := console.NewColorConsole() 26 | spec, err := in.Convert() 27 | if err != nil { 28 | return err 29 | } 30 | // mkdir 31 | dir, err := mkDir(in.Dir) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | // generate bean 37 | for _, each := range spec.Beans { 38 | err = generateBean(dir[dirBean], *each) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | 44 | for _, item := range spec.Service.Routes { 45 | if item.HasRequest || item.HasResponse { 46 | if strings.TrimSpace(spec.Service.Import) != "" { 47 | spec.Service.Import = fmt.Sprintf("import %s.bean.*;", spec.Service.ParentPackage) + "\n" + spec.Service.Import 48 | continue 49 | } 50 | spec.Service.Import = fmt.Sprintf("import %s.bean.*;", spec.Service.ParentPackage) 51 | break 52 | } 53 | } 54 | 55 | // generate interface 56 | err = generateIService(dir[dirService], spec.Service) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // generate implement 62 | err = generateService(dir[dirService], spec.Service) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | log.MarkDone() 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /generate/generate_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | var pluginJson = "{\"Api\":{\"Info\":{\"Title\":\"\",\"Desc\":\"\",\"Version\":\"\",\"Author\":\"\",\"Email\":\"\"},\"Types\":[{\"Name\":\"User\",\"Annotations\":null,\"Members\":[{\"Annotations\":null,\"Name\":\"Name\",\"Type\":\"string\",\"Expr\":{\"StringExpr\":\"string\",\"Name\":\"string\"},\"Tag\":\"\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false}]},{\"Name\":\"request\",\"Annotations\":null,\"Members\":[{\"Annotations\":null,\"Name\":\"Name\",\"Type\":\"string\",\"Expr\":{\"StringExpr\":\"string\",\"Name\":\"string\"},\"Tag\":\"`json:\\\"name\\\"`\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false},{\"Annotations\":null,\"Name\":\"User\",\"Type\":\"User\",\"Expr\":{\"StringExpr\":\"User\"},\"Tag\":\"`json:\\\"user\\\"`\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false}]},{\"Name\":\"response\",\"Annotations\":null,\"Members\":[{\"Annotations\":null,\"Name\":\"Out\",\"Type\":\"string\",\"Expr\":{\"StringExpr\":\"string\",\"Name\":\"string\"},\"Tag\":\"\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false}]}],\"Service\":{\"Name\":\"template\",\"Groups\":[{\"Annotations\":[{\"Name\":\"server\",\"Properties\":{\"group\":\"template\",\"jwt\":\"Auth\"},\"Value\":\"\"}],\"Routes\":[{\"Annotations\":[{\"Name\":\"handler\",\"Properties\":null,\"Value\":\"handlerName\"}],\"Method\":\"get\",\"Path\":\"/users/id/:userId\",\"RequestType\":{\"Name\":\"request\",\"Annotations\":null,\"Members\":[{\"Annotations\":null,\"Name\":\"Name\",\"Type\":\"string\",\"Expr\":{\"StringExpr\":\"string\",\"Name\":\"string\"},\"Tag\":\"`json:\\\"name\\\"`\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false},{\"Annotations\":null,\"Name\":\"User\",\"Type\":\"User\",\"Expr\":{\"StringExpr\":\"User\"},\"Tag\":\"`json:\\\"user\\\"`\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false}]},\"ResponseType\":{\"Name\":\"response\",\"Annotations\":null,\"Members\":[{\"Annotations\":null,\"Name\":\"Out\",\"Type\":\"string\",\"Expr\":{\"StringExpr\":\"string\",\"Name\":\"string\"},\"Tag\":\"\",\"Comment\":\"\",\"Comments\":null,\"Docs\":null,\"IsInline\":false}]}}]}]}},\"ApiFilePath\":\"/Users/anqiansong/go/src/github.com/zeromicro/goctl-android/test.api\",\"Style\":\"\",\"Dir\":\"/Users/anqiansong/go/src/github.com/zeromicro/goctl-android/java\"}" 25 | 26 | func TestDo(t *testing.T) { 27 | var plugin Plugin 28 | err := json.Unmarshal([]byte(pluginJson), &plugin) 29 | assert.Nil(t, err) 30 | 31 | plugin.Dir = t.TempDir() 32 | plugin.ParentPackage = "com.gozero" 33 | assert.Nil(t, Do(plugin)) 34 | } 35 | -------------------------------------------------------------------------------- /generate/iservice.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "path/filepath" 19 | 20 | "github.com/tal-tech/go-zero/tools/goctl/util" 21 | "github.com/zeromicro/goctl-android/template" 22 | ) 23 | 24 | type ( 25 | Route struct { 26 | MethodName string 27 | Method string 28 | Path string 29 | RequestBeanName string 30 | ResponseBeanName string 31 | HasRequest bool 32 | HasResponse bool 33 | ShowRequestBody bool 34 | HavePath bool 35 | HaveQuery bool 36 | PathId string 37 | PathIdExpr string 38 | QueryId string 39 | QueryIdExpr string 40 | Doc string 41 | } 42 | IService struct { 43 | ParentPackage string 44 | Import string 45 | Routes []*Route 46 | } 47 | ) 48 | 49 | func generateIService(dir string, data IService) error { 50 | filename := filepath.Join(dir, "IService.java") 51 | base := filepath.Dir(filename) 52 | err := util.MkdirIfNotExist(base) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | return util.With("iservice").Parse(template.IService).SaveTo(data, filename, true) 58 | } 59 | -------------------------------------------------------------------------------- /generate/iservice_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestGenerateIService(t *testing.T) { 24 | 25 | tm := t.TempDir() 26 | err := generateIService(tm, IService{ 27 | ParentPackage: "com.tal", 28 | Import: "import com.tal.bean.*;", 29 | Routes: []*Route{ 30 | { 31 | MethodName: "register", 32 | Method: "POST", 33 | Path: "/api/user/register", 34 | RequestBeanName: "RegisterRequest", 35 | HasRequest: true, 36 | ShowRequestBody: true, 37 | Doc: "// 注册", 38 | }, 39 | { 40 | MethodName: "login", 41 | Method: "POST", 42 | Path: "/api/user/login", 43 | RequestBeanName: "LoginRequest", 44 | ResponseBeanName: "LoginRespinse", 45 | HasRequest: true, 46 | ShowRequestBody: true, 47 | HasResponse: true, 48 | Doc: "// 登录", 49 | }, 50 | { 51 | MethodName: "getUserInfo", 52 | Method: "GET", 53 | Path: "/api/user/info/{id}", 54 | RequestBeanName: "UserInfoRequest", 55 | ResponseBeanName: "UserInfoResponse", 56 | HasRequest: true, 57 | ShowRequestBody: false, 58 | HasResponse: true, 59 | HavePath: true, 60 | PathIdExpr: `@Path("id")int id`, 61 | PathId: "in.getId()", 62 | Doc: "// 获取用户信息", 63 | }, 64 | { 65 | MethodName: "searchUser", 66 | Method: "GET", 67 | Path: "/api/user/search", 68 | RequestBeanName: "UserInfoRequest", 69 | ResponseBeanName: "UserInfoResponse", 70 | HasRequest: true, 71 | ShowRequestBody: false, 72 | HasResponse: true, 73 | HavePath: false, 74 | HaveQuery: true, 75 | QueryId: `in.getKeyWord()`, 76 | QueryIdExpr: `@Query("keyword")String keyword`, 77 | Doc: "// 搜索用户信息", 78 | }, 79 | }, 80 | }) 81 | assert.Nil(t, err) 82 | } 83 | -------------------------------------------------------------------------------- /generate/service.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "path/filepath" 19 | 20 | "github.com/tal-tech/go-zero/tools/goctl/util" 21 | "github.com/zeromicro/goctl-android/template" 22 | ) 23 | 24 | func generateService(dir string, data IService) error { 25 | filename := filepath.Join(dir, "Service.java") 26 | base := filepath.Dir(filename) 27 | err := util.MkdirIfNotExist(base) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return util.With("service").Parse(template.Service).SaveTo(data, filename, true) 33 | } 34 | -------------------------------------------------------------------------------- /generate/service_test.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package generate 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestGenerateService(t *testing.T) { 24 | err := generateService(t.TempDir(), IService{ 25 | ParentPackage: "com.tal", 26 | Import: "import com.tal.bean.*;", 27 | Routes: []*Route{ 28 | { 29 | MethodName: "register", 30 | Method: "POST", 31 | Path: "/api/user/register", 32 | RequestBeanName: "RegisterRequest", 33 | HasRequest: true, 34 | ShowRequestBody: true, 35 | Doc: "// 注册", 36 | }, 37 | { 38 | MethodName: "login", 39 | Method: "POST", 40 | Path: "/api/user/login", 41 | RequestBeanName: "LoginRequest", 42 | ResponseBeanName: "LoginRespinse", 43 | HasRequest: true, 44 | ShowRequestBody: true, 45 | HasResponse: true, 46 | Doc: "// 登录", 47 | }, 48 | { 49 | MethodName: "getUserInfo", 50 | Method: "GET", 51 | Path: "/api/user/info/{id}", 52 | RequestBeanName: "UserInfoRequest", 53 | ResponseBeanName: "UserInfoResponse", 54 | HasRequest: true, 55 | ShowRequestBody: false, 56 | HasResponse: true, 57 | HavePath: true, 58 | PathIdExpr: `@Path("id")int id`, 59 | PathId: "in.getId()", 60 | Doc: "// 获取用户信息", 61 | }, 62 | { 63 | MethodName: "searchUser", 64 | Method: "GET", 65 | Path: "/api/user/search", 66 | RequestBeanName: "UserInfoRequest", 67 | ResponseBeanName: "UserInfoResponse", 68 | HasRequest: true, 69 | ShowRequestBody: false, 70 | HasResponse: true, 71 | HavePath: false, 72 | HaveQuery: true, 73 | QueryId: `in.getKeyWord()`, 74 | QueryIdExpr: `@Query("keyword")String keyword`, 75 | Doc: "// 搜索用户信息", 76 | }, 77 | }, 78 | }) 79 | assert.Nil(t, err) 80 | } 81 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zeromicro/goctl-android 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/stretchr/testify v1.6.1 7 | github.com/tal-tech/go-zero v1.1.0 8 | github.com/urfave/cli/v2 v2.3.0 9 | ) 10 | -------------------------------------------------------------------------------- /goctl-android.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "runtime" 21 | 22 | "github.com/urfave/cli/v2" 23 | "github.com/zeromicro/goctl-android/action" 24 | ) 25 | 26 | var ( 27 | version = "20201207" 28 | commands = []*cli.Command{ 29 | { 30 | Name: "android", 31 | Usage: "generates http client for android", 32 | Action: action.Android, 33 | Flags: []cli.Flag{ 34 | &cli.StringFlag{ 35 | Name: "package", 36 | Usage: "the package of android", 37 | }, 38 | }, 39 | }, 40 | } 41 | ) 42 | 43 | func main() { 44 | app := cli.NewApp() 45 | app.Usage = "a plugin of goctl to generate http client code for android" 46 | app.Version = fmt.Sprintf("%s %s/%s", version, runtime.GOOS, runtime.GOARCH) 47 | app.Commands = commands 48 | if err := app.Run(os.Args); err != nil { 49 | fmt.Printf("goctl-android: %+v\n", err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # goctl-android 2 | 3 | ![go-zero](https://img.shields.io/badge/Github-go--zero-brightgreen?link=https://github.com/tal-tech/go-zero&logo=github) 4 | ![License](https://img.shields.io/badge/License-MIT-blue?link=https://github.com/zeromicro/goctl-android/blob/main/LICENSE) 5 | ![Go](https://github.com/zeromicro/goctl-android/workflows/Go/badge.svg) 6 | 7 | goctl-android是一款基于goctl的插件,用于生成java(android)端http client请求代码。 8 | 本插件特性: 9 | * 仅支持post json 10 | * 支持get query参数 11 | * 支持path路由变量 12 | * 仅支持响应体为json 13 | 14 | > 警告:本插件是对goctl plugin开发流程的指引,切勿用于生产环境。 15 | 16 | # 插件使用 17 | * 编译goctl-android插件 18 | ```shell script 19 | $ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro/goctl-android 20 | ``` 21 | * 将`$GOPATH/bin`中的`goctl-android`添加到环境变量 22 | * 创建api文件 23 | ```go 24 | info( 25 | title: "type title here" 26 | desc: "type desc here" 27 | author: "type author here" 28 | email: "type email here" 29 | version: "type version here" 30 | ) 31 | 32 | 33 | type ( 34 | RegisterReq { 35 | Username string `json:"username"` 36 | Password string `json:"password"` 37 | Mobile string `json:"mobile"` 38 | } 39 | 40 | LoginReq { 41 | Username string `json:"username"` 42 | Password string `json:"password"` 43 | } 44 | 45 | UserInfoReq { 46 | Id string `path:"id"` 47 | } 48 | 49 | UserInfoReply { 50 | Name string `json:"name"` 51 | Age int `json:"age"` 52 | Birthday string `json:"birthday"` 53 | Description string `json:"description"` 54 | Tag []string `json:"tag"` 55 | } 56 | 57 | UserSearchReq { 58 | KeyWord string `form:"keyWord"` 59 | } 60 | ) 61 | 62 | service user-api { 63 | @doc( 64 | summary: "注册" 65 | ) 66 | @handler register 67 | post /api/user/register (RegisterReq) 68 | 69 | @doc( 70 | summary: "登录" 71 | ) 72 | @handler login 73 | post /api/user/login (LoginReq) 74 | 75 | @doc( 76 | summary: "获取用户信息" 77 | ) 78 | @handler getUserInfo 79 | get /api/user/:id (UserInfoReq) returns (UserInfoReply) 80 | 81 | @doc( 82 | summary: "用户搜索" 83 | ) 84 | @handler searchUser 85 | get /api/user/search (UserSearchReq) returns (UserInfoReply) 86 | } 87 | ``` 88 | * 生成android代码 89 | 90 | ```shell script 91 | $ goctl api plugin -plugin goctl-android="android -package com.tal" -api user.api -dir . 92 | ``` 93 | >说明: 其中`goctl-android`为可执行的二进制文件,`"android -package com.tal"`为goctl-plugin自定义的参数,这里需要用引号`""`引起来。 94 | 95 | 我们来看一下生成java代码后的目录结构 96 | ```text 97 | ├── bean 98 | │   ├── LoginReq.java 99 | │   ├── RegisterReq.java 100 | │   ├── UserInfoReply.java 101 | │   ├── UserInfoReq.java 102 | │   └── UserSearchReq.java 103 | ├── service 104 | │   ├── IService.java 105 | │   └── Service.java 106 | └── user.api 107 | ``` 108 | 109 | > [点击这里](https://github.com/zeromicro/ClientCall) 查看Java示例源码 110 | 111 | maven依赖 112 | ```xml 113 | 114 | com.squareup.retrofit2 115 | retrofit 116 | 2.9.0 117 | 118 | 119 | com.squareup.okhttp3 120 | okhttp 121 | 4.9.0 122 | 123 | 124 | com.alibaba 125 | fastjson 126 | 1.2.75 127 | 128 | 129 | com.squareup.retrofit2 130 | converter-gson 131 | 2.9.0 132 | 133 | ``` 134 | 135 | > 本插件是基于retrofit来实现http请求,因此会用到一些java依赖,gradle包管理形式自行处理。 136 | 137 | * 编写测试 138 | ```Java 139 | public static void main(String[] args) { 140 | LoginReq loginReq = new LoginReq(); 141 | loginReq.setUsername("zeromicro"); 142 | loginReq.setPassword("111111"); 143 | Service service = Service.getInstance(); 144 | service.login(loginReq, new Callback() { 145 | public void onResponse(Call call, Response response) { 146 | System.out.println("login success"); 147 | } 148 | 149 | public void onFailure(Call call, Throwable throwable) { 150 | 151 | } 152 | }); 153 | 154 | RegisterReq registerReq=new RegisterReq(); 155 | registerReq.setUsername("zeromicro"); 156 | registerReq.setPassword("1111"); 157 | registerReq.setMobile("12311111111"); 158 | service.register(registerReq, new Callback() { 159 | public void onResponse(Call call, Response response) { 160 | System.out.println("register success"); 161 | } 162 | 163 | public void onFailure(Call call, Throwable throwable) { 164 | 165 | } 166 | }); 167 | 168 | UserInfoReq infoReq = new UserInfoReq(); 169 | infoReq.setId("8888"); 170 | service.getUserInfo(infoReq, new Callback() { 171 | public void onResponse(Call call, Response response) { 172 | System.out.println("userInfo:"+ JSON.toJSONString(response.body())); 173 | } 174 | 175 | public void onFailure(Call call, Throwable throwable) { 176 | 177 | } 178 | }); 179 | 180 | UserSearchReq searchReq=new UserSearchReq(); 181 | searchReq.setKeyWord("song"); 182 | service.searchUser(searchReq, new Callback() { 183 | public void onResponse(Call call, Response response) { 184 | System.out.println("search:"+JSON.toJSONString(response.body())); 185 | } 186 | 187 | public void onFailure(Call call, Throwable throwable) { 188 | 189 | } 190 | }); 191 | ``` 192 | 193 | * 请求结果 194 | * client log 195 | ```text 196 | register success 197 | login success 198 | search:{"age":20,"birthday":"1991-01-01","description":"coding now","name":"zeromicro","tag":["Golang","Android"]} 199 | userInfo:{"age":20,"birthday":"1991-01-01","description":"coding now","name":"zeromicro","tag":["Golang","Android"]} 200 | ``` 201 | 202 | * server log 203 | ```text 204 | SearchUser: {KeyWord:song} 205 | GetUserInfo: {Id:8888} 206 | Login: {Username:zeromicro Password:111111} 207 | Register: {Username:zeromicro Password:1111 Mobile:12311111111} 208 | ``` 209 | 210 | # 插件开发流程 211 | 212 | * 自定义参数 213 | ```go 214 | commands = []*cli.Command{ 215 | { 216 | Name: "android", 217 | Usage: "generates http client for android", 218 | Action: action.Android, 219 | Flags: []cli.Flag{ 220 | &cli.StringFlag{ 221 | Name: "package", 222 | Usage: "the package of android", 223 | }, 224 | }, 225 | }, 226 | } 227 | ``` 228 | 229 | * 获取goctl传递过来的json信息 230 | * 利用goctl中提供的方法解析 231 | ```go 232 | plugin, err := plugin.NewPlugin() 233 | if err != nil { 234 | return err 235 | } 236 | ``` 237 | 238 | * 或者自定义结构体去反序列化 239 | 240 | ```go 241 | var plugin generate.Plugin 242 | plugin.ParentPackage = pkg 243 | err = json.Unmarshal(std, &plugin) 244 | if err != nil { 245 | return err 246 | } 247 | ``` 248 | 249 | * 实现插件逻辑 250 | ```go 251 | generate.Do(plugin) 252 | ``` 253 | 254 | >说明:上述摘要代码来自goctl-android,完整信息可浏览源码。 255 | -------------------------------------------------------------------------------- /template/bean.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package template 16 | 17 | var Bean = `package {{.ParentPackage}}.bean; 18 | {{.Import}} 19 | public class {{.Name.ToCamel}} { 20 | {{range $index,$item := .Members}}{{$item.Doc}} 21 | private {{$item.TypeName}} {{$item.Name.Untitle}}; {{$item.Comment}} 22 | {{end}}{{range $index,$item := .Members}} 23 | public {{$item.TypeName}} get{{$item.Name.ToCamel}}() { 24 | return {{$item.Name.Untitle}}; 25 | } 26 | 27 | public void set{{$item.Name.ToCamel}}({{$item.TypeName}} {{$item.Name.Untitle}}) { 28 | this.{{$item.Name.Untitle}} = {{$item.Name.Untitle}}; 29 | } 30 | {{end}} 31 | }` 32 | -------------------------------------------------------------------------------- /template/iservice.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package template 16 | 17 | var IService = `package {{.ParentPackage}}.service; 18 | 19 | {{.Import}} 20 | import okhttp3.RequestBody; 21 | import retrofit2.Call; 22 | import retrofit2.http.*; 23 | 24 | public interface IService { 25 | {{range $index,$item := .Routes}}{{$item.Doc}} 26 | @{{$item.Method}}("{{$item.Path}}") 27 | Call{{if $item.HasResponse}}<{{$item.ResponseBeanName}}>{{else}}{{end}} {{$item.MethodName}}({{if $item.HavePath}}{{$item.PathIdExpr}}{{end}}{{if $item.HaveQuery}}{{$item.QueryIdExpr}}{{end}}{{if $item.ShowRequestBody}}@Body RequestBody req{{end}}); 28 | {{end}} 29 | }` 30 | -------------------------------------------------------------------------------- /template/service.go: -------------------------------------------------------------------------------- 1 | // MIT License 2 | // 3 | // Copyright (c) 2020 goctl-android 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 15 | package template 16 | 17 | var Service = `package {{.ParentPackage}}.service; 18 | 19 | {{.Import}} 20 | import com.alibaba.fastjson.JSON; 21 | import okhttp3.MediaType; 22 | import okhttp3.RequestBody; 23 | import retrofit2.Call; 24 | import retrofit2.Callback; 25 | import retrofit2.Retrofit; 26 | import retrofit2.converter.gson.GsonConverterFactory; 27 | 28 | public class Service { 29 | private static final String MEDIA_TYPE_JSON = "application/json; charset=utf-8"; 30 | private static final String BASE_RUL = "http://localhost:8888/";// TODO replace to your host and delete this comment 31 | private static Service instance; 32 | private static IService service; 33 | 34 | private Service() { 35 | Retrofit retrofit = new Retrofit.Builder() 36 | .baseUrl(BASE_RUL) 37 | .addConverterFactory(GsonConverterFactory.create()) 38 | .build(); 39 | service = retrofit.create(IService.class); 40 | } 41 | 42 | public static Service getInstance() { 43 | if (instance == null) { 44 | instance = new Service(); 45 | } 46 | return instance; 47 | } 48 | 49 | private RequestBody buildJSONBody(Object obj) { 50 | String s = JSON.toJSONString(obj); 51 | return RequestBody.create(s, MediaType.parse(MEDIA_TYPE_JSON)); 52 | } 53 | {{range $index,$item := .Routes}}{{$item.Doc}} 54 | public void {{$item.MethodName}}({{if $item.HasRequest}}{{$item.RequestBeanName}} in, {{end}}Callback{{if $item.HasResponse}}<{{$item.ResponseBeanName}}>{{else}}{{end}} callback) { 55 | Call{{if $item.HasResponse}}<{{$item.ResponseBeanName}}>{{else}}{{end}} call = service.{{$item.MethodName}}({{if $item.HavePath}}{{$item.PathId}}{{end}}{{if $item.HaveQuery}}{{$item.QueryId}}{{end}}{{if $item.ShowRequestBody}}buildJSONBody(in){{end}}); 56 | call.enqueue(callback); 57 | } 58 | {{end}} 59 | } 60 | ` 61 | --------------------------------------------------------------------------------