goctl added
This commit is contained in:
40
tools/goctl/api/dartgen/gen.go
Normal file
40
tools/goctl/api/dartgen/gen.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package dartgen
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"zero/core/lang"
|
||||
"zero/tools/goctl/api/parser"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func DartCommand(c *cli.Context) error {
|
||||
apiFile := c.String("api")
|
||||
dir := c.String("dir")
|
||||
if len(apiFile) == 0 {
|
||||
return errors.New("missing -api")
|
||||
}
|
||||
if len(dir) == 0 {
|
||||
return errors.New("missing -dir")
|
||||
}
|
||||
|
||||
p, err := parser.NewParser(apiFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api, err := p.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(dir, "/") {
|
||||
dir = dir + "/"
|
||||
}
|
||||
api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
|
||||
lang.Must(genData(dir+"data/", api))
|
||||
lang.Must(genApi(dir+"api/", api))
|
||||
lang.Must(genVars(dir + "vars/"))
|
||||
return nil
|
||||
}
|
||||
75
tools/goctl/api/dartgen/genapi.go
Normal file
75
tools/goctl/api/dartgen/genapi.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package dartgen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"zero/core/logx"
|
||||
"zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
const apiTemplate = `import 'api.dart';
|
||||
import '../data/{{with .Info}}{{.Title}}{{end}}.dart';
|
||||
{{with .Service}}
|
||||
/// {{.Name}}
|
||||
{{range .Routes}}
|
||||
/// --{{.Path}}--
|
||||
///
|
||||
/// 请求: {{with .RequestType}}{{.Name}}{{end}}
|
||||
/// 返回: {{with .ResponseType}}{{.Name}}{{end}}
|
||||
Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.Name}} request,{{end}}{{end}}
|
||||
{Function({{with .ResponseType}}{{.Name}}{{end}}) ok,
|
||||
Function(String) fail,
|
||||
Function eventually}) async {
|
||||
await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
|
||||
ok: (data) {
|
||||
if (ok != null) ok({{with .ResponseType}}{{.Name}}{{end}}.fromJson(data));
|
||||
}, fail: fail, eventually: eventually);
|
||||
}
|
||||
{{end}}
|
||||
{{end}}`
|
||||
|
||||
func genApi(dir string, api *spec.ApiSpec) error {
|
||||
e := os.MkdirAll(dir, 0755)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
e = genApiFile(dir)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
|
||||
file, e := os.OpenFile(dir+api.Info.Title+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
t := template.New("apiTemplate")
|
||||
t = t.Funcs(funcMap)
|
||||
t, e = t.Parse(apiTemplate)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
t.Execute(file, api)
|
||||
return nil
|
||||
}
|
||||
|
||||
func genApiFile(dir string) error {
|
||||
path := dir + "api.dart"
|
||||
if fileExists(path) {
|
||||
return nil
|
||||
}
|
||||
apiFile, e := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
defer apiFile.Close()
|
||||
apiFile.WriteString(apiFileContent)
|
||||
return nil
|
||||
}
|
||||
79
tools/goctl/api/dartgen/gendata.go
Normal file
79
tools/goctl/api/dartgen/gendata.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package dartgen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"zero/core/logx"
|
||||
"zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
const dataTemplate = `// --{{with .Info}}{{.Title}}{{end}}--
|
||||
{{ range .Types}}
|
||||
class {{.Name}}{
|
||||
{{range .Members}}
|
||||
/// {{.Comment}}
|
||||
final {{.Type}} {{lowCamelCase .Name}};
|
||||
{{end}}
|
||||
{{.Name}}({ {{range .Members}}
|
||||
this.{{lowCamelCase .Name}},{{end}}
|
||||
});
|
||||
factory {{.Name}}.fromJson(Map<String,dynamic> m) {
|
||||
return {{.Name}}({{range .Members}}
|
||||
{{lowCamelCase .Name}}: {{if isDirectType .Type}}m['{{tagGet .Tag "json"}}']{{else if isClassListType .Type}}(m['{{tagGet .Tag "json"}}'] as List<dynamic>).map((i) => {{getCoreType .Type}}.fromJson(i)){{else}}{{.Type}}.fromJson(m['{{tagGet .Tag "json"}}']){{end}},{{end}}
|
||||
);
|
||||
}
|
||||
Map<String,dynamic> toJson() {
|
||||
return { {{range .Members}}
|
||||
'{{tagGet .Tag "json"}}': {{if isDirectType .Type}}{{lowCamelCase .Name}}{{else if isClassListType .Type}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
|
||||
};
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
func genData(dir string, api *spec.ApiSpec) error {
|
||||
e := os.MkdirAll(dir, 0755)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
e = genTokens(dir)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
|
||||
file, e := os.OpenFile(dir+api.Info.Title+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
t := template.New("dataTemplate")
|
||||
t = t.Funcs(funcMap)
|
||||
t, e = t.Parse(dataTemplate)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
|
||||
convertMemberType(api)
|
||||
return t.Execute(file, api)
|
||||
}
|
||||
|
||||
func genTokens(dir string) error {
|
||||
path := dir + "tokens.dart"
|
||||
if fileExists(path) {
|
||||
return nil
|
||||
}
|
||||
tokensFile, e := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
defer tokensFile.Close()
|
||||
tokensFile.WriteString(tokensFileContent)
|
||||
return nil
|
||||
}
|
||||
66
tools/goctl/api/dartgen/genvars.go
Normal file
66
tools/goctl/api/dartgen/genvars.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package dartgen
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"zero/core/logx"
|
||||
)
|
||||
|
||||
func genVars(dir string) error {
|
||||
e := os.MkdirAll(dir, 0755)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
|
||||
if !fileExists(dir + "vars.dart") {
|
||||
e = ioutil.WriteFile(dir+"vars.dart", []byte(`const serverHost='demo-crm.xiaoheiban.cn';`), 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
if !fileExists(dir + "kv.dart") {
|
||||
e = ioutil.WriteFile(dir+"kv.dart", []byte(`import 'dart:convert';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../data/tokens.dart';
|
||||
|
||||
/// 保存tokens到本地
|
||||
///
|
||||
/// 传入null则删除本地tokens
|
||||
/// 返回:true:设置成功 false:设置失败
|
||||
Future<bool> setTokens(Tokens tokens) async {
|
||||
var sp = await SharedPreferences.getInstance();
|
||||
if (tokens == null) {
|
||||
sp.remove('tokens');
|
||||
return true;
|
||||
}
|
||||
return await sp.setString('tokens', jsonEncode(tokens.toJson()));
|
||||
}
|
||||
|
||||
/// 获取本地存储的tokens
|
||||
///
|
||||
/// 如果没有,则返回null
|
||||
Future<Tokens> getTokens() async {
|
||||
try {
|
||||
var sp = await SharedPreferences.getInstance();
|
||||
var str = sp.getString('tokens');
|
||||
if (str.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return Tokens.fromJson(jsonDecode(str));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
`), 0644)
|
||||
if e != nil {
|
||||
logx.Error(e)
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
118
tools/goctl/api/dartgen/util.go
Normal file
118
tools/goctl/api/dartgen/util.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package dartgen
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"zero/tools/goctl/api/spec"
|
||||
"zero/tools/goctl/api/util"
|
||||
)
|
||||
|
||||
func lowCamelCase(s string) string {
|
||||
if len(s) < 1 {
|
||||
return ""
|
||||
}
|
||||
s = util.ToCamelCase(util.ToSnakeCase(s))
|
||||
return util.ToLower(s[:1]) + s[1:]
|
||||
}
|
||||
|
||||
func pathToFuncName(path string) string {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if !strings.HasPrefix(path, "/api") {
|
||||
path = "/api" + path
|
||||
}
|
||||
|
||||
path = strings.Replace(path, "/", "_", -1)
|
||||
path = strings.Replace(path, "-", "_", -1)
|
||||
|
||||
camel := util.ToCamelCase(path)
|
||||
return util.ToLower(camel[:1]) + camel[1:]
|
||||
}
|
||||
|
||||
func tagGet(tag, k string) (reflect.Value, error) {
|
||||
v, _ := util.TagLookup(tag, k)
|
||||
out := strings.Split(v, ",")[0]
|
||||
return reflect.ValueOf(out), nil
|
||||
}
|
||||
|
||||
func convertMemberType(api *spec.ApiSpec) {
|
||||
for i, t := range api.Types {
|
||||
for j, mem := range t.Members {
|
||||
api.Types[i].Members[j].Type = goTypeToDart(mem.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func goTypeToDart(t string) string {
|
||||
t = strings.Replace(t, "*", "", -1)
|
||||
if strings.HasPrefix(t, "[]") {
|
||||
return "List<" + goTypeToDart(t[2:]) + ">"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(t, "map") {
|
||||
tys, e := util.DecomposeType(t)
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
if len(tys) != 2 {
|
||||
log.Fatal("Map type number !=2")
|
||||
}
|
||||
|
||||
return "Map<String," + goTypeToDart(tys[1]) + ">"
|
||||
}
|
||||
|
||||
switch t {
|
||||
case "string":
|
||||
return "String"
|
||||
case "int", "int32", "int64":
|
||||
return "int"
|
||||
case "float", "float32", "float64":
|
||||
return "double"
|
||||
case "bool":
|
||||
return "bool"
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
func isDirectType(s string) bool {
|
||||
return isAtomicType(s) || isListType(s) && isAtomicType(getCoreType(s))
|
||||
}
|
||||
|
||||
func isAtomicType(s string) bool {
|
||||
switch s {
|
||||
case "String", "int", "double", "bool":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isListType(s string) bool {
|
||||
return strings.HasPrefix(s, "List<")
|
||||
}
|
||||
|
||||
func isClassListType(s string) bool {
|
||||
return strings.HasPrefix(s, "List<") && !isAtomicType(getCoreType(s))
|
||||
}
|
||||
|
||||
func getCoreType(s string) string {
|
||||
if isAtomicType(s) {
|
||||
return s
|
||||
}
|
||||
if isListType(s) {
|
||||
s = strings.Replace(s, "List<", "", -1)
|
||||
return strings.Replace(s, ">", "", -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func fileExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
133
tools/goctl/api/dartgen/vars.go
Normal file
133
tools/goctl/api/dartgen/vars.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package dartgen
|
||||
|
||||
import "text/template"
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"tagGet": tagGet,
|
||||
"isDirectType": isDirectType,
|
||||
"isClassListType": isClassListType,
|
||||
"getCoreType": getCoreType,
|
||||
"pathToFuncName": pathToFuncName,
|
||||
"lowCamelCase": lowCamelCase,
|
||||
}
|
||||
|
||||
const apiFileContent = `import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import '../vars/kv.dart';
|
||||
import '../vars/vars.dart';
|
||||
|
||||
/// 发送POST请求.
|
||||
///
|
||||
/// data:为你要post的结构体,我们会帮你转换成json字符串;
|
||||
/// ok函数:请求成功的时候调用,fail函数:请求失败的时候会调用,eventually函数:无论成功失败都会调用
|
||||
Future apiPost(String path, dynamic data,
|
||||
{Map<String, String> header,
|
||||
Function(Map<String, dynamic>) ok,
|
||||
Function(String) fail,
|
||||
Function eventually}) async {
|
||||
await _apiRequest('POST', path, data,
|
||||
header: header, ok: ok, fail: fail, eventually: eventually);
|
||||
}
|
||||
|
||||
/// 发送GET请求.
|
||||
///
|
||||
/// ok函数:请求成功的时候调用,fail函数:请求失败的时候会调用,eventually函数:无论成功失败都会调用
|
||||
Future apiGet(String path,
|
||||
{Map<String, String> header,
|
||||
Function(Map<String, dynamic>) ok,
|
||||
Function(String) fail,
|
||||
Function eventually}) async {
|
||||
await _apiRequest('GET', path, null,
|
||||
header: header, ok: ok, fail: fail, eventually: eventually);
|
||||
}
|
||||
|
||||
Future _apiRequest(String method, String path, dynamic data,
|
||||
{Map<String, String> header,
|
||||
Function(Map<String, dynamic>) ok,
|
||||
Function(String) fail,
|
||||
Function eventually}) async {
|
||||
var tokens = await getTokens();
|
||||
try {
|
||||
var client = HttpClient();
|
||||
HttpClientRequest r;
|
||||
if (method == 'POST') {
|
||||
r = await client.postUrl(Uri.parse('https://' + serverHost + path));
|
||||
} else {
|
||||
r = await client.getUrl(Uri.parse('https://' + serverHost + path));
|
||||
}
|
||||
|
||||
r.headers.set('Content-Type', 'application/json');
|
||||
if (tokens != null) {
|
||||
r.headers.set('Authorization', tokens.accessToken);
|
||||
}
|
||||
if (header != null) {
|
||||
header.forEach((k, v) {
|
||||
r.headers.set(k, v);
|
||||
});
|
||||
}
|
||||
var strData = '';
|
||||
if (data != null) {
|
||||
strData = jsonEncode(data);
|
||||
}
|
||||
r.write(strData);
|
||||
var rp = await r.close();
|
||||
var body = await rp.transform(utf8.decoder).join();
|
||||
print('${rp.statusCode} - $path');
|
||||
print('-- request --');
|
||||
print(strData);
|
||||
print('-- response --');
|
||||
print('$body \n');
|
||||
if (rp.statusCode == 404) {
|
||||
if (fail != null) fail('404 not found');
|
||||
} else {
|
||||
Map<String, dynamic> base = jsonDecode(body);
|
||||
if (rp.statusCode == 200) {
|
||||
if (base['code'] != 0) {
|
||||
if (fail != null) fail(base['desc']);
|
||||
} else {
|
||||
if (ok != null) ok(base['data']);
|
||||
}
|
||||
} else if (base['code'] != 0) {
|
||||
if (fail != null) fail(base['desc']);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (fail != null) fail(e.toString());
|
||||
}
|
||||
if (eventually != null) eventually();
|
||||
}
|
||||
`
|
||||
const tokensFileContent = `class Tokens {
|
||||
/// 用于访问的token, 每次请求都必须带在Header里面
|
||||
final String accessToken;
|
||||
final int accessExpire;
|
||||
|
||||
/// 用于刷新token
|
||||
final String refreshToken;
|
||||
final int refreshExpire;
|
||||
final int refreshAfter;
|
||||
Tokens(
|
||||
{this.accessToken,
|
||||
this.accessExpire,
|
||||
this.refreshToken,
|
||||
this.refreshExpire,
|
||||
this.refreshAfter});
|
||||
factory Tokens.fromJson(Map<String, dynamic> m) {
|
||||
return Tokens(
|
||||
accessToken: m['access_token'],
|
||||
accessExpire: m['access_expire'],
|
||||
refreshToken: m['refresh_token'],
|
||||
refreshExpire: m['refresh_expire'],
|
||||
refreshAfter: m['refresh_after']);
|
||||
}
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'access_token': accessToken,
|
||||
'access_expire': accessExpire,
|
||||
'refresh_token': refreshToken,
|
||||
'refresh_expire': refreshExpire,
|
||||
'refresh_after': refreshAfter,
|
||||
};
|
||||
}
|
||||
}
|
||||
`
|
||||
Reference in New Issue
Block a user