/**********************************************************\ | | | 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{}, ), ), ` )