From f75d8997ef00433c8591cdc8d6c5042408e6068e Mon Sep 17 00:00:00 2001 From: Liujian Date: Tue, 14 Oct 2014 21:30:32 +0800 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=94=9F=E6=88=90=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E6=95=B0=E6=8D=AE=E5=BA=93=E7=9A=84Hprose=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E6=9C=8D=E5=8A=A1=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bee.go | 1 + g_appcode.go | 8 +- g_hproseappcode.go | 685 +++++++++++++++++++++++++++++++++++++++++++++ hproseapp.go | 655 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1348 insertions(+), 1 deletion(-) create mode 100644 g_hproseappcode.go create mode 100644 hproseapp.go diff --git a/bee.go b/bee.go index e8fc482..74dc476 100644 --- a/bee.go +++ b/bee.go @@ -77,6 +77,7 @@ var commands = []*Command{ cmdRun, cmdPack, cmdApiapp, + cmdHproseapp, //cmdRouter, //cmdTest, cmdBale, diff --git a/g_appcode.go b/g_appcode.go index aca7023..8c99375 100644 --- a/g_appcode.go +++ b/g_appcode.go @@ -499,7 +499,13 @@ func (mysqlDB *MysqlDB) GetColumns(db *sql.DB, table *Table, blackList map[strin // getGoDataType maps an SQL data type to Golang data type func (*MysqlDB) GetGoDataType(sqlType string) (goType string) { - if v, ok := typeMappingMysql[sqlType]; ok { + var typeMapping = map[string]string{} + if isCreateHproseApp { + typeMapping = typeMappingMysqlOfRpc + } else { + typeMapping = typeMappingMysql + } + if v, ok := typeMapping[sqlType]; ok { return v } else { ColorLog("[ERRO] data type (%s) not found!\n", sqlType) diff --git a/g_hproseappcode.go b/g_hproseappcode.go new file mode 100644 index 0000000..1bd498f --- /dev/null +++ b/g_hproseappcode.go @@ -0,0 +1,685 @@ +/**********************************************************\ +| | +| hprose | +| | +| Official WebSite: http://www.hprose.com/ | +| http://www.hprose.org/ | +| | +\**********************************************************/ +/**********************************************************\ + * * + * Build rpc application use Hprose base on beego * + * * + * LastModified: Oct 13, 2014 * + * Author: Liu jian * + * * +\**********************************************************/ + +package main + +import ( + "database/sql" + "os" + "path" + "strings" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/lib/pq" +) + +// typeMapping maps SQL data type to corresponding Go data type +var typeMappingMysqlOfRpc = map[string]string{ + "int": "int", // int signed + "integer": "int", + "tinyint": "int8", + "smallint": "int16", + "mediumint": "int32", + "bigint": "int64", + "int unsigned": "uint", // int unsigned + "integer unsigned": "uint", + "tinyint unsigned": "uint8", + "smallint unsigned": "uint16", + "mediumint unsigned": "uint32", + "bigint unsigned": "uint64", + "bit": "uint64", + "bool": "bool", // boolean + "enum": "string", // enum + "set": "string", // set + "varchar": "string", // string & text + "char": "string", + "tinytext": "string", + "mediumtext": "string", + "text": "string", + "longtext": "string", + "blob": "byte", // blob as byte + "tinyblob": "byte", + "mediumblob": "byte", + "longblob": "byte", + "date": "time.Time", // time + "datetime": "time.Time", + "timestamp": "time.Time", + "time": "time.Time", + "float": "float32", // float & decimal + "double": "float64", + "decimal": "float64", + "binary": "string", // binary + "varbinary": "string", +} + +// typeMappingPostgres maps SQL data type to corresponding Go data type +var typeMappingPostgresOfRpc = map[string]string{ + "serial": "int", // serial + "big serial": "int64", + "smallint": "int16", // int + "integer": "int", + "bigint": "int64", + "boolean": "bool", // bool + "char": "string", // string + "character": "string", + "character varying": "string", + "varchar": "string", + "text": "string", + "date": "time.Time", // time + "time": "time.Time", + "timestamp": "time.Time", + "timestamp without time zone": "time.Time", + "interval": "string", // time interval, string for now + "real": "float32", // float & decimal + "double precision": "float64", + "decimal": "float64", + "numeric": "float64", + "money": "float64", // money + "bytea": "byte", // binary + "tsvector": "string", // fulltext + "ARRAY": "string", // array + "USER-DEFINED": "string", // user defined + "uuid": "string", // uuid + "json": "string", // json +} + +func generateHproseAppcode(driver, connStr, level, tables, currpath string) { + var mode byte + switch level { + case "1": + mode = O_MODEL + case "2": + mode = O_MODEL | O_CONTROLLER + case "3": + mode = O_MODEL | O_CONTROLLER | O_ROUTER + default: + ColorLog("[ERRO] Invalid 'level' option: %s\n", level) + ColorLog("[HINT] Level must be either 1, 2 or 3\n") + os.Exit(2) + } + var selectedTables map[string]bool + if tables != "" { + selectedTables = make(map[string]bool) + for _, v := range strings.Split(tables, ",") { + selectedTables[v] = true + } + } + switch driver { + case "mysql": + case "postgres": + case "sqlite": + ColorLog("[ERRO] Generating app code from SQLite database is not supported yet.\n") + os.Exit(2) + default: + ColorLog("[ERRO] Unknown database driver: %s\n", driver) + ColorLog("[HINT] Driver must be one of mysql, postgres or sqlite\n") + os.Exit(2) + } + genHprose(driver, connStr, mode, selectedTables, currpath) +} + +// Generate takes table, column and foreign key information from database connection +// and generate corresponding golang source files +func genHprose(dbms, connStr string, mode byte, selectedTableNames map[string]bool, currpath string) { + db, err := sql.Open(dbms, connStr) + if err != nil { + ColorLog("[ERRO] Could not connect to %s database: %s, %s\n", dbms, connStr, err) + os.Exit(2) + } + defer db.Close() + if trans, ok := dbDriver[dbms]; ok { + ColorLog("[INFO] Analyzing database tables...\n") + tableNames := trans.GetTableNames(db) + // 添加 Hprose Function + for _, tb := range tableNames { + hproseAddFunctions = append(hproseAddFunctions, strings.Replace(HPROSE_ADDFUNCTION, "{{modelName}}", camelCase(tb), -1)) + } + // 添加结束 + tables := getTableObjects(tableNames, db, trans) + mvcPath := new(MvcPath) + mvcPath.ModelPath = path.Join(currpath, "models") + mvcPath.ControllerPath = path.Join(currpath, "controllers") + mvcPath.RouterPath = path.Join(currpath, "routers") + createPaths(mode, mvcPath) + pkgPath := getPackagePath(currpath) + writeHproseSourceFiles(pkgPath, tables, mode, mvcPath, selectedTableNames) + } else { + ColorLog("[ERRO] Generating app code from %s database is not supported yet.\n", dbms) + os.Exit(2) + } +} + +// writeHproseSourceFiles generates source files for model/controller/router +// It will wipe the following directories and recreate them:./models, ./controllers, ./routers +// Newly geneated files will be inside these folders. +func writeHproseSourceFiles(pkgPath string, tables []*Table, mode byte, paths *MvcPath, selectedTables map[string]bool) { + if (O_MODEL & mode) == O_MODEL { + ColorLog("[INFO] Creating model files...\n") + writeHproseModelFiles(tables, paths.ModelPath, selectedTables) + } + if (O_CONTROLLER & mode) == O_CONTROLLER { + ColorLog("[INFO] Creating controller files...\n") + writeHproseControllerFiles(tables, paths.ControllerPath, selectedTables, pkgPath) + } + if (O_ROUTER & mode) == O_ROUTER { + ColorLog("[INFO] Creating router files...\n") + writeHproseRouterFile(tables, paths.RouterPath, selectedTables, pkgPath) + } +} + +// writeHproseModelFiles generates model files +func writeHproseModelFiles(tables []*Table, mPath string, selectedTables map[string]bool) { + for _, tb := range tables { + // if selectedTables map is not nil and this table is not selected, ignore it + if selectedTables != nil { + if _, selected := selectedTables[tb.Name]; !selected { + continue + } + } + filename := getFileName(tb.Name) + fpath := path.Join(mPath, filename+".go") + var f *os.File + var err error + if isExist(fpath) { + ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + if askForConfirmation() { + f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + continue + } + } else { + ColorLog("[WARN] skip create file\n") + continue + } + } else { + f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + continue + } + } + template := "" + if tb.Pk == "" { + template = HPROSE_STRUCT_MODEL_TPL + } else { + template = HPROSE_MODEL_TPL + } + fileStr := strings.Replace(template, "{{modelStruct}}", tb.String(), 1) + fileStr = strings.Replace(fileStr, "{{modelName}}", camelCase(tb.Name), -1) + // if table contains time field, import time.Time package + timePkg := "" + importTimePkg := "" + if tb.ImportTimePkg { + timePkg = "\"time\"\n" + importTimePkg = "import \"time\"\n" + } + fileStr = strings.Replace(fileStr, "{{timePkg}}", timePkg, -1) + fileStr = strings.Replace(fileStr, "{{importTimePkg}}", importTimePkg, -1) + if _, err := f.WriteString(fileStr); err != nil { + ColorLog("[ERRO] Could not write model file to %s\n", fpath) + os.Exit(2) + } + f.Close() + ColorLog("[INFO] model => %s\n", fpath) + formatSourceCode(fpath) + } +} + +// writeHproseControllerFiles generates controller files +func writeHproseControllerFiles(tables []*Table, cPath string, selectedTables map[string]bool, pkgPath string) { + for _, tb := range tables { + // if selectedTables map is not nil and this table is not selected, ignore it + if selectedTables != nil { + if _, selected := selectedTables[tb.Name]; !selected { + continue + } + } + if tb.Pk == "" { + continue + } + filename := getFileName(tb.Name) + fpath := path.Join(cPath, filename+".go") + var f *os.File + var err error + if isExist(fpath) { + ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + if askForConfirmation() { + f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + continue + } + } else { + ColorLog("[WARN] skip create file\n") + continue + } + } else { + f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + continue + } + } + fileStr := strings.Replace(HPROSE_CTRL_TPL, "{{ctrlName}}", camelCase(tb.Name), -1) + fileStr = strings.Replace(fileStr, "{{pkgPath}}", pkgPath, -1) + if _, err := f.WriteString(fileStr); err != nil { + ColorLog("[ERRO] Could not write controller file to %s\n", fpath) + os.Exit(2) + } + f.Close() + ColorLog("[INFO] controller => %s\n", fpath) + formatSourceCode(fpath) + } +} + +// writeHproseRouterFile generates router file +func writeHproseRouterFile(tables []*Table, rPath string, selectedTables map[string]bool, pkgPath string) { + var nameSpaces []string + for _, tb := range tables { + // if selectedTables map is not nil and this table is not selected, ignore it + if selectedTables != nil { + if _, selected := selectedTables[tb.Name]; !selected { + continue + } + } + if tb.Pk == "" { + continue + } + // add name spaces + nameSpace := strings.Replace(HPROSE_NAMESPACE_TPL, "{{nameSpace}}", tb.Name, -1) + nameSpace = strings.Replace(nameSpace, "{{ctrlName}}", camelCase(tb.Name), -1) + nameSpaces = append(nameSpaces, nameSpace) + } + // add export controller + fpath := path.Join(rPath, "router.go") + routerStr := strings.Replace(HPROSE_ROUTER_TPL, "{{nameSpaces}}", strings.Join(nameSpaces, ""), 1) + routerStr = strings.Replace(routerStr, "{{pkgPath}}", pkgPath, 1) + var f *os.File + var err error + if isExist(fpath) { + ColorLog("[WARN] %v is exist, do you want to overwrite it? Yes or No?\n", fpath) + if askForConfirmation() { + f, err = os.OpenFile(fpath, os.O_RDWR|os.O_TRUNC, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + return + } + } else { + ColorLog("[WARN] skip create file\n") + return + } + } else { + f, err = os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + ColorLog("[WARN] %v\n", err) + return + } + } + if _, err := f.WriteString(routerStr); err != nil { + ColorLog("[ERRO] Could not write router file to %s\n", fpath) + os.Exit(2) + } + f.Close() + ColorLog("[INFO] router => %s\n", fpath) + formatSourceCode(fpath) +} + +const ( + HPROSE_ADDFUNCTION = ` + // publish about {{modelName}} function + service.AddFunction("Add{{modelName}}", models.Add{{modelName}}) + service.AddFunction("Get{{modelName}}ById", models.Get{{modelName}}ById) + service.AddFunction("GetAll{{modelName}}", models.GetAll{{modelName}}) + service.AddFunction("Update{{modelName}}ById", models.Update{{modelName}}ById) + service.AddFunction("Delete{{modelName}}", models.Delete{{modelName}}) + +` + HPROSE_STRUCT_MODEL_TPL = `package models +{{importTimePkg}} +{{modelStruct}} +` + + HPROSE_MODEL_TPL = `package models + +import ( + "errors" + "fmt" + "reflect" + "strings" + {{timePkg}} + "github.com/astaxie/beego/orm" +) + +{{modelStruct}} + +func init() { + orm.RegisterModel(new({{modelName}})) +} + +// Add{{modelName}} insert a new {{modelName}} into database and returns +// last inserted Id on success. +func Add{{modelName}}(m *{{modelName}}) (id int64, err error) { + o := orm.NewOrm() + id, err = o.Insert(m) + return +} + +// Get{{modelName}}ById retrieves {{modelName}} by Id. Returns error if +// Id doesn't exist +func Get{{modelName}}ById(id int) (v *{{modelName}}, err error) { + o := orm.NewOrm() + v = &{{modelName}}{Id: id} + if err = o.Read(v); err == nil { + return v, nil + } + return nil, err +} + +// GetAll{{modelName}} retrieves all {{modelName}} matches certain condition. Returns empty list if +// no records exist +func GetAll{{modelName}}(query map[string]string, fields []string, sortby []string, order []string, + offset int64, limit int64) (ml []interface{}, err error) { + o := orm.NewOrm() + qs := o.QueryTable(new({{modelName}})) + // query k=v + for k, v := range query { + // rewrite dot-notation to Object__Attribute + k = strings.Replace(k, ".", "__", -1) + qs = qs.Filter(k, v) + } + // order by: + var sortFields []string + if len(sortby) != 0 { + if len(sortby) == len(order) { + // 1) for each sort field, there is an associated order + for i, v := range sortby { + orderby := "" + if order[i] == "desc" { + orderby = "-" + v + } else if order[i] == "asc" { + orderby = v + } else { + return nil, errors.New("Error: Invalid order. Must be either [asc|desc]") + } + sortFields = append(sortFields, orderby) + } + qs = qs.OrderBy(sortFields...) + } else if len(sortby) != len(order) && len(order) == 1 { + // 2) there is exactly one order, all the sorted fields will be sorted by this order + for _, v := range sortby { + orderby := "" + if order[0] == "desc" { + orderby = "-" + v + } else if order[0] == "asc" { + orderby = v + } else { + return nil, errors.New("Error: Invalid order. Must be either [asc|desc]") + } + sortFields = append(sortFields, orderby) + } + } else if len(sortby) != len(order) && len(order) != 1 { + return nil, errors.New("Error: 'sortby', 'order' sizes mismatch or 'order' size is not 1") + } + } else { + if len(order) != 0 { + return nil, errors.New("Error: unused 'order' fields") + } + } + + var l []{{modelName}} + qs = qs.OrderBy(sortFields...) + if _, err := qs.Limit(limit, offset).All(&l, fields...); err == nil { + if len(fields) == 0 { + for _, v := range l { + ml = append(ml, v) + } + } else { + // trim unused fields + for _, v := range l { + m := make(map[string]interface{}) + val := reflect.ValueOf(v) + for _, fname := range fields { + m[fname] = val.FieldByName(fname).Interface() + } + ml = append(ml, m) + } + } + return ml, nil + } + return nil, err +} + +// Update{{modelName}} updates {{modelName}} by Id and returns error if +// the record to be updated doesn't exist +func Update{{modelName}}ById(m *{{modelName}}) (err error) { + o := orm.NewOrm() + v := {{modelName}}{Id: m.Id} + // ascertain id exists in the database + if err = o.Read(&v); err == nil { + var num int64 + if num, err = o.Update(m); err == nil { + fmt.Println("Number of records updated in database:", num) + } + } + return +} + +// Delete{{modelName}} deletes {{modelName}} by Id and returns error if +// the record to be deleted doesn't exist +func Delete{{modelName}}(id int) (err error) { + o := orm.NewOrm() + v := {{modelName}}{Id: id} + // ascertain id exists in the database + if err = o.Read(&v); err == nil { + var num int64 + if num, err = o.Delete(&{{modelName}}{Id: id}); err == nil { + fmt.Println("Number of records deleted in database:", num) + } + } + return +} +` + HPROSE_CTRL_TPL = `package controllers + +import ( + "{{pkgPath}}/models" + "encoding/json" + "errors" + "strconv" + "strings" + + "github.com/astaxie/beego" +) + +// oprations for {{ctrlName}} +type {{ctrlName}}Controller struct { + beego.Controller +} + +func (this *{{ctrlName}}Controller) URLMapping() { + this.Mapping("Post", this.Post) + this.Mapping("GetOne", this.GetOne) + this.Mapping("GetAll", this.GetAll) + this.Mapping("Put", this.Put) + this.Mapping("Delete", this.Delete) +} + +// @Title Post +// @Description create {{ctrlName}} +// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" +// @Success 200 {int} models.{{ctrlName}}.Id +// @Failure 403 body is empty +// @router / [post] +func (this *{{ctrlName}}Controller) Post() { + var v models.{{ctrlName}} + json.Unmarshal(this.Ctx.Input.RequestBody, &v) + if id, err := models.Add{{ctrlName}}(&v); err == nil { + this.Data["json"] = map[string]int64{"id": id} + } else { + this.Data["json"] = err.Error() + } + this.ServeJson() +} + +// @Title Get +// @Description get {{ctrlName}} by id +// @Param id path string true "The key for staticblock" +// @Success 200 {object} models.{{ctrlName}} +// @Failure 403 :id is empty +// @router /:id [get] +func (this *{{ctrlName}}Controller) GetOne() { + idStr := this.Ctx.Input.Params[":id"] + id, _ := strconv.Atoi(idStr) + v, err := models.Get{{ctrlName}}ById(id) + if err != nil { + this.Data["json"] = err.Error() + } else { + this.Data["json"] = v + } + this.ServeJson() +} + +// @Title Get All +// @Description get {{ctrlName}} +// @Param query query string false "Filter. e.g. col1:v1,col2:v2 ..." +// @Param fields query string false "Fields returned. e.g. col1,col2 ..." +// @Param sortby query string false "Sorted-by fields. e.g. col1,col2 ..." +// @Param order query string false "Order corresponding to each sortby field, if single value, apply to all sortby fields. e.g. desc,asc ..." +// @Param limit query string false "Limit the size of result set. Must be an integer" +// @Param offset query string false "Start position of result set. Must be an integer" +// @Success 200 {object} models.{{ctrlName}} +// @Failure 403 +// @router / [get] +func (this *{{ctrlName}}Controller) GetAll() { + var fields []string + var sortby []string + var order []string + var query map[string]string = make(map[string]string) + var limit int64 = 10 + var offset int64 = 0 + + // fields: col1,col2,entity.col3 + if v := this.GetString("fields"); v != "" { + fields = strings.Split(v, ",") + } + // limit: 10 (default is 10) + if v, err := this.GetInt("limit"); err == nil { + limit = v + } + // offset: 0 (default is 0) + if v, err := this.GetInt("offset"); err == nil { + offset = v + } + // sortby: col1,col2 + if v := this.GetString("sortby"); v != "" { + sortby = strings.Split(v, ",") + } + // order: desc,asc + if v := this.GetString("order"); v != "" { + order = strings.Split(v, ",") + } + // query: k:v,k:v + if v := this.GetString("query"); v != "" { + for _, cond := range strings.Split(v, ",") { + kv := strings.Split(cond, ":") + if len(kv) != 2 { + this.Data["json"] = errors.New("Error: invalid query key/value pair") + this.ServeJson() + return + } + k, v := kv[0], kv[1] + query[k] = v + } + } + + l, err := models.GetAll{{ctrlName}}(query, fields, sortby, order, offset, limit) + if err != nil { + this.Data["json"] = err.Error() + } else { + this.Data["json"] = l + } + this.ServeJson() +} + +// @Title Update +// @Description update the {{ctrlName}} +// @Param id path string true "The id you want to update" +// @Param body body models.{{ctrlName}} true "body for {{ctrlName}} content" +// @Success 200 {object} models.{{ctrlName}} +// @Failure 403 :id is not int +// @router /:id [put] +func (this *{{ctrlName}}Controller) Put() { + idStr := this.Ctx.Input.Params[":id"] + id, _ := strconv.Atoi(idStr) + v := models.{{ctrlName}}{Id: id} + json.Unmarshal(this.Ctx.Input.RequestBody, &v) + if err := models.Update{{ctrlName}}ById(&v); err == nil { + this.Data["json"] = "OK" + } else { + this.Data["json"] = err.Error() + } + this.ServeJson() +} + +// @Title Delete +// @Description delete the {{ctrlName}} +// @Param id path string true "The id you want to delete" +// @Success 200 {string} delete success! +// @Failure 403 id is empty +// @router /:id [delete] +func (this *{{ctrlName}}Controller) Delete() { + idStr := this.Ctx.Input.Params[":id"] + id, _ := strconv.Atoi(idStr) + if err := models.Delete{{ctrlName}}(id); err == nil { + this.Data["json"] = "OK" + } else { + this.Data["json"] = err.Error() + } + this.ServeJson() +} +` + HPROSE_ROUTER_TPL = `// @APIVersion 1.0.0 +// @Title beego Test API +// @Description beego has a very cool tools to autogenerate documents for your API +// @Contact astaxie@gmail.com +// @TermsOfServiceUrl http://beego.me/ +// @License Apache 2.0 +// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html +package routers + +import ( + "{{pkgPath}}/controllers" + + "github.com/astaxie/beego" +) + +func init() { + ns := beego.NewNamespace("/v1", + {{nameSpaces}} + ) + beego.AddNamespace(ns) +} +` + HPROSE_NAMESPACE_TPL = ` + beego.NSNamespace("/{{nameSpace}}", + beego.NSInclude( + &controllers.{{ctrlName}}Controller{}, + ), + ), +` +) diff --git a/hproseapp.go b/hproseapp.go new file mode 100644 index 0000000..f49899a --- /dev/null +++ b/hproseapp.go @@ -0,0 +1,655 @@ +/**********************************************************\ +| | +| hprose | +| | +| Official WebSite: http://www.hprose.com/ | +| http://www.hprose.org/ | +| | +\**********************************************************/ +/**********************************************************\ + * * + * Build rpc application use Hprose base on beego * + * * + * LastModified: Oct 13, 2014 * + * Author: Liu jian * + * * +\**********************************************************/ + +package main + +import ( + "fmt" + "os" + path "path/filepath" + "strings" +) + +var isCreateHproseApp = false + +var cmdHproseapp = &Command{ + // CustomFlags: true, + UsageLine: "hprose [appname]", + Short: "create an rpc application use hprose base on beego framework", + Long: ` +create an rpc application use hprose base on beego framework + +bee hprose [appname] [-tables=""] [-driver=mysql] [-conn=root:@tcp(127.0.0.1:3306)/test] + -tables: a list of table names separated by ',', default is empty, indicating all tables + -driver: [mysql | postgres | sqlite], the default is mysql + -conn: the connection string used by the driver, the default is '' + e.g. for mysql: root:@tcp(127.0.0.1:3306)/test + e.g. for postgres: postgres://postgres:postgres@127.0.0.1:5432/postgres + +if conn is empty will create a example rpc application. otherwise generate rpc application use hprose based on an existing database. + +In the current path, will create a folder named [appname] + +In the appname folder has the follow struct: + + ├── conf + │ └── app.conf + ├── controllers + │ └── object.go + │ └── user.go + ├── routers + │ └── router.go + ├── tests + │ └── default_test.go + ├── main.go + └── models + └── object.go + └── user.go + +## For hprose client URL : http://127.0.0.1:8080/hprose +`, +} + +var hproseconf = `appname = {{.Appname}} +httpport = 8080 +runmode = dev +autorender = false +copyrequestbody = true +EnableDocs = true +` +var hproseMaingo = `package main + +import ( + _ "{{.Appname}}/docs" + "{{.Appname}}/models" + _ "{{.Appname}}/routers" + "github.com/hprose/hprose-go/hprose" + + "github.com/astaxie/beego" +) + +func main() { + if beego.RunMode == "dev" { + beego.DirectoryIndex = true + beego.StaticDir["/swagger"] = "swagger" + } + service := hprose.NewHttpService() + service.AddFunction("AddOne", models.AddOne) + service.AddFunction("GetOne", models.GetOne) + beego.Handler("/hprose", service) + beego.Run() +} +` + +var hproseMainconngo = `package main + +import ( + _ "{{.Appname}}/docs" + "{{.Appname}}/models" + _ "{{.Appname}}/routers" + "github.com/hprose/hprose-go/hprose" + + "github.com/astaxie/beego" + "github.com/astaxie/beego/orm" + {{.DriverPkg}} +) + +func init() { + orm.RegisterDataBase("default", "{{.DriverName}}", "{{.conn}}") +} + +func main() { + if beego.RunMode == "dev" { + beego.DirectoryIndex = true + beego.StaticDir["/swagger"] = "swagger" + } + service := hprose.NewHttpService() + {{HproseFunctionList}} + beego.Handler("/hprose", service) + beego.Run() +} + +` + +var hproserouter = `// @APIVersion 1.0.0 +// @Title beego Test API +// @Description beego has a very cool tools to autogenerate documents for your API +// @Contact astaxie@gmail.com +// @TermsOfServiceUrl http://beego.me/ +// @License Apache 2.0 +// @LicenseUrl http://www.apache.org/licenses/LICENSE-2.0.html +package routers + +import ( + "{{.Appname}}/controllers" + + "github.com/astaxie/beego" +) + +func init() { + ns := beego.NewNamespace("/v1", + beego.NSNamespace("/object", + beego.NSInclude( + &controllers.ObjectController{}, + ), + ), + beego.NSNamespace("/user", + beego.NSInclude( + &controllers.UserController{}, + ), + ), + ) + beego.AddNamespace(ns) +} +` + +var hproseModels = `package models + +import ( + "errors" + "strconv" + "time" +) + +var ( + Objects map[string]*Object +) + +type Object struct { + ObjectId string + Score int64 + PlayerName string +} + +func init() { + Objects = make(map[string]*Object) + Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"} + Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"} +} + +func AddOne(object Object) (ObjectId string) { + object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10) + Objects[object.ObjectId] = &object + return object.ObjectId +} + +func GetOne(ObjectId string) (object *Object, err error) { + if v, ok := Objects[ObjectId]; ok { + return v, nil + } + return nil, errors.New("ObjectId Not Exist") +} + +func GetAll() map[string]*Object { + return Objects +} + +func Update(ObjectId string, Score int64) (err error) { + if v, ok := Objects[ObjectId]; ok { + v.Score = Score + return nil + } + return errors.New("ObjectId Not Exist") +} + +func Delete(ObjectId string) { + delete(Objects, ObjectId) +} + +` + +var hproseModels2 = `package models + +import ( + "errors" + "strconv" + "time" +) + +var ( + UserList map[string]*User +) + +func init() { + UserList = make(map[string]*User) + u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}} + UserList["user_11111"] = &u +} + +type User struct { + Id string + Username string + Password string + Profile Profile +} + +type Profile struct { + Gender string + Age int + Address string + Email string +} + +func AddUser(u User) string { + u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10) + UserList[u.Id] = &u + return u.Id +} + +func GetUser(uid string) (u *User, err error) { + if u, ok := UserList[uid]; ok { + return u, nil + } + return nil, errors.New("User not exists") +} + +func GetAllUsers() map[string]*User { + return UserList +} + +func UpdateUser(uid string, uu *User) (a *User, err error) { + if u, ok := UserList[uid]; ok { + if uu.Username != "" { + u.Username = uu.Username + } + if uu.Password != "" { + u.Password = uu.Password + } + if uu.Profile.Age != 0 { + u.Profile.Age = uu.Profile.Age + } + if uu.Profile.Address != "" { + u.Profile.Address = uu.Profile.Address + } + if uu.Profile.Gender != "" { + u.Profile.Gender = uu.Profile.Gender + } + if uu.Profile.Email != "" { + u.Profile.Email = uu.Profile.Email + } + return u, nil + } + return nil, errors.New("User Not Exist") +} + +func Login(username, password string) bool { + for _, u := range UserList { + if u.Username == username && u.Password == password { + return true + } + } + return false +} + +func DeleteUser(uid string) { + delete(UserList, uid) +} +` + +var hproseControllers = `package controllers + +import ( + "{{.Appname}}/models" + "encoding/json" + + "github.com/astaxie/beego" +) + +// Operations about object +type ObjectController struct { + beego.Controller +} + +// @Title create +// @Description create object +// @Param body body models.Object true "The object content" +// @Success 200 {string} models.Object.Id +// @Failure 403 body is empty +// @router / [post] +func (this *ObjectController) Post() { + var ob models.Object + json.Unmarshal(this.Ctx.Input.RequestBody, &ob) + objectid := models.AddOne(ob) + this.Data["json"] = map[string]string{"ObjectId": objectid} + this.ServeJson() +} + +// @Title Get +// @Description find object by objectid +// @Param objectId path string true "the objectid you want to get" +// @Success 200 {object} models.Object +// @Failure 403 :objectId is empty +// @router /:objectId [get] +func (this *ObjectController) Get() { + objectId := this.Ctx.Input.Params[":objectId"] + if objectId != "" { + ob, err := models.GetOne(objectId) + if err != nil { + this.Data["json"] = err + } else { + this.Data["json"] = ob + } + } + this.ServeJson() +} + +// @Title GetAll +// @Description get all objects +// @Success 200 {object} models.Object +// @Failure 403 :objectId is empty +// @router / [get] +func (this *ObjectController) GetAll() { + obs := models.GetAll() + this.Data["json"] = obs + this.ServeJson() +} + +// @Title update +// @Description update the object +// @Param objectId path string true "The objectid you want to update" +// @Param body body models.Object true "The body" +// @Success 200 {object} models.Object +// @Failure 403 :objectId is empty +// @router /:objectId [put] +func (this *ObjectController) Put() { + objectId := this.Ctx.Input.Params[":objectId"] + var ob models.Object + json.Unmarshal(this.Ctx.Input.RequestBody, &ob) + + err := models.Update(objectId, ob.Score) + if err != nil { + this.Data["json"] = err + } else { + this.Data["json"] = "update success!" + } + this.ServeJson() +} + +// @Title delete +// @Description delete the object +// @Param objectId path string true "The objectId you want to delete" +// @Success 200 {string} delete success! +// @Failure 403 objectId is empty +// @router /:objectId [delete] +func (this *ObjectController) Delete() { + objectId := this.Ctx.Input.Params[":objectId"] + models.Delete(objectId) + this.Data["json"] = "delete success!" + this.ServeJson() +} + +` +var hproseControllers2 = `package controllers + +import ( + "{{.Appname}}/models" + "encoding/json" + + "github.com/astaxie/beego" +) + +// Operations about Users +type UserController struct { + beego.Controller +} + +// @Title createUser +// @Description create users +// @Param body body models.User true "body for user content" +// @Success 200 {int} models.User.Id +// @Failure 403 body is empty +// @router / [post] +func (u *UserController) Post() { + var user models.User + json.Unmarshal(u.Ctx.Input.RequestBody, &user) + uid := models.AddUser(user) + u.Data["json"] = map[string]string{"uid": uid} + u.ServeJson() +} + +// @Title Get +// @Description get all Users +// @Success 200 {object} models.User +// @router / [get] +func (u *UserController) GetAll() { + users := models.GetAllUsers() + u.Data["json"] = users + u.ServeJson() +} + +// @Title Get +// @Description get user by uid +// @Param uid path string true "The key for staticblock" +// @Success 200 {object} models.User +// @Failure 403 :uid is empty +// @router /:uid [get] +func (u *UserController) Get() { + uid := u.GetString(":uid") + if uid != "" { + user, err := models.GetUser(uid) + if err != nil { + u.Data["json"] = err + } else { + u.Data["json"] = user + } + } + u.ServeJson() +} + +// @Title update +// @Description update the user +// @Param uid path string true "The uid you want to update" +// @Param body body models.User true "body for user content" +// @Success 200 {object} models.User +// @Failure 403 :uid is not int +// @router /:uid [put] +func (u *UserController) Put() { + uid := u.GetString(":uid") + if uid != "" { + var user models.User + json.Unmarshal(u.Ctx.Input.RequestBody, &user) + uu, err := models.UpdateUser(uid, &user) + if err != nil { + u.Data["json"] = err + } else { + u.Data["json"] = uu + } + } + u.ServeJson() +} + +// @Title delete +// @Description delete the user +// @Param uid path string true "The uid you want to delete" +// @Success 200 {string} delete success! +// @Failure 403 uid is empty +// @router /:uid [delete] +func (u *UserController) Delete() { + uid := u.GetString(":uid") + models.DeleteUser(uid) + u.Data["json"] = "delete success!" + u.ServeJson() +} + +// @Title login +// @Description Logs user into the system +// @Param username query string true "The username for login" +// @Param password query string true "The password for login" +// @Success 200 {string} lonin success +// @Failure 403 user not exist +// @router /login [get] +func (u *UserController) Login() { + username := u.GetString("username") + password := u.GetString("password") + if models.Login(username, password) { + u.Data["json"] = "login success" + } else { + u.Data["json"] = "user not exist" + } + u.ServeJson() +} + +// @Title logout +// @Description Logs out current logged in user session +// @Success 200 {string} logout success +// @router /logout [get] +func (u *UserController) Logout() { + u.Data["json"] = "logout success" + u.ServeJson() +} + +` + +var hproseTests = `package test + +import ( + "net/http" + "net/http/httptest" + "testing" + "runtime" + "path/filepath" + _ "{{.Appname}}/routers" + + "github.com/astaxie/beego" + . "github.com/smartystreets/goconvey/convey" +) + +func init() { + _, file, _, _ := runtime.Caller(1) + apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator)))) + beego.TestBeegoInit(apppath) +} + +// TestGet is a sample to run an endpoint test +func TestGet(t *testing.T) { + r, _ := http.NewRequest("GET", "/v1/object", nil) + w := httptest.NewRecorder() + beego.BeeApp.Handlers.ServeHTTP(w, r) + + beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String()) + + Convey("Subject: Test Station Endpoint\n", t, func() { + Convey("Status Code Should Be 200", func() { + So(w.Code, ShouldEqual, 200) + }) + Convey("The Result Should Not Be Empty", func() { + So(w.Body.Len(), ShouldBeGreaterThan, 0) + }) + }) +} + +` + +var hproseAddFunctions = []string{} + +func init() { + cmdHproseapp.Run = createhprose + cmdHproseapp.Flag.Var(&tables, "tables", "specify tables to generate model") + cmdHproseapp.Flag.Var(&driver, "driver", "database driver: mysql, postgresql, etc.") + cmdHproseapp.Flag.Var(&conn, "conn", "connection string used by the driver to connect to a database instance") +} + +func createhprose(cmd *Command, args []string) int { + isCreateHproseApp = true + curpath, _ := os.Getwd() + if len(args) > 1 { + cmd.Flag.Parse(args[1:]) + } + apppath, packpath, err := checkEnv(args[0]) + if err != nil { + fmt.Println(err) + os.Exit(2) + } + if driver == "" { + driver = "mysql" + } + if conn == "" { + } + os.MkdirAll(apppath, 0755) + fmt.Println("create app folder:", apppath) + os.Mkdir(path.Join(apppath, "conf"), 0755) + fmt.Println("create conf:", path.Join(apppath, "conf")) + os.Mkdir(path.Join(apppath, "controllers"), 0755) + fmt.Println("create controllers:", path.Join(apppath, "controllers")) + os.Mkdir(path.Join(apppath, "docs"), 0755) + fmt.Println("create docs:", path.Join(apppath, "docs")) + os.Mkdir(path.Join(apppath, "tests"), 0755) + fmt.Println("create tests:", path.Join(apppath, "tests")) + + fmt.Println("create conf app.conf:", path.Join(apppath, "conf", "app.conf")) + writetofile(path.Join(apppath, "conf", "app.conf"), + strings.Replace(hproseconf, "{{.Appname}}", args[0], -1)) + + if conn != "" { + ColorLog("[INFO] Using '%s' as 'driver'\n", driver) + ColorLog("[INFO] Using '%s' as 'conn'\n", conn) + ColorLog("[INFO] Using '%s' as 'tables'\n", tables) + generateHproseAppcode(string(driver), string(conn), "3", string(tables), path.Join(curpath, args[0])) + fmt.Println("create main.go:", path.Join(apppath, "main.go")) + maingoContent := strings.Replace(hproseMainconngo, "{{.Appname}}", packpath, -1) + maingoContent = strings.Replace(maingoContent, "{{.DriverName}}", string(driver), -1) + maingoContent = strings.Replace(maingoContent, "{{HproseFunctionList}}", strings.Join(hproseAddFunctions, ""), -1) + if driver == "mysql" { + maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/go-sql-driver/mysql"`, -1) + } else if driver == "postgres" { + maingoContent = strings.Replace(maingoContent, "{{.DriverPkg}}", `_ "github.com/lib/pq"`, -1) + } + writetofile(path.Join(apppath, "main.go"), + strings.Replace( + maingoContent, + "{{.conn}}", + conn.String(), + -1, + ), + ) + } else { + os.Mkdir(path.Join(apppath, "models"), 0755) + fmt.Println("create models:", path.Join(apppath, "models")) + os.Mkdir(path.Join(apppath, "routers"), 0755) + fmt.Println(path.Join(apppath, "routers") + string(path.Separator)) + + fmt.Println("create controllers object.go:", path.Join(apppath, "controllers", "object.go")) + writetofile(path.Join(apppath, "controllers", "object.go"), + strings.Replace(hproseControllers, "{{.Appname}}", packpath, -1)) + + fmt.Println("create controllers user.go:", path.Join(apppath, "controllers", "user.go")) + writetofile(path.Join(apppath, "controllers", "user.go"), + strings.Replace(hproseControllers2, "{{.Appname}}", packpath, -1)) + + fmt.Println("create tests default.go:", path.Join(apppath, "tests", "default_test.go")) + writetofile(path.Join(apppath, "tests", "default_test.go"), + strings.Replace(hproseTests, "{{.Appname}}", packpath, -1)) + + fmt.Println("create routers router.go:", path.Join(apppath, "routers", "router.go")) + writetofile(path.Join(apppath, "routers", "router.go"), + strings.Replace(hproserouter, "{{.Appname}}", packpath, -1)) + + fmt.Println("create models object.go:", path.Join(apppath, "models", "object.go")) + writetofile(path.Join(apppath, "models", "object.go"), apiModels) + + fmt.Println("create models user.go:", path.Join(apppath, "models", "user.go")) + writetofile(path.Join(apppath, "models", "user.go"), apiModels2) + + fmt.Println("create docs doc.go:", path.Join(apppath, "docs", "doc.go")) + writetofile(path.Join(apppath, "docs", "doc.go"), "package docs") + + fmt.Println("create main.go:", path.Join(apppath, "main.go")) + writetofile(path.Join(apppath, "main.go"), + strings.Replace(hproseMaingo, "{{.Appname}}", packpath, -1)) + } + return 0 +}