gin xorm搭建web服务


现代web框架的主流采用轻量级 中间件式框架 只有api服务 其他都扔到cdn这种方式 开发快速 拼装能力强 按需添加 大受欢迎

框架成熟度

gin 出错信息详尽 方便调试
路由性能是gin卖点 web框架 路由性能最好 灵活方便
路由Get("/name")和 Get("/:id")  只要把Get("/name")放在Get("/:id")前面 不冲突
路由模块 先匹配前面 没匹配上再匹配后面
框架可持续发展 gin的代码注释量大 易读 便于参与 包装中间件 超级容易

产品困境

迭代速度快 数据库操作接口变动频繁 service层工作量大 项目成长 代码量越大 项目启动越慢 影响速度
框架或编程语言 调试简单 上手快 启动速度快 保密性高 适用高并发 性能优越等优点
功能满足度 产品研发时梳理需求
一支持高性能的restful api服务
二支持web页面服务
三支持快速跌代

编程语言成本

/团队学习成本/项目迁移成本/社区活跃度/搜索指数
golang框架 国外matini revel gin等 国内beego 都很优秀
很多场景现 框架能解决问题 但需要定制框架
框架要求 简单好用 高扩展性和开发性 不重新造轮子 用第三方成果

应用生态圈

传统行业应用场景 包括cms应用 博客 商城 微信管理后端等五大基础领域
移动应用 高并发和快速迭代  秒杀拍卖采集和处理等go用武之地

框架结构

典型的项目框架 文件夹目录 约定
控制器controller 文件夹 存储控制器文件
控制器文件名 首字母大写  如 UserCtrl
控制器文件名 须和业务强相关 用户控制器 文件名User.go 资源管理控制器名Attach
文件名称区分大小写 实体目录 entity 存储 数据库对应模型文件
文件名 首字母大写 与数据库内对应表名一致 auth_group 文件名 AuthGroup.go
数据库表名称 以下划线开头的字母 对应的实体类中相应的字段大写 表user_info对应实体名UserInfo.go
文件名区分大小写

视图目录view

视图目录包括如下子目录
公共模板目录public 存放公共模板 head的head.html 底部foot.html 错误页面error.html
应用模块目录 每一个模块用一个目录来独立存放
假设用户模块有注册 登录 密码找回 个人资料等四个逻辑页面
User模块登录页面login.html 注册页面register.html 密码重置resetpwd.html 个人资料profile.html
静态资源asset
存放静态资源子目录
image静态图片
css 放css文件
js 放js文件.常页面的js逻辑文件也投放到该目录下,该目录可以建立子目录,和view下的子目录一一对应
font用bootstrap框架 用到字体文件 该文件夹用于存储字体文件
plugin 存放较大的插件 如kindedit插件 bootstarp adminlte 等  将包含css和js或者image文件包叫做插件
静态资源独立存放好处 方别实施动静态分离

业务层目录Service

文件夹 存放业务层逻辑
业务层 指具体某一业务实现的方式 对外提供接口 对内调用数据库操作 业务层命名约定
业务逻辑名  首字母大写 且只能为字母
业务逻辑文件 以Service结尾 如用户业务逻辑 文件名UserService.go

参数封装层model

每一个业务的请求参数 封装成一个struct 对于用户管理模块
根据关键字如姓名 电话等查询用户信息 根据注册时间查询用户及分页 排序等
因此将这些参数封装成一个bean
type  PageArg struct{
Kword  string  `form:”kword” json:”kword”`
Datefrom  time.Time  `form:”datefrom”  json:”datefrom”`
Dateto  time.Time  `form:”dateto”  json:”dateto”`
Desc  string  `form:”desc”  json:”desc”`
Asc  string  `form:”asc”  json:”asc”`
Pagefrom  int  `form:”pagefron”  json:”pagefrom”`
Pagesize  int  `form:”pagesize”  json:”pagesize”`
}
考虑到有些参数是常用的 将UserArg做如下定义
Type  UserArg struct{PageArg
//…… other arg
}

核心包rest

核心包内置 应用管理框架 及常用工具类软件
orm封装工具OrmEngin.go
自定义函数数FunMap.go
参数响应结果封装Result.go
验证码管理Captcha.go
网络访问模块Http.go
加密方法封装Crypto.go
应用管理模块Restgo.go
其他用户自行添加

配置目录config

配置存放目录在config下
日志配置文件log4g.xml
应用配置文件application. Properties
其他配置文件自行添加
应用启动文件main.go
系统配置参数
#应用运行模式 gin框架支持debug/release/test三种
rest.app.mode=debug
#应用名称 用做应用标识 便于分布式计算
rest.app.name=rest
#应用部署的访问协议 支持http/https两种
rest.app.protocal=http
#应用域名
rest.app.domain=localhost
#静态资源所在的服务器地址 动静态分离
rest.app.asset=localhost
#请求contextpath
rest.app.ctxpath=
#服务器绑定的地址
rest.app.addr=
#端口
rest.app.port=80
#sessionID标识字符串 对标PHP的SESSIONID,java的JSESSIONID
rest.session.name=GSESSIONID
#session过期时间以秒为单位 0表示访问结束时过期
rest.session.timelive=3600
#默认数据资源配置,数据库驱动类型为mysql 详情 xorm
rest.datasource.default.driveName=mysql
#连接串
rest.datasource.default.dataSourceName=root:root@/restgo?charset=utf8
#连接池中idle态链接最大个个数
rest.datasource.default.maxIdle=10
#连接池最大打开连接数
rest.datasource.default.maxOpen=5
#是否显示sql语句
rest.datasource.default.showSql=true
#支持配置多个数据库
rest.datasource.ds1.driveName=mysql
#连接串
rest.datasource.ds1.dataSourceName=root:root@/restgo2?charset=utf8
#连接池中idle态链接最大个个数
rest.datasource.ds1.maxIdle=10
#连接池最大打开连接数
rest.datasource.ds1.maxOpen=5
#是否显示sql语句
rest.datasource.ds1.showSql=true
#log4g日志配置文件路径地址
rest.logger.filepath=config/log4g.xml
#静态资源及映射,如下配置则访问 localhost/assets/a.jpg 系统将去 asset目录下寻找a.jsp
rest.static.assets=./asset
#图片资源存放路径访问 localhost/mnt/a.jpg 系统将去 /data/restgo/mnt目录下寻找a.jsp
rest.static.mnt=/data/restgo/mnt
#favorite.ico 访问localhost/favorite.ico 渲染./favorite.ico
rest.staticfile.favicon.ico=favicon.ico
#/静态html文件目录 访问localhost/manual 系统寻找当前目录/staticManualDir
rest.staticfs.manual=./staticManualDir
#视图存放路径
rest.view.path=view
#视图中模板标签开始标记
rest.view.deliml={{
#视图中模板标签结束标记
rest.view.delimr=}}
#自定义微信配置支持
rest.weixin.appid=a1278287120128
rest.weixin.appsecret=$^siwe2i23i^(12
rest.weixin.token=helloword
#redis服务器地址
rest.redis.host=localhost
#redis端口
rest.redis.port=3306
#redis 密码
rest.redis.passwd=$^siwe2i23i^(12
#使用的数据库
rest.redis.db=0
解析配置文件 配置文件以.xml .ini等格式存储
java 常以.properties文件和.yml格式的文件 以.properties格式存储配置信息
type Config struct {
   App map[string]string
   Session map[string]string
   Datasource map[string](map[string]string)
   Static map[string]string
   StaticFile map[string]string
   Logger map[string]string
   Templete map[string]string
   All map[string]string
}
map容器存储key-value类型数据
不同的配置类型设计不同的map
App   配置对应 rest.app
Session 配置对应 rest.session 依此类推
datasource 生产环境支持多数据源 对应map设计成map[string](map[string]string)形式
cfg:&{App:map[addr: asset:http://h.com:8080/asset ctxpath: domain:h.com mode:release name:i9527演示 port:8080 protocal:http]
Session:map[name:rest_session_id timelive:3600]
Datasource:map[default:map[dataSourceName:rt:root@/rt?charset=utf8 driveName:mysql maxIdle:10 maxOpen:5 showSql:false]]
Static:map[asset:./asset mnt:/data/release/static]
StaticFile:map[favicon.ico:favicon.ico]
StaticFS:map[admin:./admin static:./static]
Logger:map[filepath:config/log4g.xml]
View:map[deliml:{{ delimr:}} path:view]
TempFileMap:map[]
All:map[rest.app.addr:
rest.app.asset:http://h.com:8080/asset
rest.app.ctxpath:
rest.app.domain:h.com
rest.app.mode:release
rest.app.name:i9527演示
rest.app.port:8080
rest.app.protocal:http
rest.datasource.default.dataSourceName:rt:rt@/rest?charset=utf8
rest.datasource.default.driveName:mysql
rest.datasource.default.maxIdle:10
rest.datasource.default.maxOpen:5
rest.datasource.default.showSql:false
rest.logger.filepath:config/log4g.xml
rest.redis.db:0
rest.redis.host:localhost
rest.redis.passwd:$^siwe2i23i^(12
rest.redis.port:3306
rest.session.name:rest_session_id
rest.session.timelive:3600
rest.static.asset:./asset
rest.static.mnt:/data/release/static
rest.staticfile.favicon.ico:favicon.ico
rest.staticfs.admin:./admin
rest.staticfs.static:./static
rest.view.deliml:{{
rest.view.delimr:}}
rest.view.path:view
rest.weixin.appid:a127828
rest.weixin.appsecret:$^si^(12
rest.weixin.token:hello]
}
添加自定义配置文件
系统灵活性 需要支持添加配置文件 以微信api对接为例 自定义配置
设计模块模块名 公众平台appid appsecret 及双方加密因子token
#自定义微信配置支持
rest.weixin.appid=a127828
rest.weixin.appsecret=$^si^(12
rest.weixin.token=helloword
获取配置信息 //获取整数
func (cfg *Config) LoadCfg(key string) string {
  return cfg.All[key]
}//获取字符串配置
func (cfg *Config) LoadString(key string) string {
  return cfg.All[key]
}//获取整数
func (cfg *Config) LoadInt(key string) (int,error) {
  return strconv.Atoi (cfg.All[key])
}//获取32位整数
func (cfg *Config) LoadInt64(key string) (int64,error) {
  return strconv.ParseInt(cfg.All[key],10,64)
}//获取64位整数
func (cfg *Config) LoadInt32(key string) (int64,error) {
  return strconv.ParseInt(cfg.All[key],10,32)
}//获取布尔配置
func (cfg *Config) LoadBool(key string) bool {
  return cfg.All[key]=="true" || "TRUE"==cfg.All[key]
}//cfg.loadCfg("restgo.weixin.appid")
对配置函数进行扩展 直接修改Config.go
路由统一管理
路由将用户请求 request uri 与后端业务绑定 http://localhot/user/register
requesturi 为/user/register代表用户注册 后端需要提供函数 注册页面 注册账号服务
系统日益复杂 路由参数越多  统一管理 利于开发和维护
每个控制器对应一个模块 控制器内部实现路由注册功能 有利于维护 思路清晰
只需对控制器注册 控制器中的路由注册
路由容错功能 如http://localhot//user/register 和http://localhot/user/register一样
type Page struct {//具体控制器
  restgo.Controller
}//实现Router方法
func (ctrl *Page)Router(router *gin.Engine){
    router.POST("page/create",ctrl.create)
    router.POST("page/update",ctrl.update)
    router.POST("page/query",ctrl.query)
    router.POST("page/delete",ctrl.delete)
    router.POST("page/findOne",ctrl.findOne)
    router.GET("/",ctrl.showIndex)
}
Main.go 主函数
func registerRouter(router *gin.Engine){
  new(controller.Page).Router(router)
}
func main(){
  router := gin.Default()
  registerRouter(router *gin.Engine)
} 添加控制器 在 registerRouter 中添加注册
错误信息统一配置
提高系统友好性 将不能识别的路由 内部跳转的路由统一配置 别于前端统一定制
路由路径错误 大小写敏感 /user/register 被写作了/user/Register
后端未提供服务
统一处理的路由规则 规则和服务之间 未提供明确的服务 则调用错误信息 利用这个特效
系统runtime类错误
外部传递俩个参数 a和b 内部计算c=a/b 当b为0时 出现runtime错误 会调用错误配置方法
定制自己的路由框架
预处理uri 主要是对uri进行预处理 包括格式判断 过滤斜杠等
判断页面 约定使用.shtml 结尾的都是页面 不包含任务后缀的都是api
判断模板是否存在
通过判断文件路径是否存在来判断模板是否存在 约定所有模板都存放到view文件夹下
模板名和文件名一一对应 对于模板文件位置为view/user/list.html
{{define "user/list.html"}} somthing {{end}}
Config中对模板进行初始化
f, _ := filepath.Glob("view*")
for _, b := range f {
  cfg.TempFileMap[b] = 0
}
默认路由函数中判断 request uri 是否存在在cfg.TempFileMap中
var urimap map[string]int = make(map[string]int)
//Controller.go//对未定义的路由规则进行处理
func NoRoute(ctx *gin.Context) {
  uri := ctx.Request.RequestURI
  isAjax := "XMLHttpRequest"==ctx.GetHeader("X-Requested-With")
  isPage := strings.Contains(ctx.Request.RequestURI,".shtml")
  uri = strings.TrimLeft(uri, "/")
  uri = strings.TrimSuffix(uri, ".shtml")
  //如果已经定义过了则是一定存在的 //存在则=计算统计次数
  //不存在则为-1  //0 代表初始化  //如果定义了
  stat, has := urimap[uri]  //如果有
  if !has { //没有则先初始化一下
    urimap[uri] = 0
  }
  if 0 == stat {//寻找初始化的数据
    cfg := GetCfg()
    var flag int = -1
    for fpath, _ := range cfg.TempFileMap {
     fpath = strings.Replace(fpath,"\\","/",-1)
     if strings.Index(fpath, uri) > -1 {
       flag = 1
       break
     }
     fmt.Print(fpath)
    }
    urimap[uri] = stat + flag
  }
  //如果不存在则跳转出错页面
  if 0 > urimap[uri] {
    NoMethod(ctx)
  } else {//如果是AJAX
    if isPage{//response html
     ctx.HTML(200, uri+".html", "Q")
    }else if isAjax{//response json
     ctx.JSON(200,nil)
    }
  }
}
func NoMethod(ctx *gin.Context) {
  uri := ctx.Request.RequestURI
  fmt.Printf("NoMethod" + uri)
  uri = strings.TrimLeft(uri, "/")
  uri = strings.TrimSuffix(uri, ".shtml")
  //ctx.HTML(http.StatusOK, model+"/"+action+".html", gin.H{"title": "test"})
  ctx.HTML(200, uri+".html", "Q")
}
mvc思想 模型(model) 视图(view) 控制器(controller)的缩写
软件设计典范 用业务逻辑 数据 界面显示分离的方法组织代码
将业务逻辑聚集到一个部件 好处改进和个性化定制界面及用户交互的同时 不需要重新编写业务逻辑
控制器定义 控制器文件controller目录 控制器 struct 名和 文件名保持大小写一致 采用驼峰命名 首字母大写
package controller
import (
  "restgo/restgo"
  "github.com/gin-gonic/gin"
  "net/http"
  "fmt"
)
type Page struct {
  restgo.Controller
}//前置操作
func (ctrl *Page)before() gin.HandlerFunc {
  return func(ctx *gin.Context) {
    uri := ctx.Request.RequestURI
    fmt.Print(uri)
    if 1==1{
     ctx.Next()
    }
    return
  }
}
func (ctrl *Page)Router(router *gin.Engine){
  router.GET("/",ctrl.showIndex)
  r := router.Group("page").Use(ctrl.before())
  r.POST("create",ctrl.create)
  r.POST("update",ctrl.update)
  r.POST("query",ctrl.query)
  r.POST("delete",ctrl.delete)
  r.POST("findOne",ctrl.findOne)
}
func (ctrl * Page) create(ctx *gin.Context){
  ctrl.Data = []int{1,2,3}
  ctrl.AjaxData(ctx)
}
func (ctrl * Page) showIndex(ctx *gin.Context){
   ctx.HTML(http.StatusOK,"panel/index.html","")
}
func (ctrl *Page)delete(ctx *gin.Context){}
func (ctrl *Page)update(ctx *gin.Context){}
func (ctrl *Page)query(ctx *gin.Context){}
func (ctrl *Page)findOne(ctx *gin.Context){}
控制器文件四部分
struct定义
控制器和rest.Controller有同样的方法 rest.Controller 只实现 数据响应接口
前置操作before
主要实现前置操作 当一个控制器 方法被访问时 需要进行特别的数据处理 比如鉴权 参数校验 前置操作逻辑不宜太复杂
路由控制函数router
这个函数是必须实现的方法 需要在该函数中实现路由配置和映射
业务函数实现
主要通过函数实现 一个控制器通常实现fineOne/query/create/update方法
跳转重定向实现简单 调用gin框架内置的即可
func (ctrl *Page)Redirect(ctx *gin.Context){
  ctx.Redirect(302,"/")
}
资源控制器
api 服务器无需资源控制器 因为api一般只需要响应json/xml
但有应用比如cms 需要静态资源服务 要使用资源控制器
资源控制器设计考虑
实现缓存 加快系统访问速度
灵活配置 很多资源服务 和nginx服务器反向代理相互整合的 考虑易整合性
资源管理 要统一规划 资源有静态 如css js文件 有动态的 如上传后的图片 因此需要动态规划保证资源的存储空间和访问规则等
对gin框架做封装 实现强大的资源管理功能
app.properties
#静态资源及映射 访问 localhost/assets/a.jpg  系统 去 asset目录下寻找a.jsp
rest.static.assets=./asset
#图片资源存放路径访问 localhost/mnt/a.jpg  系统 /data/rest/mnt 目录下寻找a.jsp
rest.static.mnt=/data/rest/mnt
#favorite.ico 访问localhost/favorite.ico 则渲染./favorite.ico
rest.staticfile.favicon.ico=./favicon.ico
是通过main函数中的方法实现的//main.go
for k,v :=range cfg.Static{
  router.Static(k, v)
}
for k,v :=range cfg.StaticFile{
  router.StaticFile(k, v)
}
static是调用golang内置的文件服务 absolotepath  文件夹绝对路径
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
fileServer.ServeHTTP(c.Writer, c.Request)
staticfile 方法调用gin自身实现的GET方法
group.GET(relativePath, handler)
group.HEAD(relativePath, handler)
controller绑定参数 获取path中的参数
// this one will match /user/john/ and also /user/john/send
// If no other routers match /user/john, it will redirect to /user/john/
router.GET("/user/:name/*action", func(c *gin.Context) {
  name := c.Param("name")
  action := c.Param("action")
  message := name + " is " + action
  c.String(http.StatusOK, message)
})
获取query string中的参数
// Query string parameters are parsed using the existing underlying request object.
// The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
  firstname := c.DefaultQuery("firstname", "Guest")
  lastname := c.Query("lastname")
// shortcut for c.Request.URL.Query().Get("lastname")
  c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
处理表单数据
// Query string parameters are parsed using the existing underlying router.POST("/form_post", func(c *gin.Context) {
  message := c.PostForm("message")
  nick := c.DefaultPostForm("nick", "anonymous")
  c.JSON(200, gin.H{
    "status":  "posted",
    "message": message,
    "nick":   nick,
  })
})
处理单文件上传
//curl -X POST http://localhost:8080/upload -F  "file=@/User/apple/test.zip" -H "Content-Type: multipart/form-data"
 router.POST("/upload", func(c *gin.Context) { // single file
  file, _ := c.FormFile("file")
  log.Println(file.Filename)
  // Upload the file to specific dst.
  // c.SaveUploadedFile(file, dst)
  c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
处理多文件上传
// curl -X POST http://localhost:8080/upload \
-F "upload[]=@/Users/appleboy/test1.zip" \
-F "upload[]=@/Users/appleboy/test2.zip" \
-H "Content-Type: multipart/form-data"
router := gin.Default()
// Set a lower memory limit for multipart forms (default is 32 MiB)
// router.MaxMultipartMemory = 8 << 20  // 8 MiB
router.POST("/upload", func(c *gin.Context) {// Multipart form
  form, _ := c.MultipartForm()
  files := form.File["upload[]"]
  for _, file := range files {
    log.Println(file.Filename)
    // Upload the file to specific dst//c.SaveUploadedFile(file, dst)
  }
  c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
控制器模型绑定 gin 内置绑定操作
一 MustBind 类 Bind BindJSON BindQuery 类绑定主要 绑定失败则 返回400错误
二 ShouldBind类 ShouldBind ShouldBindJSON ShouldBindQuery 绑定失败 不响应400 而是将错误信息返回给上下文环境
开发者自行处理 gin框架支持JSON  XML Form Query FormPost FormMultipart ProtoBuf  MsgPack等格式
ctx.ShouldBindJSON(&pageArg)
ctx.BindJSON(&pageArg)
ctx.ShouldBind(&pageArg)
ctx.BindWith(&pageArg,binding.JSON)
ctx.ShouldBindWith(&pageArg,binding.JSON)
数据绑定流程
model文件夹下定义 PageArg 结构体 其中需要绑定json数据必须定义json的tag
如果绑定form表单数据则需要定义包含form的tag
type PageArg struct {
  Kword string `form:"kword"`
  Datefrom time.Time `form:"datefrom" time_format:"2006-01-02 15:04:05"`
  Dateto time.Time  `form:"dateto" time_format:"2006-01-02 15:04:05"`
  Pagesize int    `form:"pagesize" json:"pagesize"`
  Pagefrom int    `form:"pagefrom" json:"pagefrom"`
  Desc string     `form:"desc" json:"desc"`
  Asc  string     `form:"asc" json:"asc"`
}客户端发送json格式数据 区分 JSON格式必须符合规范
//#curl -v  -H "content-type:application/json" -d "{\"pagefrom\":1,\"pagesize\":20}" \
http://127.0.0.1/test/query
服务器接收并进行数据处理返回数据//定制路由映射绑定关系
func (ctrl *TestController)Router(router *gin.Engine){
  r := router.Group("test").Use(ctrl.before())
  r.Any("query",ctrl.query)
}
//实现绑定
func (ctrl *TestController)query(ctx *gin.Context){
  var pageArg model.PageArg
  ctx.ShouldBindJSON(&pageArg)
  restgo.ResultOk(ctx,pageArg)
}
控制器参数校验 参数校验 用于校验前端提交参数的合法性和合理性
gin集成go-playground/validator.vx 作为校验插件
go-playground/validator.vx 内置校验方法 email/url/base64/required等 可自定义校验
func (ctrl *TestController)query(ctx *gin.Context){
  var pageArg model.PageArg
  ctx.ShouldBindJSON(&pageArg)
  err := validator.New().Struct(&pageArg)
  if err != nil {  // this check is only needed when your code could produce
    // an invalid value for validation such as interface with nil
    // value most including myself do not usually have code like this.
    if _, ok := err.(*validator.InvalidValidationError); ok {
     fmt.Println(err)
     return
    }
    for _, err := range err.(validator.ValidationErrors) {
     fmt.Println(err.Namespace())
     fmt.Println(err.Field())
     fmt.Println(err.StructNamespace()) // can differ when a custom TagNameFunc is registered or
     fmt.Println(err.StructField())   // by passing alt name to ReportError like below
     fmt.Println(err.Tag())
     fmt.Println(err.ActualTag())
     fmt.Println(err.Kind())
     fmt.Println(err.Type())
     fmt.Println(err.Value())
     fmt.Println(err.Param())
     fmt.Println()
    }  // from here you can create your own error messages in whatever language you wish
    //return
  }
  restgo.ResultOk(ctx,err)
}推荐使用自定义校验方案  处理更灵活 交互体验更友好
type PageArg struct {
  Kword string `form:"kword"`
  Datefrom time.Time `form:"datefrom" time_format:"2006-01-02 15:04:05"`
  Dateto time.Time  `form:"dateto" time_format:"2006-01-02 15:04:05"`
  Pagesize int    `form:"pagesize" json:"pagesize"`
  Pagefrom int    `form:"pagefrom" json:"pagefrom"  validate:"gte=0"`
  Desc string     `form:"desc" json:"desc"`
  Asc  string     `form:"asc" json:"asc"`
}
func (p* PageArg)Validate() (bool,error){
  if p.Datefrom.IsZero() {
    return false,errors.New("输入开始时间")
  }
  if p.Pagesize>100 {
    return false,errors.New("一次只能请求100条数据")
  }
  if p.Pagefrom<0 {
    return false,errors.New("分页参数错误")
  }
  return true,nil
}
ctx.ShouldBindJSON(&pageArg)
bindok,err := pageArg.Validate()
if !bindok{restgo.ResultFail(ctx,err)
}
数据响应 1 数据格式的封装 2 数据响应类型
数据响格式封装 为了别于运维 需要将响应结果封装到函数 这样前端获取的数据结构统一
对于api来说 响应到前端的数据包含 几个参数
code 用于指示数据请求状态 200表示成功
data 后端服务返回的基础数据 一般是对象
msg 后端调用返回的操作提示 如恭喜 操作成功 或 失败原因
rows 当后端返回数组 该参数代表获取的全部记录 该参数适配jquerytable系列前端框架
total 当返回数组时 该参数用来标识全部记录数目
rest/Result.go 封装了4个函数
//对于基础响应函数 需要制定返回的code
func Result(ctx * gin.Context,code int,data interface{},msg string){
  ctx.JSON(http.StatusOK, gin.H{"code": code, "data": data, "msg":msg})
}
//请求成功响应函数 携带数据
func ResultOk(ctx * gin.Context,data interface{}){
  ctx.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": data, "msg": ""})
}
//请求列表成功响应函数 携带数据列表和总页数
func ResultList(ctx * gin.Context,data interface{},total int64){
  ctx.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "rows": data, "msg": "","total":total})
}
//请求成功响应提示 携带提示和数据
func ResultOkMsg(ctx * gin.Context,data interface{},msg string){
  ctx.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": data, "msg": msg})
}
//请求失败响应函数 携带失败原因
func ResultFail(ctx * gin.Context,err interface{}){
  ctx.JSON(http.StatusOK, gin.H{"code": http.StatusBadRequest, "data": nil, "msg":err})
}
//请求失败响应函数 携带失败原因和数据
func ResultFailData(ctx * gin.Context,data interface{},err interface{}){
  ctx.JSON(http.StatusOK, gin.H{"code": http.StatusBadRequest, "data": data, "msg":err})
}
控制器数据响应 数据类型gin框架内置 数据类型响应格式 包括xml\json\yaml\html等
xml格式
func main() {
  r := gin.Default()
  r.GET("/someXML", func(c *gin.Context) {
    c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  })// Listen and serve on 0.0.0.0:8080
  r.Run(":8080")
}
//json格式
func main() {
  r := gin.Default()
  r.GET("/moreJSON", func(c *gin.Context) {// You also can use a struct
  var msg struct {
    Name   string `json:"user"`
    Message string
    Number  int
  }
  msg.Name = "Lena"// Note that msg.Name becomes "user" in the JSON
  msg.Message = "hey"
  msg.Number = 123 //output:{"user": "Lena", "Message": "hey", "Number": 123}
  c.JSON(http.StatusOK, msg)
})// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
//yaml格式
func main() {
  r := gin.Default()
  r.GET("/someYAML", func(c *gin.Context) {
  c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})  // Listen and serve on 0.0.0.0:8080
 r.Run(":8080")
}
静态资源文件
func main() {
   r := gin.Default()
   r.StaticFile("/favicon.ico", "./resources/favicon.ico")// Listen and serve on 0.0.0.0:8080
   r.Run(":8080")
}
html文件
func main() {
  router := gin.Default()
  router.LoadHTMLGlob("templates/*")//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
  router.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
     "title": "Main website",
    })
  })
  router.Run(":8080")
}
view/index.html
{{templete  "index.html"}}<html><h1>{{ .title }}</h1></html>{{end}}
控制器controller编程 以用户管理为例 如何控制器编程
用户管理需要的接口
用户管理模块api描述
编号 请求格式   接口描述      请求数据  响应数据
1  /user/query   姓名 电话 注册时间 最后登陆时间  角色等搜索和统计用户
2  /user/findOne  根据用户编号获取基础信息
3  /user/login   用户名 密码等 登录操作
4  /user/register 用户注册操作
5  /user/resetpwd 用户重置密码
6  /user/edit   编辑用户基础信息
7  /user/profile 用户个人中心页面
新建控制器
在 controller 文件夹下新建控制器 User.go 内容如下
package controller
type User struct {
  rest.Controller
}
添加路由映射规则
对 User 结构体扩展如下方法
func (ctrl *User)Router(router *gin.Engine){
   r := router.Group("user")
   r.Any("query",ctrl.query)
}
实现query方法
func (ctrl *User)query(ctx *gin.Context){
  var userArg model.UserArg
  ctx.ShouldBindJSON(&userArg)
  //根据 userArg 拼接sql查询条件获取数据  //var data = ...
  //最后响应数据列表到前端  //rest.ResultList(ctx,data,120)
}
查询用的参数 不建议对参数名称采用特殊的处理 建议封装到一个结构体
如关于用户信息的查询参数 封装到 UserArg 结构体中
type PageArg struct {
  Kword string `form:"kword"`
  Datefrom time.Time `form:"datefrom" time_format:"2006-01-02 15:04:05"`
  Dateto time.Time  `form:"dateto" time_format:"2006-01-02 15:04:05"`
  Pagesize int    `form:"pagesize" json:"pagesize"`
  Pagefrom int    `form:"pagefrom" json:"pagefrom"  validate:"gte=0"`
  Desc string     `form:"desc" json:"desc"`
  Asc  string     `form:"asc" json:"asc"`
}
type UserArg struct {
  PageArg
  ttype string `form:"ttype" json:"ttype"`//用于区分是注册时间还是登陆时间
}
使用如下方法判断
var userArg model.UserArg
ctx.ShouldBindJSON(&userArg)
if len(userArg.Kword)>0{}
模型M和Orm
java中ssm框架 和数据库操作 有entity dao 及service层
entity将数据库表结构和java对象关联起来
dao 处理对数据库的基本操作
service层封装 具体的业务逻辑
借鉴java框架 将 entity dao service 封装到一层 称之为模型层
数据库处理 用一个对象或者结构体完成对某一个表的操作
不关注表的具体名字或表内字段命名等细节
不拼接sql语句 不将数据库操作返回的数组和字段进行 mapper 映射 ORM 框架自动处理好一切
golang的ORM框架 xorm gorm 及beegoorm 以xorm为例集成ORM框架
使用xorm
#go get github.com/go-xorm/xorm
import (
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
)
var engine *xorm.Engine
func main() {
  var err error
  engine, err = xorm.NewEngine("mysql", "root:123@/test?charset=utf8")
}
使用数据源ds1 通过如下方式调用
func (service *UserService)FindOne(userId int64)(entity.User){
  var user entity.User
  orm := rest.OrmEngin("ds1")
  orm.Id(userId).Get(&user)
  return  user
}
使用默认数据源时 通过如下方式调用
func (service *UserService)FindOne(userId int64)(entity.User){
  var user entity.User
  orm := restgo.OrmEngin()
  orm.Id(userId).Get(&user)
  return  user
}
实体entity 在entity目录下新建User.go 文件
package entity
import "time"
type User struct {
  ID int64 `xorm:"pk autoincr 'id'" json:"id"`
  CreateAt time.Time `xorm:"created" json:"create_at"  time_format:"2006-01-02 15:04:05"`
  Stat int `json:"stat"`
  UserName string `xorm:"varchar(40)" json:"user_name"`
  Passwd string `xorm:"varchar(40)" json:"-"`
  NickName string `xorm:"varchar(40)" json:"nick_name"`
  Avatar string `xorm:"varchar(180)" json:"avatar"`
}
实现 service service目录 创建 UserService.go 内容如下
package service
type UserService struct {}//根据userId 获取用户编号
func (service *UserService)FindOne(userId int64)(entity.User){
  var user entity.User
  orm := rest.OrmEngin("ds1")
  orm.Id(userId).Get(&user)
  return  user
}//定义了根据ID获取用户信息的服务
高级查询 特殊的服务 如根据关键字 创建时间等查询用户信息 需要设计便于维护的数据结构 将查询条件封装到结构体
model目录 定义用户信息查询条件结构体UserArg.go
package model
type UserArg struct {
  PageArg
  ttype string `form:"ttype" json:"ttype"`
}其中 PageArg 结构体是公用的结构体 定义了常用的查询条件
如时间Datafrom Dateto 关键字Kname 及分页起始页 pagefrom 分页大小pagesize asc排序字段 desc排序字段等
type PageArg struct {
  Kword string `form:"kword"`
  Datefrom time.Time `form:"datefrom" time_format:"2006-01-02 15:04:05"`
  Dateto time.Time  `form:"dateto" time_format:"2006-01-02 15:04:05"`
  Pagesize int    `form:"pagesize" json:"pagesize"`
  Pagefrom int    `form:"pagefrom" json:"pagefrom"  validate:"gte=0"`
  Desc string     `form:"desc" json:"desc"`
  Asc  string     `form:"asc" json:"asc"`
}
UserService.go 中定义通用查询方法Query
func (service *UserService)Query(arg model.UserArg)([]entity.User){
  var users []entity.User = make([]entity.User , 0)
  orm := rest.OrmEngin("ds1")
  t := orm.Where("id>0")
  if (0<len(arg.Kword)){
    t = t.Where("name like ?","%"+arg.Kword+"%")
  }
  if (!arg.Datefrom.IsZero()){
    t = t.Where("create_at >= ?",arg.Datefrom)
  }
  if (!arg.Dateto.IsZero()){
    t = t.Where("create_at <= ?",arg.Dateto)
  }
  t.Limit(arg.GetPageFrom()).Find(&users)
  return  users
}
在UserController.go中调用 Query 方法
func (ctrl *User)query(ctx *gin.Context){
  var userArg model.UserArg
  ctx.ShouldBind(&userArg)
  ret := userService.Query(userArg)//最后响应数据列表到前端
  rest.ResultList(ctx,ret,1024)
}
视图层V web 前后端分离已经成为主流 后端restful风格api大行其道
前端js框架 vue reactjs anglarjs百花齐放
golang 视图层却是后端渲染 适合做安全性较高的工作
视图配置参数在启动时加载到服务器并进行解析
前后端分离 不用后端渲染 将系统参数模块化管理
将公用参数通过js的形式渲染
如下所示 js文件有/public/userinfo.shtml提供 这是后端提供的页面服务 可以将用户信息存放到该文件中
<!DOCTYPE html><html><head><script src="/public/userinfo.shtml"></script></head><body></body></html>
html编写前端 独立部署 二者通过api交互 这种方式要求非常高 需要在开始时将接口定下来
前后端分离是趋势 因为大数据崛起 前端呈现多样化 后端高性能和高并发 要求前后端分离

模板基础语法

模板用自定义函数 自动函数统一管理 管理模块rest/Func.go 该模块内置ctxpath version 等
如定制新的方法 hello方法为例 自定义 hello 函数 需要在restgo/Func.go中添加hello函数
func init(){
  restFuncMap["ctxpath"]=ctxpath
  restFuncMap["pageurl"]=pageurl
  restFuncMap["apiurl"]=apiurl
  restFuncMap["version"]=version
  restFuncMap["hello"]=hello
}
func GetFuncMap()(template.FuncMap){ return restFuncMap
}
func hello(d string) string{  return "hello "+d
}在view/user目录下新建hello.html
调用该方法{{define "user/hello.html"}}
<!DOCTYPE html><html><head></head><body>{{hello "rest"}}</body></html>{{end}}
请求结果如下 curl localhost/user/hello.shtml
<!DOCTYPE html><html><head></head><body>hello rest</body></html>
包含文件
在freemarker 或者jsp中 熟悉include方法 该方法对菜单 公用头部文件和尾部文件等公用模板 处理方便和快捷
go就是template
head left foot区域公共 不同的是content区域  抽象如下模板//public/head.html
{{define "public/head.html"}}这里是head区域内容{{end}}
//public/foot.html
{{define "public/foot.html"}}这里是foot区域内容{{end}}
//public/left.html
{{define "public/left.html"}}这里是left区域内容{{end}}
//public/content.html
{{define "public/content.html"}}
<html><head></head><body>
{{template "public/head.html"}}
{{template "public/left.html"}}
<div class="content">具体内容</div>
{{template "public/foot.html"}}
</body></html>
{{end}}
静态文件处理 为了便于动静态分离 扩展asset方法 用于制定静态资源路径
func init(){
...
restFuncMap["asset"]=asset
}
func asset(d string) string{
  cfg := GetCfg()
  return cfg.App["protocal"]+"://" + cfg.App["asset"]
}摸板中调用方式
<link rel="stylesheet" href="{{asset}}/assets/font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="{{asset}}/assets/ionicons/css/ionicons.min.css">
当资源文件迁移时 直接修改如下配置
rest.app.asset=localhost
session 日志 鉴权 验证码等
Session 集成了github.com/tommy351/gin-sessions
在main函数 使用如下方法开启session
store := sessions.NewCookieStore([]byte(cfg.Session["name"]))
router.Use(sessions.Middleware(cfg.Session["name"],store))
如果修改session配置 可以修改app配置文件
rest.session.name=GSESSIONID
rest.session.timelive=3600
验证码 图片验证码 rest/Captcha.go  验证码渲染方法loadVerify方法和验证码校验方法ChecjVerify
该方案基于session  自行集成图片验证码方案
缓存 缓存分为内部缓存和第三方缓存机制
简单业务 用内部map进行缓存
复杂系统 建议采用第三方缓存设备 典型redis memcache等
缓存选型的点在 分布式和连接池 及参数配置等因数
连接池 go-redis支持连接池
参数配置 在app配置文件中进行配置
通过LoadCfg方法获取配置参数
鉴权 鉴权需要用户根据自己的业务扩展 提供Demo 位于rest/Auth.go
日志 系统集成log4go日志系统 配置参数为filepath 指定配置文件路径
rest.logger.filepath=config/log4g.xml
配置文件初始化在main函数
restgo.Configuration(cfg.Logger["filepath"])
日志管理模块位于restgo/Logger.go 提供Debug方法和Error方法 根据需要扩充
可用gin提供的日志记录方法 log4go源于google的log工程 官方停止维护