├── .gitignore ├── README.md ├── curl.go ├── des.go ├── docs ├── README.md ├── UPDATE.md ├── images │ ├── adm模型.png │ ├── echo-logo.png │ ├── echo.png │ ├── phalgo-sample-hello.png │ ├── viper-logo.png │ └── 目录结构.png └── manual-zh-CN │ ├── [1.1]PhalGo-介绍.md │ ├── [1.2]PhalGo-初识PhalGO.md │ ├── [1.3]PhalGo-ADM思想.md │ ├── [1.4]PhalGo-Viper获取配置.md │ ├── [2.1]PhalGo-Echo.md │ ├── [2.2]PhalGo-Request.md │ ├── [2.3]PhalGo-参数验证过滤.md │ ├── [2.4]PhalGo-Respones.md │ ├── [2.5]PhalGo-异常处理.md │ ├── [2.6]PhalGo-日志处理.md │ ├── [3.1]PhalGo-Model概述.md │ ├── [4.1]PhalGo-Redis缓存.md │ ├── [4.2]PhalGo-Free缓存.md │ ├── [4.3]PhalGo-Tool工具.md │ ├── [4.4]PhalGo-Json.md │ ├── [4.5]PhalGo-curl.md │ ├── [4.7]PhalGo-pprof.md │ └── [4.8]PhalGo-签名加密.md ├── engine.go ├── errors └── errors.go ├── free.go ├── gorm.go ├── httplib ├── README.md ├── httplib.go └── httplib_test.go ├── json.go ├── logs.go ├── pprof.go ├── redis.go ├── request.go ├── response.go ├── tool.go ├── validation ├── README.md ├── util.go ├── util_test.go ├── validation.go ├── validation_test.go └── validators.go └── viper.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhalGo V 0.0.4 2 | 3 | > phalgo 已经更换为 https://github.com/sunmi-OS/gocore phalgo不在维护 4 | 5 | 6 | ## 前言 7 | 8 | PhalGo是一个Go语言开发的一体化开发框架,主要用于API开发,因为使用ECHO框架作为http服务,MVC模式一样可以使用,牛顿曾经说过"如果我比别人看得远,那是因为我站在巨人的肩膀上",既然Golang有那么多优秀的组件为什么还要重复造轮子呢?所以就有了一个把一些优秀组件整合起来降低开发成本的想法,整合了比较好的组件比如echo,gorm,viper等等,开源出来希望可以帮助到大家,也希望和大家一起交流! 9 | 10 | **注意:框架前期还不是很完善,请不要直接使用到生产环境!** 11 | 12 | 13 | ## PhalGo的目的 14 | 15 | PhalGo不是新技术,也不是新的模式,而是继续将前人,大神和顶级大师写的非常优秀的组件进行整合进行分享,并且进行封装来更易于开发人员来进行使用,最终达到建立规范降低开发成本的目的,这是PhalGo被创造出来核心的目的。 16 | 17 | ##PhalGo名字的由来 18 | 19 | PhalGo是对PhalApi和PhalCon的致敬,吸取了一些好的思想,应为是使用golnag编写所以命名为PhalGo 20 | 21 | 22 | ## 安装 23 | 24 | 多谢各位同学的反馈PhalGo安装已经推荐使用**glide**进行安装 25 | 26 | glide工具的安装也很简单可以参考:https://github.com/Masterminds/glide 27 | 28 | 我们只需要在我们的项目目录建立**glide.yaml**文件加以下内容然后执行**glide install**便会自动开始安装,package: 后面更项目名称 29 | 30 | 31 | package: phalgo-sample 32 | import: 33 | - package: github.com/wenzhenxi/phalgo 34 | 35 | 36 | PhalGo的升级也很简单,只需要在项目目录执行: 37 | 38 | glide up 39 | 40 | 因为有部分组件依赖golang.org国内网络可能会有问题,可以直接clone官方示例项目把项目**phalgo-sample**中的vendor复制到你的项目目录: 41 | 42 | **phalgo-sample:**[https://github.com/wenzhenxi/phalgo-sample](https://github.com/wenzhenxi/phalgo-sample "https://github.com/wenzhenxi/phalgo-sample") 43 | 44 | ## Holle,world! 45 | 46 | 创建文件 server.go 47 | 48 | package main 49 | 50 | import ( 51 | "github.com/wenzhenxi/phalgo" 52 | "github.com/labstack/echo" 53 | ) 54 | 55 | func main() { 56 | 57 | //初始化ECHO路由 58 | phalgo.NewEcho() 59 | // Routes路由 60 | phalgo.Echo.GET("/", func(c echo.Context) error { 61 | Response := phalgo.NewResponse(c) 62 | return Response.RetSuccess("hello,world!") 63 | }) 64 | //开启服务 65 | phalgo.Start(":1333") 66 | } 67 | 68 | 运行: 69 | 70 | go run server.go 71 | 72 | 请求**localhost:1333**: 73 | 74 | ![](http://i.imgur.com/tHi9dT2.png) 75 | 76 | ## 依赖 77 | 78 | //配置文件读取 79 | github.com/spf13/viper 80 | 81 | //辅助使用,参数过滤,curl等(已经集成到框架) 82 | github.com/astaxie/beego 83 | 84 | //主要路由 85 | github.com/labstack/echo 86 | 87 | //主要数据操作 88 | github.com/jinzhu/gorm 89 | 90 | //log记录 91 | github.com/Sirupsen/logrus 92 | 93 | //进程级别缓存 94 | github.com/coocood/freecache 95 | 96 | //redis依赖 97 | github.com/garyburd/redigo 98 | 99 | //注意会使用到如下依赖(国内可能需要翻墙) 100 | golang.org/x/net/context 101 | golang.org/x/sys/unix 102 | golang.org/x/crypto/md4 103 | 104 | ## PhalGo-DOC 105 | 106 | **文档正在完善中,多谢大家的支持!** 107 | 108 | [[1.1]PhalGo-介绍](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B1.1%5DPhalGo-%E4%BB%8B%E7%BB%8D.md) 109 | 110 | [[1.2]PhalGo-初识PhalGO](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B1.2%5DPhalGo-%E5%88%9D%E8%AF%86PhalGO.md) 111 | 112 | [[1.3]PhalGo-ADM思想](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B1.3%5DPhalGo-ADM%E6%80%9D%E6%83%B3.md) 113 | 114 | [[1.4]PhalGo-Viper获取配置](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B1.4%5DPhalGo-Viper%E8%8E%B7%E5%8F%96%E9%85%8D%E7%BD%AE.md) 115 | 116 | [[2.1]PhalGo-Echo](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B2.1%5DPhalGo-Echo.md) 117 | 118 | [[2.2]PhalGo-Request](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/[2.2]PhalGo-Request.md) 119 | 120 | [[2.3]PhalGo-参数验证过滤](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B2.3%5DPhalGo-%E5%8F%82%E6%95%B0%E9%AA%8C%E8%AF%81%E8%BF%87%E6%BB%A4.md) 121 | 122 | [[2.4]PhalGo-Respones](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B2.4%5DPhalGo-Respones.md) 123 | 124 | [[2.5]PhalGo-异常处理](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B2.5%5DPhalGo-%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86.md) 125 | 126 | [[2.6]PhalGo-日志处理](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B2.4%5DPhalGo-%E6%97%A5%E5%BF%97%E5%A4%84%E7%90%86.md) 127 | 128 | [[3.1]PhalGo-Model概述](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B3.1%5DPhalGo-Model%E6%A6%82%E8%BF%B0.md) 129 | 130 | [[4.1]PhalGo-Redis使用](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.1%5DPhalGo-Redis%E4%BD%BF%E7%94%A8.md?) 131 | 132 | [[4.2]PhalGo-Free缓存](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.2%5DPhalGo-Free%E7%BC%93%E5%AD%98.md) 133 | 134 | [[4.3]PhalGo-Tool工具](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.3%5DPhalGo-Tool%E5%B7%A5%E5%85%B7.md) 135 | 136 | [[4.4]PhalGo-Json](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.4%5DPhalGo-Json.md) 137 | 138 | [[4.5]PhalGo-curl](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.5%5DPhalGo-curl.md ) 139 | 140 | [[4.7]PhalGo-pprof](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.7%5DPhalGo-pprof.md) 141 | 142 | [[4.8]PhalGo-签名和加密.md](http://git.oschina.net/wenzhenxi/phalgo/blob/master/docs/manual-zh-CN/%5B4.7%5DPhalGo-%E7%AD%BE%E5%90%8D%E5%92%8C%E5%8A%A0%E5%AF%86.md) 143 | 144 | 145 | ## 联系方式 146 | 147 | 个人主页:w-blog.cn 148 | 149 | 喵了个咪邮箱:wenzhenxi@vip.qq.com 150 | 151 | 官方QQ群:149043947 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /curl.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Curl 2 | // 调用HTTP请求,依赖beego-curl 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/astaxie/beego" 6 | 7 | package phalgo 8 | 9 | import ( 10 | //"github.com/astaxie/beego/httplib" 11 | "github.com/wenzhenxi/phalgo/httplib" 12 | ) 13 | type Curl struct { 14 | } 15 | 16 | // Get请求 17 | func (this *Curl)CurlGet(url string) (string, error) { 18 | 19 | curl := httplib.Get(url) 20 | str, err := curl.String() 21 | if err != nil { 22 | return "", err 23 | } 24 | return str, nil 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /des.go: -------------------------------------------------------------------------------- 1 | // PhalGo-des 2 | // des加解密和3des加解密 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况:无依赖 5 | 6 | package phalgo 7 | 8 | 9 | import ( 10 | "bytes" 11 | "crypto/cipher" 12 | "crypto/des" 13 | ) 14 | 15 | type Des struct { 16 | 17 | } 18 | 19 | 20 | func (this *Des)DesEncrypt(origData []byte, key string, iv string) ([]byte, error) { 21 | block, err := des.NewCipher([]byte(key)) 22 | if err != nil { 23 | return nil, err 24 | } 25 | origData = this.PKCS5Padding(origData, block.BlockSize()) 26 | // origData = ZeroPadding(origData, block.BlockSize()) 27 | blockMode := cipher.NewCBCEncrypter(block, []byte(iv)) 28 | crypted := make([]byte, len(origData)) 29 | // 根据CryptBlocks方法的说明,如下方式初始化crypted也可以 30 | // crypted := origData 31 | blockMode.CryptBlocks(crypted, origData) 32 | return crypted, nil 33 | } 34 | 35 | func (this *Des)DesDecrypt(crypted []byte, key string, iv string) ([]byte, error) { 36 | block, err := des.NewCipher([]byte(key)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | blockMode := cipher.NewCBCDecrypter(block, []byte(iv)) 41 | origData := make([]byte, len(crypted)) 42 | // origData := crypted 43 | blockMode.CryptBlocks(origData, crypted) 44 | origData = this.PKCS5UnPadding(origData) 45 | // origData = ZeroUnPadding(origData) 46 | return origData, nil 47 | } 48 | 49 | /* 50 | DES/ECB/PKCS5Padding 加密 51 | */ 52 | func (this *Des)DesEncryptECB(origData []byte, key string ) ([]byte, error) { 53 | block, err := des.NewCipher([]byte(key)) 54 | if err != nil { 55 | return nil, err 56 | } 57 | bs := block.BlockSize() 58 | origData = this.PKCS5Padding(origData, bs) 59 | if len(origData)%bs != 0 { 60 | return nil,err 61 | } 62 | crypted := make([]byte, len(origData)) 63 | dst := crypted 64 | for len(origData) > 0 { 65 | block.Encrypt(dst, origData[:bs]) 66 | origData = origData[bs:] 67 | dst = dst[bs:] 68 | } 69 | 70 | return crypted, nil 71 | } 72 | /* 73 | DES/ECB/PKCS5Padding 解密 74 | */ 75 | func (this *Des)DesDecryptECB(crypted []byte, key string ) ([]byte, error) { 76 | block, err := des.NewCipher([]byte(key)) 77 | if err != nil { 78 | return nil, err 79 | } 80 | bs := block.BlockSize() 81 | if len(crypted)%bs != 0 { 82 | return nil, err 83 | } 84 | origData := make([]byte, len(crypted)) 85 | dst := origData 86 | for len(crypted) > 0 { 87 | block.Decrypt(dst, crypted[:bs]) 88 | crypted = crypted[bs:] 89 | dst = dst[bs:] 90 | } 91 | origData = this.PKCS5UnPadding(origData) 92 | return origData, nil 93 | } 94 | 95 | func (this *Des)ZeroPadding(ciphertext []byte, blockSize int) []byte { 96 | padding := blockSize - len(ciphertext) % blockSize 97 | padtext := bytes.Repeat([]byte{0}, padding) 98 | return append(ciphertext, padtext...) 99 | } 100 | 101 | func (this *Des)ZeroUnPadding(origData []byte) []byte { 102 | return bytes.TrimRightFunc(origData, func(r rune) bool { 103 | return r == rune(0) 104 | }) 105 | } 106 | 107 | // 3DES加密 108 | func (this *Des)TripleDesEncrypt(origData []byte, key string, iv string) ([]byte, error) { 109 | block, err := des.NewTripleDESCipher([]byte(key)) 110 | if err != nil { 111 | return nil, err 112 | } 113 | origData = this.PKCS5Padding(origData, block.BlockSize()) 114 | // origData = ZeroPadding(origData, block.BlockSize()) 115 | //blockMode := cipher.NewCBCEncrypter(block, key[:8]) 116 | blockMode := cipher.NewCBCEncrypter(block, []byte(iv)) 117 | crypted := make([]byte, len(origData)) 118 | blockMode.CryptBlocks(crypted, origData) 119 | return crypted, nil 120 | } 121 | 122 | // 3DES解密 123 | func (this *Des)TripleDesDecrypt(crypted []byte, key string, iv string) ([]byte, error) { 124 | block, err := des.NewTripleDESCipher([]byte(key)) 125 | if err != nil { 126 | return nil, err 127 | } 128 | //blockMode := cipher.NewCBCDecrypter(block, key[:8]) 129 | blockMode := cipher.NewCBCDecrypter(block, []byte(iv)) 130 | origData := make([]byte, len(crypted)) 131 | // origData := crypted 132 | blockMode.CryptBlocks(origData, crypted) 133 | origData = this.PKCS5UnPadding(origData) 134 | // origData = ZeroUnPadding(origData) 135 | return origData, nil 136 | } 137 | 138 | func (this *Des)PKCS5Padding(ciphertext []byte, blockSize int) []byte { 139 | padding := blockSize - len(ciphertext) % blockSize 140 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 141 | return append(ciphertext, padtext...) 142 | } 143 | 144 | func (this *Des)PKCS5UnPadding(origData []byte) []byte { 145 | length := len(origData) 146 | // 去掉最后一个字节 unpadding 次 147 | unpadding := int(origData[length - 1]) 148 | return origData[:(length - unpadding)] 149 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Phalgo手册 2 | 3 | 中文手册:manual-zh-CN 4 | 5 | # 联系方式 6 | 7 | 个人主页:w-blog.cn 8 | 9 | 喵了个咪邮箱:wenzhenxi@vip.qq.com 10 | 11 | 官方QQ群:149043947 12 | 13 | 笔者能力有限有说的不对的地方希望大家能够指出,也希望多多交流! 14 | 15 | -------------------------------------------------------------------------------- /docs/UPDATE.md: -------------------------------------------------------------------------------- 1 | #更新日志 2 | 3 | ##2016/6/14 4 | 5 | 1. 完善验证规则 6 | 7 | 2. 优化Request,Response初始化方式 8 | 9 | 3. 优化json参数和getpost参数获取规则 10 | 11 | 4. 对Beego的validation和httplib依赖进行整合 12 | 13 | 5. 版本提升v0.0.3 14 | 15 | ##2016/5/27 16 | 17 | 1. 修改项目依赖使用glide来管理依赖 18 | 19 | 2. 优化代码质量修复细节BUG 20 | 21 | 3. 版本提升v0.0.2 22 | 23 | ##2016/5/11 24 | 25 | 1. 项目建立 -------------------------------------------------------------------------------- /docs/images/adm模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/adm模型.png -------------------------------------------------------------------------------- /docs/images/echo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/echo-logo.png -------------------------------------------------------------------------------- /docs/images/echo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/echo.png -------------------------------------------------------------------------------- /docs/images/phalgo-sample-hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/phalgo-sample-hello.png -------------------------------------------------------------------------------- /docs/images/viper-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/viper-logo.png -------------------------------------------------------------------------------- /docs/images/目录结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenzhenxi/phalgo/a31aaa1f0918c2d4a1de02b3320bc627983e589a/docs/images/目录结构.png -------------------------------------------------------------------------------- /docs/manual-zh-CN/[1.1]PhalGo-介绍.md: -------------------------------------------------------------------------------- 1 | # PhalGo-介绍 2 | 3 | phalgo是一个Go语言的一体化开发框架,主要用于API开发应为使用ECHO框架作为http服务web程序一样可以使用,牛顿曾经说过"如果我比别人看得远,那是因为我站在巨人的肩膀上",既然Golang有那么多优秀的组件为什么还要重复造轮子呢?所以就有了一个把一些优秀组件整合起来降低开发成本的想法,整合了比较好的组件比如echo,gorm,viper等等 4 | 5 | ## PhalGo的目的 6 | 7 | PhalGo不是新技术,也不是新的模式,而是继续将前人,大神和顶级大师写的非常优秀的组件进行整合进行分享,并且进行封装来更易于开发人员来进行使用最终达到建立规范降低开发成本的目的,这是PhalGo被创造出来核心的目的。 8 | 9 | ## PhalGo名字的由来 10 | 11 | phalgo是对phalapi和phalcon的致敬,吸取了一些好的思想,应为是使用golnag编写所以命名为phalgo 12 | 13 | ## 安装 14 | 15 | 多谢各位同学的反馈PhalGo安装已经推荐使用**glide**进行安装 16 | 17 | glide工具的安装也很简单可以参考:https://github.com/Masterminds/glide 18 | 19 | 我们只需要在我们的项目目录建立**glide.yaml**文件加以下内容然后执行**glide install**便会自动开始安装 20 | 21 | package: phalgo-sample //你的项目名称 22 | import: 23 | - package: github.com/wenzhenxi/phalgo 24 | 25 | 26 | PhalGo的升级也很简单,只需要在项目目录执行: 27 | 28 | glide up 29 | 30 | 因为有部分组件依赖golang.org国内网络可能会有问题,可以直接clone官方示例项目把项目**phalgo-sample**中的vendor复制到你的项目目录: 31 | 32 | **phalgo-sample:**[https://github.com/wenzhenxi/phalgo-sample](https://github.com/wenzhenxi/phalgo-sample "https://github.com/wenzhenxi/phalgo-sample") 33 | 34 | ## Holle,world! 35 | 36 | 创建文件 server.go 37 | 38 | package main 39 | 40 | import ( 41 | "github.com/wenzhenxi/phalgo" 42 | "github.com/labstack/echo" 43 | ) 44 | 45 | func main() { 46 | 47 | //初始化ECHO路由 48 | phalgo.NewEcho() 49 | // Routes路由 50 | phalgo.Echo.Get("/", func(c echo.Context) error { 51 | Response := phalgo.Response{Context:c} 52 | return Response.RetSuccess("hello,world!") 53 | }) 54 | //开启服务 55 | phalgo.RunFasthttp(":1333") 56 | } 57 | 58 | 运行: 59 | 60 | go run server.go 61 | 62 | 请求**localhost:1333**: 63 | 64 | ![](http://i.imgur.com/tHi9dT2.png) 65 | 66 | ## 依赖说明 67 | 68 | //配置文件读取 69 | go get github.com/spf13/viper 70 | 71 | //辅助使用,参数过滤,curl等 72 | go get github.com/astaxie/beego 73 | 74 | //主要路由 75 | go get github.com/labstack/echo 76 | 77 | //主要数据操作 78 | go get github.com/jinzhu/gorm 79 | 80 | //log记录 81 | go get github.com/Sirupsen/logrus 82 | 83 | //进程级别缓存 84 | go get github.com/coocood/freecache 85 | 86 | //高速http 87 | go get github.com/valyala/fasthttp 88 | 89 | //redis依赖 90 | go get github.com/garyburd/redigo 91 | 92 | //注意会使用到如下依赖(国内可能需要翻墙) 93 | golang.org/x/net/context 94 | golang.org/x/sys/unix 95 | golang.org/x/crypto/md4 96 | 97 | ## 联系方式 98 | 99 | 如果愿意一起交流一起帮忙晚上框架可以通过一下几种方式联系我 100 | 101 | 个人主页:w-blog.cn 102 | 103 | 喵了个咪邮箱:wenzhenxi@vip.qq.com 104 | 105 | 官方QQ群:149043947 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[1.2]PhalGo-初识PhalGO.md: -------------------------------------------------------------------------------- 1 | # PhalGo-初识PhalGO 2 | 3 | PhalGo是一个API开发框架,因为使用了Echo框架同样也能用于MVC的开发,MVC具体可以参考Echo官方文档,对于PhalGo来说可以使用推荐的结构来设计的API接口,也可以按照自己的需求来处理,这都是灵活的,如何使用取决于开发者 4 | 5 | ## 目录结构 6 | 7 | │ 8 | ├── Api //API业务层入口,所有API业务存放于此 9 | │ └── user //通过名称划分多个项目 10 | │ ├── Api //API入口层负责请求参数接受业务拼接以及返回结果 11 | │ ├── Domain //Domain领域层负责对业务拆分然后由API层拼接 12 | │ └── Model //Model层负责数据交互 13 | │ 14 | ├── Config //配置文件用户存放配置文件 15 | │ └── conf.toml 16 | │ 17 | ├── Data //用户存放sql文件,数据结构跟着项目走 18 | │ └── user.sql 19 | │ 20 | ├── General //公共工具全项目通用的自定义工具 21 | │ └── tool.go 22 | │ 23 | ├── Runtime //日志目录存放运行时生成的目录 24 | │ └── 20160525.log 25 | │ 26 | ├── Routes //路由文件存放目录 27 | │ └── routes.go 28 | │ 29 | └── main.go //入口文件 30 | 31 | 32 | 如上面所说的PhalGo目录结构是灵活的,你可以修改目录结构只要满足使用PhalGo的几个条件 33 | 34 | ## phalgo-sample 35 | 36 | **phalgo-sample**是一个官方提供的一个API例子项目,它配备了一套标准的目录结构,以及整体组件的使用,可以下载进行参考 37 | 38 | 39 | **附上phalgo-sample地址:**[https://github.com/wenzhenxi/phalgo-sample](https://github.com/wenzhenxi/phalgo-sample "https://github.com/wenzhenxi/phalgo-sample") 40 | 41 | 42 | cd $GOPATH/src 43 | git clone https://github.com/wenzhenxi/phalgo-sample.git 44 | cd phalgo-sample 45 | go build main.go 46 | ./main 47 | 48 | 然后就可以请求localhost:1234/hello就会打印出如下结果: 49 | 50 | ![](http://i.imgur.com/lOosI1E.png) 51 | -------------------------------------------------------------------------------- /docs/manual-zh-CN/[1.3]PhalGo-ADM思想.md: -------------------------------------------------------------------------------- 1 | # PhalGo-ADM思想 2 | 3 | 关于ADM思想主要是指在API开发中使用API,Domain和Model三层结构,PhalGo从PhalApi中学习并且推崇这种设计模式,这种模式的好处在于分工明确,业务复用,数据复用可以减少复杂业务重复的代码量,**很多框架关心性能,而不关心人文;很多项目关心技术,而不关注业务。**ADM设计就是从业务的角度出发建立的开发规范. 4 | 5 | ## ADM分工协作 6 | 7 | ### Api 8 | 9 | Api层可以理解为是请求开始结束以及组合业务的地方,主要负责以下几件事情: 10 | 11 | 1. 获取请求参数并且验证请求参数的有效性 12 | 2. 对Domain领域层的实现进行拼接来组成整个接口的业务 13 | 3. 对于返回结果进行处理 14 | 15 | 我们可以看一个获取用户详情接口的例子: 16 | 17 | func (this *User_Api)GetUserInfo() echo.HandlerFunc { 18 | 19 | return func(c echo.Context) error { 20 | Request := phalgo.Requser{Context:c} 21 | Response := phalgo.Response{Context:c} 22 | defer Request.ErrorLogRecover() 23 | 24 | //获取请求参数 25 | id := Request.GetParam("id").GetInt() 26 | //拼接领域层业务 27 | user, err := this.Domain.User.GetUserInfo(id) 28 | if err != nil { 29 | return Response.RetError(err, 400) 30 | } 31 | //返回结果 32 | return Response.RetSuccess(user) 33 | } 34 | } 35 | 36 | (1)Api接口层应该做: 37 | 38 | - 应该:对用户登录态进行必要的检测 39 | - 应该:控制业务场景的主流程,创建领域业务实例,并进行调用 40 | - 应该:进行必要的日志纪录 41 | - 应该:返回接口结果 42 | 43 | (2)Api接口层不应该做: 44 | 45 | - 不应该:进行业务规则的处理或者计算 46 | - 不应该:关心数据是否使用缓存,或进行缓存相关的直接操作 47 | - 不应该:直接操作数据库 48 | - 不应该:将多个接口合并在一起 49 | 50 | ### Domain 51 | 52 | Domain可以称之为领域层,是ADM(Api-Domain-Model)分层中的桥梁,主要负责处理业务规则。Domain层存在的目录是为了把复杂业务拆分成一个一个小模块然后组织起来实现复杂的业务,从而达到代码复用,拆分复杂业务的作用. 53 | 54 | #### 举个栗子 55 | 56 | **场景:**我们在传统MVC开发的时候,使用控制器来处理业务,我们可能会写很多的重复代码来验证用户提交的信息是否ok,比如完善信息的时候验证邮箱,在修改用户信息的时候也要验证修改的邮箱,在管理的时候去编辑一个用户的时候也可能在去验证邮箱,这个时候我们的验证逻辑代码已经重复写在了3个地方,如果有一个需求加入了电话号码,那么你需要在3个地方加上对电话号码的验证逻辑 57 | 58 | 场景一并不难以遇到,而且在复杂的业务情况下重复使用的业务逻辑会更多,如果我们使用了Domain来处理用户修改前的验证那么我们只需要修改这个验证逻辑加上对电话号码的验证,那么所有需要验证用户信息的地方就会全部生效,而不用去做重复的工作并且避免遗漏的风险 59 | 60 | 下面是我们获取User详情的Domain层的实现,它不仅仅只是可以用在获取用户详情也可以用来验证用户ID是否有效,增加代码复用性减少修改代价 61 | 62 | func (this *Domain_User)GetUserInfo(id int)(Model.User,error) { 63 | 64 | user, err := this.Model.User.GetInfoById(id) 65 | if err != nil { 66 | return user, errors.New("UserInfo There is no") 67 | } 68 | return user,nil 69 | } 70 | 71 | **一个函数如果超过了一屏那将会影响到阅读者的理解,使用领域层拆分笨重的业务可以很好的解决这个问题** 72 | 73 | ### Model 74 | 75 | Model层想必不用说了,就是和数据库通讯获取数据,Model层是单纯的不带任何业务只是简单的获取数据库数据,我们看一段Model层代码: 76 | 77 | 78 | type User struct { 79 | Id int `gorm:"column:aId"` 80 | Name string `gorm:"column:name"` 81 | Passwd string `gorm:"column:passwd"` 82 | Email string `gorm:"column:email"` 83 | } 84 | 85 | func (User) TableName() string { 86 | return "user" 87 | } 88 | 89 | func (this *User)GetInfoById(id int) (User, error) { 90 | 91 | User := User{} 92 | err := phalgo.GetORM().Where("id = ?", id).First(&User).Error 93 | return User, err 94 | } 95 | 96 | **注意:Model层只应该获取数据然后结束关于没有获取到数据,获取出错等情况抛出到Domain层进行处理,这样可以增加灵活性,比如通过用户名是否重复和通过用户名称获取ID,一个返回存在才算异常,一个返回不存在才算异常,具体更具业务不同体现也不同,所以Model层处理并不合适** 97 | 98 | ## 层级调用的顺序 99 | 100 | 整体上讲根据从Api接口层、Domain领域层再到Model数据源层的顺序进行开发。 101 | 102 | 在开发过程中,需要注意不能**越层调用**也不能**逆向调用**,即不能Api调用Model。而应该是**上层调用下层,或者同层级调用**,也就是说,我们应该: 103 | 104 | - Api层调用Domain层 105 | - Domain层调用Domain层 106 | - Domain层调用Model层 107 | - Model层调用Model层 108 | 109 | ![](http://i.imgur.com/B2maSj7.png) 110 | 111 | 为了更明确调用的关系,以下调用是 错误 的: 112 | 113 | - 错误的做法1:Api层直接调用Model层 114 | - 错误的做法2: Domain层调用Api层,也不应用将Api层对象传递给Domain层 115 | - 错误的做法3: Model层调用Domain层 116 | 117 | 这样的约定,便于我们形成统一的开发规范,降低学习维护成本。 118 | 119 | 比如需要添加缓存,我们知道应该定位到Model层数据源进行扩展;若发现业务规则处理不当,则应该进入Domain层探其究竟;如果需要对接口的参数进行调整,即使是新手也知道应该找到对应的Api文件进行改动。 120 | -------------------------------------------------------------------------------- /docs/manual-zh-CN/[1.4]PhalGo-Viper获取配置.md: -------------------------------------------------------------------------------- 1 | # PhalGo-Viper获取配置 2 | 3 | ![](http://i.imgur.com/8ahAJXa.png) 4 | 5 | **viper项目地址:**[https://github.com/spf13/viper](https://github.com/spf13/viper "https://github.com/spf13/viper") 6 | 7 | ## 什么是viper 8 | 9 | viper是国外大神**spf13**编写的开源配置解决方案,viper拥有一下功能以及特性如下: 10 | 11 | - 设置默认值 12 | - 从JSON,toml YAML,HCl,和java属性配置文件 13 | - 从环境变量env读取值 14 | - 读缓冲区 15 | - 远程读取配置文件 16 | - key不区分大小写 17 | 18 | ## 为什么用viper 19 | 20 | viper不用担心你的文件格式,可以获取环境变量,也可以从远端获取配置文件,并且还有缓冲机制,功能非常棒非常**牛逼**,能满足不同的对配置文件的使用的要求,所以PhalGo采用viper来解决配置问题 21 | 22 | 23 | ## 初始化Config 24 | 25 | PhalGo追求最简单的使用各个组件所以viper我们只需要初始化就可以开始使用,我们只需要调用**NewConfig**函数需要你传入两个参数,一个是你文件相对于项目目录的相对路径,比如我在项目目录下建立的/conf文件目录就需要填写conf,第二个就是配置文件文件名,只需要名称即可,可以不用输入后缀viper会自动识别. 26 | 27 | //初始化配置文件 28 | phalgo.NewConfig("conf", "sys") 29 | 30 | 比如我们创建了一个**sys.toml**文件内容如下: 31 | 32 | 强烈推荐使用toml格式,toml格式介绍:[http://mlworks.cn/posts/introduction-to-toml/](http://mlworks.cn/posts/introduction-to-toml/ "http://mlworks.cn/posts/introduction-to-toml/") 33 | 34 | [system] 35 | port = ":1234" 36 | 37 | [dbDefault] 38 | dbHost = "localhost" #数据库连接地址 39 | dbName = "phalgo" #数据库名称 40 | dbUser = "root" #数据库用户名 41 | dbPasswd = "" #数据库密码 42 | dbPort = "3306" #数据库端口号 43 | dbOpenconns_max = 20 #最大连接数 44 | dbIdleconns_max = 0 #最大空闲连接 45 | dbType = "mysql" #数据库类型 46 | 47 | 48 | 我们就可以简单的使用 49 | 50 | phalgo.Config.GetString("system.port") #返回一个string类型的":1234" 51 | 52 | phalgo.Config和**"github.com/spf13/viper"**是等价的,所以可以通过phalgo.Config来调用viper提供的方法 53 | 54 | ## Config详解 55 | 56 | ### 设置默认值 57 | 58 | phalgo.Config.SetDefault("ContentDir", "content") 59 | phalgo.Config.SetDefault("LayoutDir", "layouts") 60 | phalgo.Config.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) 61 | 62 | ### 获取不同类型配置 63 | 64 | - phalgo.Config.Get(key string) : interface{} 65 | - phalgo.Config.GetBool(key string) : bool 66 | - phalgo.Config.GetFloat64(key string) : float64 67 | - phalgo.Config.GetInt(key string) : int 68 | - phalgo.Config.GetString(key string) : string 69 | - phalgo.Config.GetStringMap(key string) : map[string]interface{} 70 | - phalgo.Config.GetStringMapString(key string) : map[string]string 71 | - phalgo.Config.GetStringSlice(key string) : []string 72 | - phalgo.Config.GetTime(key string) : time.Time 73 | - phalgo.Config.GetDuration(key string) : time.Duration 74 | - phalgo.Config.IsSet(key string) : bool 75 | 76 | ### 获取多级参数 77 | 78 | viper支持获取配置的层级关系,在每个key之间通过"."分割机制,比如上面演示的 79 | 80 | phalgo.Config.GetString("system.port") 81 | 82 | 就是获取的二级参数 83 | 84 | ## 更多 85 | 86 | viper提供很多有意思的功能,可以通过开篇viper项目地址进行了解 87 | 88 | -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.1]PhalGo-Echo.md: -------------------------------------------------------------------------------- 1 | # PhalGo-Echo路由 2 | 3 | ![](http://i.imgur.com/8HePFuX.png) 4 | 5 | Echo官网地址:[https://labstack.com/echo](https://labstack.com/echo "Echo官网地址") 6 | 7 | Echo是PhalGo最核心的组件,负责了整体的请求路由返回等功能,并且Echo支持HTTP2协议以及HTTPS协议 8 | 9 | 10 | 11 | ## 为什么选择Echo 12 | 13 | 在初期笔者考虑过Echo,gin以及beego来尝试实现自己的项目,最终还是选择了使用Echo来作为PhalGo的主要路由框架 14 | 15 | 让我决定的因素是应为Echo支持使用fasthttp所以在效率上面基本其他框架没法比(笔者是个性能狗),我们可以看一下Echo官方的性能图 16 | 17 | 机器配置: 18 | 19 | - Go 1.6 20 | - wrk 4.0.0 21 | - 2 GB, 2 Core 22 | 23 | ![](http://i.imgur.com/VV2MozE.png) 24 | 25 | **gin相对来说更人性化一些beego相对更简单易用一些感兴趣的可以尝试一下** 26 | 27 | ## 注册路由 28 | 29 | 在PhalGo中所有的组件需要使用都需要在入口进行注册 30 | 31 | //初始化ECHO路由 32 | phalgo.NewEcho() 33 | 34 | 然后就可以注册我们的路由了,建议在项目建立一个routes目录中存放路由go文件然后在入口文件中引入 35 | 36 | // Routes 载入路由 37 | routes.GetRoutes() 38 | 39 | Echo支持restful标准 40 | 41 | phalgo.Echo.Get() //接受Get请求 42 | phalgo.Echo.Post() //接受Post请求 43 | phalgo.Echo.Delete() //接受Delete请求 44 | phalgo.Echo.Put() //接受Put请求 45 | phalgo.Echo.Any() //接受所有请求方式 46 | 47 | 以上所有方式都需要接受两个参数,第一个是请求的路径比如填入**"/test/:id"**,就需要使用**localhost/test/5**这种方式请求,第二个参数必须是一个方法**func(Context) error**例子如下 48 | 49 | func hello(c echo.Context) error { 50 | return c.String(http.StatusOK, "Hello, World!") 51 | } 52 | 53 | //接收Get请求 54 | e.GET("/test/:id",hello) 55 | 56 | ## 开启服务 57 | 58 | 开启服务有两种方式一种是默认的Standard方式一种是Fasthttp,看过上面的对比图也应该之道Fasthttp有多强了把,我们可以在入口文件最后加入如下语句开启服务 59 | 60 | //使用Fasthttp方式 61 | phalgo.RunFasthttp(":1333") 62 | //使用Standard方式 63 | phalgo.RunStandard(":1333") 64 | 65 | 这样就可以运行一个http服务了 66 | 67 | ## 中间件middleware 68 | 69 | Echo有很多好用的中间件,笔者在这里进行了封装,这里简单提及几个以及他们的功能,使用方式只需要在开启服务之前初始化即可 70 | 71 | **1.Recover** 72 | 73 | 使用Recover会打印出打印请求异常信息 74 | 75 | phalgo.Recover() 76 | 77 | 比如访问一个未注册路由的地址,页面上会打印Not Found,终端上会打印如下语句: 78 | 79 | {"time":"2016-05-24T17:02:12+08:00","level":"ERROR","prefix":"echo","file":"echo.go","line":"226","message":"Not Found"} 80 | 81 | **2.Logger** 82 | 83 | 使用Logger会打印出所有的请求明细,请求IP请求方式,请求花费时间请求地址,请求httpcode等等,方便调试 84 | 85 | 请求成功: 86 | 87 | {"time":"2016-05-24T17:02:12+08:00","remote_ip":"101.81.5.247","method":"GET","uri":"/","status":200, "latency":194,"latency_human":"194.251µs","rx_bytes":0,"tx_bytes":76} 88 | 89 | 请求失败: 90 | 91 | {"time":"2016-05-24T17:02:10+08:00","remote_ip":"101.81.5.247","method":"GET","uri":"/s","status":404, "latency":79,"latency_human":"79.126µs","rx_bytes":0,"tx_bytes":9} 92 | 93 | **3.Gzip压缩** 94 | 95 | 在很多时候我们需要开启Gzip压缩来减少返回数据的大小来节约流量可以通过以下方式设置: 96 | 97 | phalgo.Gzip() 98 | 99 | **4.末尾斜杠处理** 100 | 101 | 在匹配路由的时候有一个问题比如我们定义了一个路由是**/test**我们通过**localhost/test/**是请求不到应为末尾多了一个斜杠,这个时候就可以通过中间件的末尾斜杠处理来添加末尾斜杠或者是删除末尾斜杠 102 | 103 | //自动添加末尾斜杠 104 | phalgo.AddTrailingSlash() 105 | //自动删除末尾斜杠 106 | phalgo.RemoveTrailingSlash() -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.2]PhalGo-Request.md: -------------------------------------------------------------------------------- 1 | # PhalGo-Request 2 | 3 | PhalGo-Request对Echo进行了封装,目的是为了减少**获取请求参数,转换类型,判断请求参数有效**此类重复操作带来的代码量,PhalGo-Request支持**Get,Post,Json**三类请求参数,并且使用连贯操作来获取API需要的参数减少重复代码量,. 4 | 5 | ## Request初始化 6 | 7 | PhalGo是灵活的你可以使用PhalGo-Request也可以使用Echo原生的参数获取(在使用restful风格的情况需要使用Echo原生获取方式),如果使用PhalGo-Request需要在接口内部进行初始化方可使用: 8 | 9 | return func(c echo.Context) error { 10 | Request := phalgo.NewRequest(c) 11 | } 12 | 13 | ## 获取Get和Post参数 14 | 15 | 参数两种传递方式想必不用多说大家都之道就是GET和POST方式,我们可以通过以下方式来获取GET或POST请求参数: 16 | 17 | //获取get请求参数,接受string类型 18 | id := Request.GetParam("id").GetString() 19 | //获取post参数,接受string类型 20 | id := Request.PostParam("id").GetString() 21 | 22 | 当然如果你不想区分get和post参数可以这样使用,优先级GET->POST 23 | 24 | id := Request.Param("id").GetString() 25 | 26 | 在获取请求的时候在最后一步需要指定你获取请求参数的类型,PhalGo-Request现支持三种类型的获取,**String,Int,Float**帮助你更加方便的处理参数类型不需要自己进行类型转换多余的操作,使用方法分别如下: 27 | 28 | id := Request.PostParam("id").GetString() 29 | id := Request.PostParam("id").GetInt() 30 | id := Request.PostParam("id").GetFloat() 31 | 32 | ## 处理Json参数 33 | 34 | 说道请求处理还有一个不得不说的请求类型那就是Json,我们在很多复杂的业务情况下接口需要接受一个列表?那当然会是一个json对象,比如说我们要对请求参数进行加密,我们通常会把所有的请求参数放到一个json中进行加密然后传递过来解密之后使用,所以json在请求参数处理中是必不可少的,PhalGo-Request也是意识到了这一点对json处理进行了封装,我们来看一下最简单的从请求参数中来获取json参数的一个小例子: 35 | 36 | //通过get请求获取params的json字符串 37 | params := Request.GetParam("params").GetString() 38 | //注入Json字符串 39 | Request.SetJson(params) 40 | //通过JsonParam和GetJsonString来获取想要的参数想要的类型 41 | id := Request.JsonParam("user.id").GetString() 42 | 43 | 我们在获取Json参数时需要使用JsonParam函数JsonParam接受一个字符串,字符串通过"."隔开代表了获取json的层级关系,为了更好的说明有入下一个例子: 44 | 45 | json := `{ 46 | "userlist": { 47 | "miaomi": { 48 | "username": "喵了个咪" 49 | } 50 | } 51 | }` 52 | Request.SetJson(json) 53 | //这样就能获取到字符串"喵了个咪" 54 | Request.JsonParam("userlist.miaom.usernamei").GetString() 55 | 56 | Json也同样支持**String,Int,Float**类型 ,除此之外Json类型还多一个类型那就是json类型**GetJson**方法,GetJson会返回一个JS实例,我们可以通过JS实例来获取更多的类型比如切片,Map等类型. 57 | 58 | **关于JS实例更多处理可以查看后续的PhalGo-Json或者是查看源代码** 59 | 60 | ## 小结 61 | 62 | 有的童鞋会问如果我获取的是Int类型但是参数传递的是中文字符要怎么处理呢,或者说是这个参数必须传递需要自己验证吗,这些问题都留到下一节的**参数验证过滤**来给大家解答. 63 | 64 | **如果大家对PhalGo有好的想法或者是有好的意见随时可以联系我!** 65 | -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.3]PhalGo-参数验证过滤.md: -------------------------------------------------------------------------------- 1 | #PhalGo-参数验证过滤 2 | 3 | phalGo 的参数过滤使用 beego 提供的独立模块 **validation** 进行封装改造 , 从而达到可以和 request 一同使用的效果 , 通过统一的报错机制对不匹配的参数进行处理. 4 | 5 | ##连贯操作 6 | 7 | 当我们自己进行参数验证时,我们需要先获取参数,在判断参数是否存在,判断长度是否在制定范围内,在判断是否符合我们所需要的格式,尤其在Go语言需要花费大量的代码量来实现这一系列功能,但是在PhalGo中进行了良好的封装,对一个参数的基本处理都能在一行内完成,如下所示: 8 | 9 | name := Request.Param("name").Require(true).Max(30).SetDefault("喵咪").GetString() 10 | 11 | 一个连贯操作就对一个参数进行很好的描述,而不用和传统的写法一样消耗大量的代码量在参数获取验证上面 12 | 13 | ##使用方法 14 | 15 | PhalGo的参数验证是和Request紧密结合在一起的大致格式如下: 16 | 17 | Request.Param(参数名称).你需要的验证规则.Get参数类型 18 | 19 | 这样就可以获取一个被验证过的参数 20 | 21 | ##报错机制 22 | 23 | 当我们参数验证不通过是需要处理的,当我们对所有参数定义完成之后,我们只需要重写一下一个error处理语句方可对验证不通过的参数进行处理: 24 | 25 | //参数过滤error处理 26 | if err := Request.GetError(); err != nil { 27 | return Response.RetError(err, -1) 28 | } 29 | 30 | 注意:此处默认会返回首个验证失败的参数报错,当有多个验证失败也只返回首个 31 | 32 | ##支持参数验证类型 33 | 34 | PhalGo的参数验证支持大部分验证规则: 35 | 36 | - Require(bool) //是否必须 37 | - Max(int) //最大长度/大小 38 | - Min(int) //最小长度/大小 39 | - ZipCode() //邮政编码 40 | - Phone() //手机号或固定电话号 41 | - Tel() //固定电话号 42 | - Mobile() //手机号 43 | - Base64() //base64编码 44 | - IP() //IP格式,目前只支持IPv4格式验证 45 | - Email() //邮箱格式 46 | - Match(string) //正则匹配,其他类型都将被转成字符串再匹配 47 | - NoMatch(string) //反正则匹配,其他类型都将被转成字符串再匹配 48 | - Numeric() //数字 49 | - Alpha() //alpha字符 50 | - AlphaNumeric() //alpha字符或数字 51 | - AlphaDash() //alpha字符或数字或横杠-_ -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.4]PhalGo-Respones.md: -------------------------------------------------------------------------------- 1 | # PhalGo-Respones 2 | 3 | 在处理请求 Api 请求中 , 除了对 Request 之外最重要的就是 Response 了 , PhalGo 支持两种格式的参数返回一种是 Josn 一种是 XML , 在 PhalGo 中默认使用JSON进行返回 , 可以在入口文件进行全局返回参数配置或在摸个接口内部指定此接口返回的参数类型 4 | 5 | ## Response初始化 6 | 7 | Response 和 Request 一样需要在 Api 中进行初始化: 8 | 9 | Response := phalgo.NewResponse(c) 10 | 11 | ## 返回格式 12 | 13 | Response 默认返回格式如下: 14 | 15 | type RetParameter struct { 16 | Code int `json:"code";xml:"code"` 17 | Data interface{} `json:"data";xml:"data"` 18 | Msg string `json:"msg";xml:"msg"` 19 | } 20 | 21 | code 为请求的状态码 , data 是具体返回的数据 , msg 是返回内容的描述例如"成功" , "失败" 22 | 23 | ## 基础使用 24 | 25 | ### RetSuccess 26 | 27 | 我们最常用的就是成功返回 , RetSuccess接受一个接口类型的参数 , 此参数会填充到 data 中 , 例子如下: 28 | 29 | return Response.RetSuccess("hello " + name + " Welcome to PhalGo world") 30 | 31 | ![](http://i.imgur.com/RNj3Fc1.png) 32 | 33 | 如果需要返回自定的 msg 内容需要使用 SetMsg 方法 例子如下: 34 | 35 | Response.SetMsg("成功") 36 | 37 | ![](http://i.imgur.com/zlshvac.png) 38 | 39 | **注意 : 使用 RetSuccess 函数时 code 默认为 1 , msg 默认为""** 40 | 41 | ### RetError 42 | 43 | 当我们处理程序异常的时候 , 我们会建立一个异常返回 , RetError 需要接受两个参数一个是 error 会把报错信息填充到 msg 中 , 另外一个是 返回的code 例子如下: 44 | 45 | return Response.RetError(errors.New("No Name"), -1) 46 | 47 | ![](http://i.imgur.com/KI5Z1IN.png) 48 | 49 | 当然你如果要返回一些 Data 内容 , 也只需要使用 SetData : 50 | 51 | Response.SetData(map[string]string{"name" : "喵咪"}) 52 | 53 | ![](http://i.imgur.com/Fl22Yjc.png) 54 | 55 | ### RetCustomize 56 | 57 | PhalGo 当然也提供了灵活结果返回方法 RetCustomize 接受三个参数 , 分别是 code data msg 开发者可以按照自己的需求返回自己想要的内容 , 例子如下: 58 | 59 | return Response.RetCustomize(201, map[string]string{"name":"喵咪"}, "成功") 60 | 61 | ![](http://i.imgur.com/ouLezwV.png) 62 | 63 | ### Ret 64 | 65 | 当然大家也有自己要求的返回格式 PhalGo 并不约束你需要使用 code,data,msg 这类格式 , PhalGo 提供了 Ret 方法接受一个结构类型进行返回 , 开发者可以按照约定的格式自行返回想要的内容 , 例子如下: 66 | 67 | return Response.Ret(map[string]string{"name":"喵咪"}) 68 | 69 | ![](http://i.imgur.com/AS5gvs1.png) 70 | 71 | ### Write 72 | 73 | 最简单粗暴的就是直接打印内容给到用户 , Write 接受一个 []byte 类型 , Write 默认是无格式的 , 例子如下: 74 | 75 | Response.Write([]byte("Debug:test Write")) 76 | 77 | ![](http://i.imgur.com/Um6QaPB.png) 78 | 79 | ### SetStatus 80 | 81 | http 请求的一个重要内容就是 Status 码 , PhalGo 可以通过 SetStatus 方法来设置 , 例子如下: 82 | 83 | Response.SetStatus(404) 84 | 85 | 此时返回的 Status 码会是404 86 | 87 | ## XML 88 | 89 | PhalGo 在支持 Json 的情况下同时也支持 XML 返回格式 , 只需要通过 SetRetType 方法进行设置 , 例子如下: 90 | 91 | phalgo.SetRetType(phalgo.RETMXL) 92 | 93 | ![](http://i.imgur.com/Y5Aki23.png) 94 | 95 | 这里有一个注意的情况了 , 此设置在 API 中设置影响范围只是此 API , 如需影响全局需要在 Main 中进行设置 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.5]PhalGo-异常处理.md: -------------------------------------------------------------------------------- 1 | #PhalGo-异常处理 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[2.6]PhalGo-日志处理.md: -------------------------------------------------------------------------------- 1 | #PhalGo-日志处理 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[3.1]PhalGo-Model概述.md: -------------------------------------------------------------------------------- 1 | #PhalGo-Model概述 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.1]PhalGo-Redis缓存.md: -------------------------------------------------------------------------------- 1 | #PhalGo-Redis缓存 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.2]PhalGo-Free缓存.md: -------------------------------------------------------------------------------- 1 | #PhalGo-Free缓存 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.3]PhalGo-Tool工具.md: -------------------------------------------------------------------------------- 1 | #PhalGo-Tool工具 -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.4]PhalGo-Json.md: -------------------------------------------------------------------------------- 1 | #PhalGo-Json -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.5]PhalGo-curl.md: -------------------------------------------------------------------------------- 1 | #PhalGo-curl -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.7]PhalGo-pprof.md: -------------------------------------------------------------------------------- 1 | #PhalGo-pprof -------------------------------------------------------------------------------- /docs/manual-zh-CN/[4.8]PhalGo-签名加密.md: -------------------------------------------------------------------------------- 1 | #PhalGo-签名加密 -------------------------------------------------------------------------------- /engine.go: -------------------------------------------------------------------------------- 1 | // PhalGo-engine 2 | // 注意路由引擎,依赖Echo对器进行封装 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/labstack/echo" 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/labstack/echo/middleware" 11 | "github.com/labstack/echo" 12 | "net/http" 13 | ) 14 | 15 | const ( 16 | RETJSON = 1 17 | RETMXL = 2 18 | RETDES = 3 19 | ) 20 | 21 | var ( 22 | Echo *echo.Echo 23 | RetType int = 1 24 | ) 25 | // 初始化echo实例 26 | func NewEcho() *echo.Echo { 27 | 28 | Echo = echo.New() 29 | return Echo 30 | } 31 | 32 | // 设置Ret格式 33 | func SetRetType(i int) { 34 | 35 | RetType = i 36 | } 37 | 38 | 39 | // 开启服务 40 | func Start(prot string) { 41 | Echo.Logger.Fatal(Echo.Start(prot)) 42 | } 43 | 44 | // 打印请求异常信息 45 | func Recover() { 46 | 47 | Echo.Use(middleware.Recover()) 48 | } 49 | 50 | // 是否开启debug 51 | func SetDebug(on bool) { 52 | Echo.Debug = on 53 | } 54 | 55 | // 获取debug状态 56 | func Debug() bool { 57 | return Echo.Debug 58 | } 59 | 60 | // 打印请求信息 61 | func Logger() { 62 | 63 | Echo.Use(middleware.Logger()) 64 | } 65 | 66 | // 开启gzip压缩 67 | func Gzip() { 68 | 69 | Echo.Use(middleware.Gzip()) 70 | } 71 | 72 | // 设置Body大小 73 | func BodyLimit(str string) { 74 | 75 | Echo.Use(middleware.BodyLimit(str)) 76 | } 77 | 78 | // 自动添加末尾斜杠 79 | func AddTrailingSlash() { 80 | 81 | Echo.Use(middleware.AddTrailingSlashWithConfig(middleware.TrailingSlashConfig{ 82 | RedirectCode: http.StatusMovedPermanently, 83 | })) 84 | } 85 | 86 | // 自动删除末尾斜杠 87 | func RemoveTrailingSlash() { 88 | 89 | Echo.Use(middleware.RemoveTrailingSlashWithConfig(middleware.TrailingSlashConfig{ 90 | RedirectCode: http.StatusMovedPermanently, 91 | })) 92 | } 93 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The kingshard Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package errors 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | var ( 22 | ErrNoParams = errors.New("No params") 23 | ErrMD5 = errors.New("MD5 Check Error") 24 | 25 | ErrCustom = func(str string) error { 26 | return errors.New(str) 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /free.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Free 2 | // 进程级别缓存数据Free,使用gob转意存储 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/coocood/freecache" 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/coocood/freecache" 11 | "encoding/gob" 12 | "bytes" 13 | ) 14 | 15 | //缺点: 16 | // 1.当需要缓存的数据占用超过提前分配缓存的 1024/1 则不能缓存 17 | // 2.当分配内存过多则会导致内存占用高 最好不要超过100MB的内存分配 18 | 19 | var Free *freecache.Cache 20 | 21 | // 初始化Free进程缓存 22 | func NewFree() { 23 | cacheSize := 100 * 1024 * 1024 24 | Free = freecache.NewCache(cacheSize) 25 | } 26 | 27 | // Gob加密 28 | func GobEncode(data interface{}) ([]byte, error) { 29 | 30 | buf := bytes.NewBuffer(nil) 31 | enc := gob.NewEncoder(buf) 32 | err := enc.Encode(data) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return buf.Bytes(), nil 37 | } 38 | 39 | // Gob解密 40 | func GobDecode(data []byte, to interface{}) error { 41 | 42 | buf := bytes.NewBuffer(data) 43 | dec := gob.NewDecoder(buf) 44 | return dec.Decode(to) 45 | } -------------------------------------------------------------------------------- /gorm.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Grom 2 | // 数据库处理,使用Grom 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/jinzhu/gorm" 6 | 7 | package phalgo 8 | 9 | import ( 10 | "fmt" 11 | "time" 12 | "github.com/jinzhu/gorm" 13 | _ "github.com/jinzhu/gorm/dialects/mssql" 14 | _ "github.com/jinzhu/gorm/dialects/mysql" 15 | _ "github.com/jinzhu/gorm/dialects/postgres" 16 | _ "github.com/jinzhu/gorm/dialects/sqlite" 17 | ) 18 | 19 | var Gorm map[string]*gorm.DB 20 | 21 | func init() { 22 | Gorm = make(map[string]*gorm.DB) 23 | } 24 | 25 | // 初始化Gorm 26 | func NewDB(dbname string) { 27 | 28 | var orm *gorm.DB 29 | var err error 30 | 31 | //默认配置 32 | Config.SetDefault(dbname, map[string]interface{}{ 33 | "dbHost": "127.0.0.1", 34 | "dbName": "phalgo", 35 | "dbUser": "root", 36 | "dbPasswd": "", 37 | "dbPort": 3306, 38 | "dbIdleconns_max": 0, 39 | "dbOpenconns_max": 20, 40 | "dbType": "mysql", 41 | }) 42 | dbHost := Config.GetString(dbname + ".dbHost") 43 | dbName := Config.GetString(dbname + ".dbName") 44 | dbUser := Config.GetString(dbname + ".dbUser") 45 | dbPasswd := Config.GetString(dbname + ".dbPasswd") 46 | dbPort := Config.GetString(dbname + ".dbPort") 47 | dbType := Config.GetString(dbname + ".dbType") 48 | 49 | connectString := dbUser + ":" + dbPasswd + "@tcp(" + dbHost + ":" + dbPort + ")/" + dbName + "?charset=utf8" 50 | //开启sql调试模式 51 | //GDB.LogMode(true) 52 | 53 | for orm, err = gorm.Open(dbType, connectString); err != nil; { 54 | fmt.Println("数据库连接异常! 5秒重试") 55 | time.Sleep(5 * time.Second) 56 | orm, err = gorm.Open(dbType, connectString) 57 | } 58 | //连接池的空闲数大小 59 | orm.DB().SetMaxIdleConns(Config.GetInt(dbname + ".idleconns_max")) 60 | //最大打开连接数 61 | orm.DB().SetMaxIdleConns(Config.GetInt(dbname + ".openconns_max")) 62 | Gorm[dbname] = orm 63 | //defer Gorm[dbname].Close() 64 | } 65 | 66 | // 通过名称获取Gorm实例 67 | func GetORMByName(dbname string) *gorm.DB { 68 | 69 | return Gorm[dbname] 70 | } 71 | 72 | // 获取默认的Gorm实例 73 | func GetORM() *gorm.DB { 74 | 75 | return Gorm["dbDefault"] 76 | } 77 | -------------------------------------------------------------------------------- /httplib/README.md: -------------------------------------------------------------------------------- 1 | # httplib 2 | httplib is an libs help you to curl remote url. 3 | 4 | # How to use? 5 | 6 | ## GET 7 | you can use Get to crawl data. 8 | 9 | import "github.com/astaxie/beego/httplib" 10 | 11 | str, err := httplib.Get("http://beego.me/").String() 12 | if err != nil { 13 | // error 14 | } 15 | fmt.Println(str) 16 | 17 | ## POST 18 | POST data to remote url 19 | 20 | req := httplib.Post("http://beego.me/") 21 | req.Param("username","astaxie") 22 | req.Param("password","123456") 23 | str, err := req.String() 24 | if err != nil { 25 | // error 26 | } 27 | fmt.Println(str) 28 | 29 | ## Set timeout 30 | 31 | The default timeout is `60` seconds, function prototype: 32 | 33 | SetTimeout(connectTimeout, readWriteTimeout time.Duration) 34 | 35 | Exmaple: 36 | 37 | // GET 38 | httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) 39 | 40 | // POST 41 | httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second) 42 | 43 | 44 | ## Debug 45 | 46 | If you want to debug the request info, set the debug on 47 | 48 | httplib.Get("http://beego.me/").Debug(true) 49 | 50 | ## Set HTTP Basic Auth 51 | 52 | str, err := Get("http://beego.me/").SetBasicAuth("user", "passwd").String() 53 | if err != nil { 54 | // error 55 | } 56 | fmt.Println(str) 57 | 58 | ## Set HTTPS 59 | 60 | If request url is https, You can set the client support TSL: 61 | 62 | httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) 63 | 64 | More info about the `tls.Config` please visit http://golang.org/pkg/crypto/tls/#Config 65 | 66 | ## Set HTTP Version 67 | 68 | some servers need to specify the protocol version of HTTP 69 | 70 | httplib.Get("http://beego.me/").SetProtocolVersion("HTTP/1.1") 71 | 72 | ## Set Cookie 73 | 74 | some http request need setcookie. So set it like this: 75 | 76 | cookie := &http.Cookie{} 77 | cookie.Name = "username" 78 | cookie.Value = "astaxie" 79 | httplib.Get("http://beego.me/").SetCookie(cookie) 80 | 81 | ## Upload file 82 | 83 | httplib support mutil file upload, use `req.PostFile()` 84 | 85 | req := httplib.Post("http://beego.me/") 86 | req.Param("username","astaxie") 87 | req.PostFile("uploadfile1", "httplib.pdf") 88 | str, err := req.String() 89 | if err != nil { 90 | // error 91 | } 92 | fmt.Println(str) 93 | 94 | 95 | See godoc for further documentation and examples. 96 | 97 | * [godoc.org/github.com/astaxie/beego/httplib](https://godoc.org/github.com/astaxie/beego/httplib) 98 | -------------------------------------------------------------------------------- /httplib/httplib.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package httplib is used as http.Client 16 | // Usage: 17 | // 18 | // import "github.com/astaxie/beego/httplib" 19 | // 20 | // b := httplib.Post("http://beego.me/") 21 | // b.Param("username","astaxie") 22 | // b.Param("password","123456") 23 | // b.PostFile("uploadfile1", "httplib.pdf") 24 | // b.PostFile("uploadfile2", "httplib.txt") 25 | // str, err := b.String() 26 | // if err != nil { 27 | // t.Fatal(err) 28 | // } 29 | // fmt.Println(str) 30 | // 31 | // more docs http://beego.me/docs/module/httplib.md 32 | package httplib 33 | 34 | import ( 35 | "bytes" 36 | "compress/gzip" 37 | "crypto/tls" 38 | "encoding/json" 39 | "encoding/xml" 40 | "io" 41 | "io/ioutil" 42 | "log" 43 | "mime/multipart" 44 | "net" 45 | "net/http" 46 | "net/http/cookiejar" 47 | "net/http/httputil" 48 | "net/url" 49 | "os" 50 | "strings" 51 | "sync" 52 | "time" 53 | ) 54 | 55 | var defaultSetting = BeegoHTTPSettings{ 56 | UserAgent: "beegoServer", 57 | ConnectTimeout: 60 * time.Second, 58 | ReadWriteTimeout: 60 * time.Second, 59 | Gzip: true, 60 | DumpBody: true, 61 | } 62 | 63 | var defaultCookieJar http.CookieJar 64 | var settingMutex sync.Mutex 65 | 66 | // createDefaultCookie creates a global cookiejar to store cookies. 67 | func createDefaultCookie() { 68 | settingMutex.Lock() 69 | defer settingMutex.Unlock() 70 | defaultCookieJar, _ = cookiejar.New(nil) 71 | } 72 | 73 | // SetDefaultSetting Overwrite default settings 74 | func SetDefaultSetting(setting BeegoHTTPSettings) { 75 | settingMutex.Lock() 76 | defer settingMutex.Unlock() 77 | defaultSetting = setting 78 | } 79 | 80 | // NewBeegoRequest return *BeegoHttpRequest with specific method 81 | func NewBeegoRequest(rawurl, method string) *BeegoHTTPRequest { 82 | var resp http.Response 83 | u, err := url.Parse(rawurl) 84 | if err != nil { 85 | log.Println("Httplib:", err) 86 | } 87 | req := http.Request{ 88 | URL: u, 89 | Method: method, 90 | Header: make(http.Header), 91 | Proto: "HTTP/1.1", 92 | ProtoMajor: 1, 93 | ProtoMinor: 1, 94 | } 95 | return &BeegoHTTPRequest{ 96 | url: rawurl, 97 | req: &req, 98 | params: map[string][]string{}, 99 | files: map[string]string{}, 100 | setting: defaultSetting, 101 | resp: &resp, 102 | } 103 | } 104 | 105 | // Get returns *BeegoHttpRequest with GET method. 106 | func Get(url string) *BeegoHTTPRequest { 107 | return NewBeegoRequest(url, "GET") 108 | } 109 | 110 | // Post returns *BeegoHttpRequest with POST method. 111 | func Post(url string) *BeegoHTTPRequest { 112 | return NewBeegoRequest(url, "POST") 113 | } 114 | 115 | // Put returns *BeegoHttpRequest with PUT method. 116 | func Put(url string) *BeegoHTTPRequest { 117 | return NewBeegoRequest(url, "PUT") 118 | } 119 | 120 | // Delete returns *BeegoHttpRequest DELETE method. 121 | func Delete(url string) *BeegoHTTPRequest { 122 | return NewBeegoRequest(url, "DELETE") 123 | } 124 | 125 | // Head returns *BeegoHttpRequest with HEAD method. 126 | func Head(url string) *BeegoHTTPRequest { 127 | return NewBeegoRequest(url, "HEAD") 128 | } 129 | 130 | // BeegoHTTPSettings is the http.Client setting 131 | type BeegoHTTPSettings struct { 132 | ShowDebug bool 133 | UserAgent string 134 | ConnectTimeout time.Duration 135 | ReadWriteTimeout time.Duration 136 | TLSClientConfig *tls.Config 137 | Proxy func(*http.Request) (*url.URL, error) 138 | Transport http.RoundTripper 139 | EnableCookie bool 140 | Gzip bool 141 | DumpBody bool 142 | } 143 | 144 | // BeegoHTTPRequest provides more useful methods for requesting one url than http.Request. 145 | type BeegoHTTPRequest struct { 146 | url string 147 | req *http.Request 148 | params map[string][]string 149 | files map[string]string 150 | setting BeegoHTTPSettings 151 | resp *http.Response 152 | body []byte 153 | dump []byte 154 | } 155 | 156 | // GetRequest return the request object 157 | func (b *BeegoHTTPRequest) GetRequest() *http.Request { 158 | return b.req 159 | } 160 | 161 | // Setting Change request settings 162 | func (b *BeegoHTTPRequest) Setting(setting BeegoHTTPSettings) *BeegoHTTPRequest { 163 | b.setting = setting 164 | return b 165 | } 166 | 167 | // SetBasicAuth sets the request's Authorization header to use HTTP Basic Authentication with the provided username and password. 168 | func (b *BeegoHTTPRequest) SetBasicAuth(username, password string) *BeegoHTTPRequest { 169 | b.req.SetBasicAuth(username, password) 170 | return b 171 | } 172 | 173 | // SetEnableCookie sets enable/disable cookiejar 174 | func (b *BeegoHTTPRequest) SetEnableCookie(enable bool) *BeegoHTTPRequest { 175 | b.setting.EnableCookie = enable 176 | return b 177 | } 178 | 179 | // SetUserAgent sets User-Agent header field 180 | func (b *BeegoHTTPRequest) SetUserAgent(useragent string) *BeegoHTTPRequest { 181 | b.setting.UserAgent = useragent 182 | return b 183 | } 184 | 185 | // Debug sets show debug or not when executing request. 186 | func (b *BeegoHTTPRequest) Debug(isdebug bool) *BeegoHTTPRequest { 187 | b.setting.ShowDebug = isdebug 188 | return b 189 | } 190 | 191 | // DumpBody setting whether need to Dump the Body. 192 | func (b *BeegoHTTPRequest) DumpBody(isdump bool) *BeegoHTTPRequest { 193 | b.setting.DumpBody = isdump 194 | return b 195 | } 196 | 197 | // DumpRequest return the DumpRequest 198 | func (b *BeegoHTTPRequest) DumpRequest() []byte { 199 | return b.dump 200 | } 201 | 202 | // SetTimeout sets connect time out and read-write time out for BeegoRequest. 203 | func (b *BeegoHTTPRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHTTPRequest { 204 | b.setting.ConnectTimeout = connectTimeout 205 | b.setting.ReadWriteTimeout = readWriteTimeout 206 | return b 207 | } 208 | 209 | // SetTLSClientConfig sets tls connection configurations if visiting https url. 210 | func (b *BeegoHTTPRequest) SetTLSClientConfig(config *tls.Config) *BeegoHTTPRequest { 211 | b.setting.TLSClientConfig = config 212 | return b 213 | } 214 | 215 | // Header add header item string in request. 216 | func (b *BeegoHTTPRequest) Header(key, value string) *BeegoHTTPRequest { 217 | b.req.Header.Set(key, value) 218 | return b 219 | } 220 | 221 | // SetHost set the request host 222 | func (b *BeegoHTTPRequest) SetHost(host string) *BeegoHTTPRequest { 223 | b.req.Host = host 224 | return b 225 | } 226 | 227 | // SetProtocolVersion Set the protocol version for incoming requests. 228 | // Client requests always use HTTP/1.1. 229 | func (b *BeegoHTTPRequest) SetProtocolVersion(vers string) *BeegoHTTPRequest { 230 | if len(vers) == 0 { 231 | vers = "HTTP/1.1" 232 | } 233 | 234 | major, minor, ok := http.ParseHTTPVersion(vers) 235 | if ok { 236 | b.req.Proto = vers 237 | b.req.ProtoMajor = major 238 | b.req.ProtoMinor = minor 239 | } 240 | 241 | return b 242 | } 243 | 244 | // SetCookie add cookie into request. 245 | func (b *BeegoHTTPRequest) SetCookie(cookie *http.Cookie) *BeegoHTTPRequest { 246 | b.req.Header.Add("Cookie", cookie.String()) 247 | return b 248 | } 249 | 250 | // SetTransport set the setting transport 251 | func (b *BeegoHTTPRequest) SetTransport(transport http.RoundTripper) *BeegoHTTPRequest { 252 | b.setting.Transport = transport 253 | return b 254 | } 255 | 256 | // SetProxy set the http proxy 257 | // example: 258 | // 259 | // func(req *http.Request) (*url.URL, error) { 260 | // u, _ := url.ParseRequestURI("http://127.0.0.1:8118") 261 | // return u, nil 262 | // } 263 | func (b *BeegoHTTPRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHTTPRequest { 264 | b.setting.Proxy = proxy 265 | return b 266 | } 267 | 268 | // Param adds query param in to request. 269 | // params build query string as ?key1=value1&key2=value2... 270 | func (b *BeegoHTTPRequest) Param(key, value string) *BeegoHTTPRequest { 271 | if param, ok := b.params[key]; ok { 272 | b.params[key] = append(param, value) 273 | } else { 274 | b.params[key] = []string{value} 275 | } 276 | return b 277 | } 278 | 279 | // PostFile add a post file to the request 280 | func (b *BeegoHTTPRequest) PostFile(formname, filename string) *BeegoHTTPRequest { 281 | b.files[formname] = filename 282 | return b 283 | } 284 | 285 | // Body adds request raw body. 286 | // it supports string and []byte. 287 | func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest { 288 | switch t := data.(type) { 289 | case string: 290 | bf := bytes.NewBufferString(t) 291 | b.req.Body = ioutil.NopCloser(bf) 292 | b.req.ContentLength = int64(len(t)) 293 | case []byte: 294 | bf := bytes.NewBuffer(t) 295 | b.req.Body = ioutil.NopCloser(bf) 296 | b.req.ContentLength = int64(len(t)) 297 | } 298 | return b 299 | } 300 | 301 | // JSONBody adds request raw body encoding by JSON. 302 | func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { 303 | if b.req.Body == nil && obj != nil { 304 | byts, err := json.Marshal(obj) 305 | if err != nil { 306 | return b, err 307 | } 308 | b.req.Body = ioutil.NopCloser(bytes.NewReader(byts)) 309 | b.req.ContentLength = int64(len(byts)) 310 | b.req.Header.Set("Content-Type", "application/json") 311 | } 312 | return b, nil 313 | } 314 | 315 | func (b *BeegoHTTPRequest) buildURL(paramBody string) { 316 | // build GET url with query string 317 | if b.req.Method == "GET" && len(paramBody) > 0 { 318 | if strings.Index(b.url, "?") != -1 { 319 | b.url += "&" + paramBody 320 | } else { 321 | b.url = b.url + "?" + paramBody 322 | } 323 | return 324 | } 325 | 326 | // build POST/PUT/PATCH url and body 327 | if (b.req.Method == "POST" || b.req.Method == "PUT" || b.req.Method == "PATCH") && b.req.Body == nil { 328 | // with files 329 | if len(b.files) > 0 { 330 | pr, pw := io.Pipe() 331 | bodyWriter := multipart.NewWriter(pw) 332 | go func() { 333 | for formname, filename := range b.files { 334 | fileWriter, err := bodyWriter.CreateFormFile(formname, filename) 335 | if err != nil { 336 | log.Println("Httplib:", err) 337 | } 338 | fh, err := os.Open(filename) 339 | if err != nil { 340 | log.Println("Httplib:", err) 341 | } 342 | //iocopy 343 | _, err = io.Copy(fileWriter, fh) 344 | fh.Close() 345 | if err != nil { 346 | log.Println("Httplib:", err) 347 | } 348 | } 349 | for k, v := range b.params { 350 | for _, vv := range v { 351 | bodyWriter.WriteField(k, vv) 352 | } 353 | } 354 | bodyWriter.Close() 355 | pw.Close() 356 | }() 357 | b.Header("Content-Type", bodyWriter.FormDataContentType()) 358 | b.req.Body = ioutil.NopCloser(pr) 359 | return 360 | } 361 | 362 | // with params 363 | if len(paramBody) > 0 { 364 | b.Header("Content-Type", "application/x-www-form-urlencoded") 365 | b.Body(paramBody) 366 | } 367 | } 368 | } 369 | 370 | func (b *BeegoHTTPRequest) getResponse() (*http.Response, error) { 371 | if b.resp.StatusCode != 0 { 372 | return b.resp, nil 373 | } 374 | resp, err := b.DoRequest() 375 | if err != nil { 376 | return nil, err 377 | } 378 | b.resp = resp 379 | return resp, nil 380 | } 381 | 382 | // DoRequest will do the client.Do 383 | func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) { 384 | var paramBody string 385 | if len(b.params) > 0 { 386 | var buf bytes.Buffer 387 | for k, v := range b.params { 388 | for _, vv := range v { 389 | buf.WriteString(url.QueryEscape(k)) 390 | buf.WriteByte('=') 391 | buf.WriteString(url.QueryEscape(vv)) 392 | buf.WriteByte('&') 393 | } 394 | } 395 | paramBody = buf.String() 396 | paramBody = paramBody[0 : len(paramBody)-1] 397 | } 398 | 399 | b.buildURL(paramBody) 400 | url, err := url.Parse(b.url) 401 | if err != nil { 402 | return nil, err 403 | } 404 | 405 | b.req.URL = url 406 | 407 | trans := b.setting.Transport 408 | 409 | if trans == nil { 410 | // create default transport 411 | trans = &http.Transport{ 412 | TLSClientConfig: b.setting.TLSClientConfig, 413 | Proxy: b.setting.Proxy, 414 | Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), 415 | } 416 | } else { 417 | // if b.transport is *http.Transport then set the settings. 418 | if t, ok := trans.(*http.Transport); ok { 419 | if t.TLSClientConfig == nil { 420 | t.TLSClientConfig = b.setting.TLSClientConfig 421 | } 422 | if t.Proxy == nil { 423 | t.Proxy = b.setting.Proxy 424 | } 425 | if t.Dial == nil { 426 | t.Dial = TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout) 427 | } 428 | } 429 | } 430 | 431 | var jar http.CookieJar 432 | if b.setting.EnableCookie { 433 | if defaultCookieJar == nil { 434 | createDefaultCookie() 435 | } 436 | jar = defaultCookieJar 437 | } 438 | 439 | client := &http.Client{ 440 | Transport: trans, 441 | Jar: jar, 442 | } 443 | 444 | if b.setting.UserAgent != "" && b.req.Header.Get("User-Agent") == "" { 445 | b.req.Header.Set("User-Agent", b.setting.UserAgent) 446 | } 447 | 448 | if b.setting.ShowDebug { 449 | dump, err := httputil.DumpRequest(b.req, b.setting.DumpBody) 450 | if err != nil { 451 | log.Println(err.Error()) 452 | } 453 | b.dump = dump 454 | } 455 | return client.Do(b.req) 456 | } 457 | 458 | // String returns the body string in response. 459 | // it calls Response inner. 460 | func (b *BeegoHTTPRequest) String() (string, error) { 461 | data, err := b.Bytes() 462 | if err != nil { 463 | return "", err 464 | } 465 | 466 | return string(data), nil 467 | } 468 | 469 | // Bytes returns the body []byte in response. 470 | // it calls Response inner. 471 | func (b *BeegoHTTPRequest) Bytes() ([]byte, error) { 472 | if b.body != nil { 473 | return b.body, nil 474 | } 475 | resp, err := b.getResponse() 476 | if err != nil { 477 | return nil, err 478 | } 479 | if resp.Body == nil { 480 | return nil, nil 481 | } 482 | defer resp.Body.Close() 483 | if b.setting.Gzip && resp.Header.Get("Content-Encoding") == "gzip" { 484 | reader, err := gzip.NewReader(resp.Body) 485 | if err != nil { 486 | return nil, err 487 | } 488 | b.body, err = ioutil.ReadAll(reader) 489 | } else { 490 | b.body, err = ioutil.ReadAll(resp.Body) 491 | } 492 | return b.body, err 493 | } 494 | 495 | // ToFile saves the body data in response to one file. 496 | // it calls Response inner. 497 | func (b *BeegoHTTPRequest) ToFile(filename string) error { 498 | f, err := os.Create(filename) 499 | if err != nil { 500 | return err 501 | } 502 | defer f.Close() 503 | 504 | resp, err := b.getResponse() 505 | if err != nil { 506 | return err 507 | } 508 | if resp.Body == nil { 509 | return nil 510 | } 511 | defer resp.Body.Close() 512 | _, err = io.Copy(f, resp.Body) 513 | return err 514 | } 515 | 516 | // ToJSON returns the map that marshals from the body bytes as json in response . 517 | // it calls Response inner. 518 | func (b *BeegoHTTPRequest) ToJSON(v interface{}) error { 519 | data, err := b.Bytes() 520 | if err != nil { 521 | return err 522 | } 523 | return json.Unmarshal(data, v) 524 | } 525 | 526 | // ToXML returns the map that marshals from the body bytes as xml in response . 527 | // it calls Response inner. 528 | func (b *BeegoHTTPRequest) ToXML(v interface{}) error { 529 | data, err := b.Bytes() 530 | if err != nil { 531 | return err 532 | } 533 | return xml.Unmarshal(data, v) 534 | } 535 | 536 | // Response executes request client gets response mannually. 537 | func (b *BeegoHTTPRequest) Response() (*http.Response, error) { 538 | return b.getResponse() 539 | } 540 | 541 | // TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field. 542 | func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { 543 | return func(netw, addr string) (net.Conn, error) { 544 | conn, err := net.DialTimeout(netw, addr, cTimeout) 545 | if err != nil { 546 | return nil, err 547 | } 548 | err = conn.SetDeadline(time.Now().Add(rwTimeout)) 549 | return conn, err 550 | } 551 | } 552 | -------------------------------------------------------------------------------- /httplib/httplib_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httplib 16 | 17 | import ( 18 | "io/ioutil" 19 | "os" 20 | "strings" 21 | "testing" 22 | "time" 23 | ) 24 | 25 | func TestResponse(t *testing.T) { 26 | req := Get("http://httpbin.org/get") 27 | resp, err := req.Response() 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | t.Log(resp) 32 | } 33 | 34 | func TestGet(t *testing.T) { 35 | req := Get("http://httpbin.org/get") 36 | b, err := req.Bytes() 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | t.Log(b) 41 | 42 | s, err := req.String() 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | t.Log(s) 47 | 48 | if string(b) != s { 49 | t.Fatal("request data not match") 50 | } 51 | } 52 | 53 | func TestSimplePost(t *testing.T) { 54 | v := "smallfish" 55 | req := Post("http://httpbin.org/post") 56 | req.Param("username", v) 57 | 58 | str, err := req.String() 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | t.Log(str) 63 | 64 | n := strings.Index(str, v) 65 | if n == -1 { 66 | t.Fatal(v + " not found in post") 67 | } 68 | } 69 | 70 | //func TestPostFile(t *testing.T) { 71 | // v := "smallfish" 72 | // req := Post("http://httpbin.org/post") 73 | // req.Debug(true) 74 | // req.Param("username", v) 75 | // req.PostFile("uploadfile", "httplib_test.go") 76 | 77 | // str, err := req.String() 78 | // if err != nil { 79 | // t.Fatal(err) 80 | // } 81 | // t.Log(str) 82 | 83 | // n := strings.Index(str, v) 84 | // if n == -1 { 85 | // t.Fatal(v + " not found in post") 86 | // } 87 | //} 88 | 89 | func TestSimplePut(t *testing.T) { 90 | str, err := Put("http://httpbin.org/put").String() 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | t.Log(str) 95 | } 96 | 97 | func TestSimpleDelete(t *testing.T) { 98 | str, err := Delete("http://httpbin.org/delete").String() 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | t.Log(str) 103 | } 104 | 105 | func TestWithCookie(t *testing.T) { 106 | v := "smallfish" 107 | str, err := Get("http://httpbin.org/cookies/set?k1=" + v).SetEnableCookie(true).String() 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | t.Log(str) 112 | 113 | str, err = Get("http://httpbin.org/cookies").SetEnableCookie(true).String() 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | t.Log(str) 118 | 119 | n := strings.Index(str, v) 120 | if n == -1 { 121 | t.Fatal(v + " not found in cookie") 122 | } 123 | } 124 | 125 | func TestWithBasicAuth(t *testing.T) { 126 | str, err := Get("http://httpbin.org/basic-auth/user/passwd").SetBasicAuth("user", "passwd").String() 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | t.Log(str) 131 | n := strings.Index(str, "authenticated") 132 | if n == -1 { 133 | t.Fatal("authenticated not found in response") 134 | } 135 | } 136 | 137 | func TestWithUserAgent(t *testing.T) { 138 | v := "beego" 139 | str, err := Get("http://httpbin.org/headers").SetUserAgent(v).String() 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | t.Log(str) 144 | 145 | n := strings.Index(str, v) 146 | if n == -1 { 147 | t.Fatal(v + " not found in user-agent") 148 | } 149 | } 150 | 151 | func TestWithSetting(t *testing.T) { 152 | v := "beego" 153 | var setting BeegoHTTPSettings 154 | setting.EnableCookie = true 155 | setting.UserAgent = v 156 | setting.Transport = nil 157 | setting.ReadWriteTimeout = 5 * time.Second 158 | SetDefaultSetting(setting) 159 | 160 | str, err := Get("http://httpbin.org/get").String() 161 | if err != nil { 162 | t.Fatal(err) 163 | } 164 | t.Log(str) 165 | 166 | n := strings.Index(str, v) 167 | if n == -1 { 168 | t.Fatal(v + " not found in user-agent") 169 | } 170 | } 171 | 172 | func TestToJson(t *testing.T) { 173 | req := Get("http://httpbin.org/ip") 174 | resp, err := req.Response() 175 | if err != nil { 176 | t.Fatal(err) 177 | } 178 | t.Log(resp) 179 | 180 | // httpbin will return http remote addr 181 | type IP struct { 182 | Origin string `json:"origin"` 183 | } 184 | var ip IP 185 | err = req.ToJSON(&ip) 186 | if err != nil { 187 | t.Fatal(err) 188 | } 189 | t.Log(ip.Origin) 190 | 191 | if n := strings.Count(ip.Origin, "."); n != 3 { 192 | t.Fatal("response is not valid ip") 193 | } 194 | } 195 | 196 | func TestToFile(t *testing.T) { 197 | f := "beego_testfile" 198 | req := Get("http://httpbin.org/ip") 199 | err := req.ToFile(f) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | defer os.Remove(f) 204 | b, err := ioutil.ReadFile(f) 205 | if n := strings.Index(string(b), "origin"); n == -1 { 206 | t.Fatal(err) 207 | } 208 | } 209 | 210 | func TestHeader(t *testing.T) { 211 | req := Get("http://httpbin.org/headers") 212 | req.Header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36") 213 | str, err := req.String() 214 | if err != nil { 215 | t.Fatal(err) 216 | } 217 | t.Log(str) 218 | } 219 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Json 2 | // Json解析功能,重写widuu/gojson新增部分功能 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 无依赖 5 | // 附上gojson地址:github.com/widuu/gojson 6 | 7 | package phalgo 8 | 9 | import ( 10 | "encoding/json" 11 | "errors" 12 | "strconv" 13 | ) 14 | 15 | type Js struct { 16 | Data interface{} 17 | } 18 | 19 | 20 | //Initialize the json configruation 21 | func Json(data string) *Js { 22 | j := new(Js) 23 | var f interface{} 24 | err := json.Unmarshal([]byte(data), &f) 25 | if err != nil { 26 | return j 27 | } 28 | j.Data = f 29 | return j 30 | } 31 | 32 | 33 | 34 | //According to the key of the returned data information,return js.data 35 | func (j *Js) Get(key string) *Js { 36 | m := j.Getdata() 37 | if v, ok := m[key]; ok { 38 | j.Data = v 39 | return j 40 | } 41 | j.Data = nil 42 | return j 43 | } 44 | 45 | //return json data 46 | func (j *Js) Getdata() map[string]interface{} { 47 | if m, ok := (j.Data).(map[string]interface{}); ok { 48 | return m 49 | } 50 | return nil 51 | } 52 | 53 | func (j *Js) Getindex(i int) *Js { 54 | 55 | num := i - 1 56 | if m, ok := (j.Data).([]interface{}); ok { 57 | v := m[num] 58 | j.Data = v 59 | return j 60 | } 61 | 62 | if m, ok := (j.Data).(map[string]interface{}); ok { 63 | var n = 0 64 | var data = make(map[string]interface{}) 65 | for i, v := range m { 66 | if n == num { 67 | switch vv := v.(type) { 68 | case float64: 69 | data[i] = strconv.FormatFloat(vv, 'f', -1, 64) 70 | j.Data = data 71 | return j 72 | case string: 73 | data[i] = vv 74 | j.Data = data 75 | return j 76 | case []interface{}: 77 | j.Data = vv 78 | return j 79 | } 80 | 81 | } 82 | n++ 83 | } 84 | 85 | } 86 | j.Data = nil 87 | return j 88 | } 89 | 90 | // When the data {"result":["username","password"]} can use arrayindex(1) get the username 91 | func (j *Js) Arrayindex(i int) string { 92 | num := i - 1 93 | if i > len((j.Data).([]interface{})) { 94 | data := errors.New("index out of range list").Error() 95 | return data 96 | } 97 | if m, ok := (j.Data).([]interface{}); ok { 98 | v := m[num] 99 | switch vv := v.(type) { 100 | case float64: 101 | return strconv.FormatFloat(vv, 'f', -1, 64) 102 | case string: 103 | return vv 104 | default: 105 | return "" 106 | } 107 | 108 | } 109 | 110 | if _, ok := (j.Data).(map[string]interface{}); ok { 111 | return "error" 112 | } 113 | return "error" 114 | 115 | } 116 | 117 | //The data must be []interface{} ,According to your custom number to return your key and array data 118 | func (j *Js) Getkey(key string, i int) *Js { 119 | num := i - 1 120 | if i > len((j.Data).([]interface{})) { 121 | j.Data = errors.New("index out of range list").Error() 122 | return j 123 | } 124 | if m, ok := (j.Data).([]interface{}); ok { 125 | v := m[num].(map[string]interface{}) 126 | if h, ok := v[key]; ok { 127 | j.Data = h 128 | return j 129 | } 130 | 131 | } 132 | j.Data = nil 133 | return j 134 | } 135 | 136 | //According to the custom of the PATH to find the PATH 137 | func (j *Js) Getpath(args ...string) *Js { 138 | d := j 139 | for i := range args { 140 | m := d.Getdata() 141 | 142 | if val, ok := m[args[i]]; ok { 143 | d.Data = val 144 | } else { 145 | d.Data = nil 146 | return d 147 | } 148 | } 149 | return d 150 | } 151 | 152 | //----------------------------------------新增------------------------------------- 153 | 154 | func (j *Js)ToData() interface{} { 155 | return j.Data 156 | } 157 | 158 | func (j *Js)ToSlice() []interface{} { 159 | if m, ok := (j.Data).([]interface{}); ok { 160 | return m 161 | } 162 | return nil 163 | } 164 | 165 | func (j *Js) ToInt() int { 166 | if m, ok := j.Data.(int); ok { 167 | return m 168 | } 169 | return 0 170 | } 171 | 172 | func (j *Js) ToFloat() float64 { 173 | 174 | if m, ok := j.Data.(float64); ok { 175 | return m 176 | } 177 | if m, ok := j.Data.(string); ok { 178 | if m, ok := strconv.ParseFloat(m, 64); ok != nil { 179 | return m; 180 | } 181 | } 182 | return 0 183 | } 184 | 185 | //----------------------------------------新增---------------------------------------- 186 | 187 | func (j *Js) Tostring() string { 188 | if m, ok := j.Data.(string); ok { 189 | return m 190 | } 191 | if m, ok := j.Data.(float64); ok { 192 | return strconv.FormatFloat(m, 'f', -1, 64) 193 | } 194 | return "" 195 | } 196 | 197 | func (j *Js) ToArray() (k, d []string) { 198 | var key, data []string 199 | if m, ok := (j.Data).([]interface{}); ok { 200 | for _, value := range m { 201 | for index, v := range value.(map[string]interface{}) { 202 | switch vv := v.(type) { 203 | case float64: 204 | data = append(data, strconv.FormatFloat(vv, 'f', -1, 64)) 205 | key = append(key, index) 206 | case string: 207 | data = append(data, vv) 208 | key = append(key, index) 209 | 210 | } 211 | } 212 | } 213 | 214 | return key, data 215 | } 216 | 217 | if m, ok := (j.Data).(map[string]interface{}); ok { 218 | for index, v := range m { 219 | switch vv := v.(type) { 220 | case float64: 221 | data = append(data, strconv.FormatFloat(vv, 'f', -1, 64)) 222 | key = append(key, index) 223 | case string: 224 | data = append(data, vv) 225 | key = append(key, index) 226 | } 227 | } 228 | return key, data 229 | } 230 | 231 | return nil, nil 232 | } 233 | 234 | //获取[]string类型,整数和浮点类型会被转换成string 235 | func (j *Js) StringtoArray() []string { 236 | var data []string 237 | 238 | switch j.Data.(type) { 239 | case []interface{}: 240 | for _, v := range j.Data.([]interface{}) { 241 | switch vv := v.(type) { 242 | case string: 243 | data = append(data, vv) 244 | case float64: 245 | data = append(data, strconv.FormatFloat(vv, 'f', -1, 64)) 246 | } 247 | } 248 | } 249 | 250 | return data 251 | } -------------------------------------------------------------------------------- /logs.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Logs 2 | // 日志记录功能 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/Sirupsen/logrus" 基于logrus的log类 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/Sirupsen/logrus" 11 | "os" 12 | "time" 13 | "fmt" 14 | ) 15 | 16 | var LogS *logrus.Logger 17 | var day string 18 | var logfile *os.File 19 | 20 | // 初始化Log日志记录 21 | func init() { 22 | var err error 23 | LogS = logrus.New() 24 | LogS.Formatter = new(logrus.JSONFormatter) 25 | //log.Formatter = new(logrus.TextFormatter) // default 26 | LogS.Level = logrus.DebugLevel 27 | 28 | if !IsDirExists(GetPath() + "/Runtime") { 29 | if mkdirerr := MkdirFile(GetPath() + "/Runtime"); mkdirerr != nil { 30 | fmt.Println(mkdirerr) 31 | } 32 | } 33 | 34 | logfile, err = os.OpenFile(GetPath() + "/Runtime/" + time.Now().Format("2006-01-02") + ".log", os.O_RDWR | os.O_APPEND, 0666) 35 | if err != nil { 36 | logfile, err = os.Create(GetPath() + "/Runtime/" + time.Now().Format("2006-01-02") + ".log") 37 | if err != nil { 38 | fmt.Println(err) 39 | } 40 | } 41 | LogS.Out = logfile 42 | day = time.Now().Format("02") 43 | } 44 | 45 | // 检测是否跨天了,把记录记录到新的文件目录中 46 | func updateLogFile() { 47 | var err error 48 | day2 := time.Now().Format("02") 49 | if day2 != day { 50 | logfile.Close() 51 | logfile, err = os.Create(GetPath() + "/Runtime/" + time.Now().Format("2006-01-02") + ".log") 52 | if err != nil { 53 | fmt.Println(err) 54 | } 55 | LogS.Out = logfile 56 | } 57 | } 58 | 59 | // 记录Debug信息 60 | func LogDebug(str interface{}, data logrus.Fields) { 61 | updateLogFile() 62 | LogS.WithFields(data).Debug(str) 63 | } 64 | 65 | // 记录Info信息 66 | func LogInfo(str interface{}, data logrus.Fields) { 67 | updateLogFile() 68 | LogS.WithFields(data).Info(str) 69 | } 70 | 71 | // 记录Error信息 72 | func LogError(str interface{}, data logrus.Fields) { 73 | updateLogFile() 74 | LogS.WithFields(data).Error(str) 75 | } 76 | -------------------------------------------------------------------------------- /pprof.go: -------------------------------------------------------------------------------- 1 | // PhalGo-pprof 2 | // 快捷的pprof性能分析 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "net/http/pprof" 6 | 7 | package phalgo 8 | 9 | import ( 10 | _ "net/http/pprof" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | // 开启PProf性能分析 16 | func OpenPProf(port string) { 17 | go func() { 18 | log.Println(http.ListenAndServe("localhost:" + port, nil)) 19 | }() 20 | } -------------------------------------------------------------------------------- /redis.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Redis 2 | // Redis操作 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/astaxie/beego/cache" 基于beego的redis操作 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/garyburd/redigo/redis" 11 | ) 12 | 13 | var Redis redis.Conn 14 | 15 | // 初始化Redis连接 16 | func NewRedis(redisdb string) { 17 | var err error 18 | 19 | Config.SetDefault(redisdb, map[string]interface{}{ 20 | "network" : "tcp", 21 | "address" : "127.0.0.1:6379", 22 | "password" : "", 23 | }) 24 | 25 | Redis, err = redis.Dial(Config.GetString(redisdb + ".network"), Config.GetString(redisdb + ".address"), redis.DialPassword(Config.GetString(redisdb + ".password")), redis.DialDatabase(0)) 26 | if err != nil { 27 | print(err) 28 | } 29 | defer Redis.Close() 30 | } 31 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Request 2 | // 请求解析,获取get,post,json参数,签名加密,链式操作,并且参数验证 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/astaxie/beego/validation" 基于beego的拦截器(已经集成) 6 | // "github.com/labstack/echo" 依赖于echo 7 | 8 | package phalgo 9 | 10 | import ( 11 | "strconv" 12 | "github.com/labstack/echo" 13 | "github.com/wenzhenxi/phalgo/validation" 14 | "github.com/wenzhenxi/phalgo/errors" 15 | "crypto/md5" 16 | "encoding/hex" 17 | "encoding/base64" 18 | "regexp" 19 | "fmt" 20 | ) 21 | 22 | type Request struct { 23 | Context echo.Context 24 | params *param 25 | Jsonparam *Jsonparam 26 | valid validation.Validation 27 | Json *Js 28 | Encryption bool 29 | Des Des 30 | jsonTag bool 31 | Debug bool 32 | } 33 | 34 | type Jsonparam struct { 35 | key string 36 | val Js 37 | } 38 | 39 | type param struct { 40 | key string 41 | val string 42 | min int 43 | max int 44 | } 45 | 46 | // 初始化request 47 | func NewRequest(c echo.Context) *Request { 48 | 49 | R := new(Request) 50 | R.Context = c 51 | //增加debug参数的匹配 52 | if R.Param("__debug__").SetDefault("").GetString() == "" { 53 | R.Debug = false 54 | } else { 55 | R.Debug = true 56 | } 57 | return R 58 | } 59 | 60 | // 清理参数 61 | func (this *Request)Clean() { 62 | 63 | this.params = new(param) 64 | this.Jsonparam = new(Jsonparam) 65 | } 66 | 67 | // 返回报错信息 68 | func (this *Request)GetError() error { 69 | 70 | if this.valid.HasErrors() { 71 | for _, v := range this.valid.Errors { 72 | 73 | return errors.ErrCustom(v.Message + v.Key) 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // 进行签名验证以及DES加密验证 81 | func (this *Request)InitDES() error { 82 | 83 | params := "" 84 | this.Json = new(Js) 85 | params = this.PostParam(Config.GetString("system.DESParam")).GetString() 86 | 87 | //如果是开启了 DES加密 需要验证是否加密,然后需要验证签名,和加密内容 88 | if Config.GetBool("system.OpenDES") == true { 89 | if params == "" { 90 | return errors.ErrNoParams 91 | } 92 | } 93 | 94 | if params != "" { 95 | enableSignCheck := Config.GetBool("DES.EnableSignCheck") 96 | sign := this.PostParam("sign").GetString() 97 | timeStamp := this.PostParam("timeStamp").GetString() 98 | randomNum := this.PostParam("randomNum").GetString() 99 | isEncrypted := this.PostParam("isEncrypted").GetString() 100 | if enableSignCheck { 101 | if sign == "" || timeStamp == "" || randomNum == "" { 102 | return errors.ErrMD5 103 | } 104 | 105 | keymd5 := md5.New() 106 | keymd5.Write([]byte(Config.GetString("system.MD5key"))) 107 | md5key := hex.EncodeToString(keymd5.Sum(nil)) 108 | 109 | signmd5 := md5.New() 110 | signmd5.Write([]byte(params + isEncrypted + timeStamp + randomNum + md5key)) 111 | sign2 := hex.EncodeToString(signmd5.Sum(nil)) 112 | 113 | if sign != sign2 { 114 | return errors.ErrMD5 115 | } 116 | } 117 | //如果是加密的params那么进行解密操作 118 | if isEncrypted == "1" { 119 | 120 | base64params, err := base64.StdEncoding.DecodeString(params) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | origData, err := this.Des.DesDecrypt(base64params, Config.GetString("system.DESkey"), Config.GetString("system.DESiv")) 126 | 127 | if err != nil { 128 | return err 129 | } 130 | params = string(origData) 131 | } 132 | this.Json = Json(params) 133 | this.Encryption = true 134 | } 135 | return nil; 136 | } 137 | 138 | // 使用Json参数传入Json字符 139 | func (this *Request)SetJson(json string) { 140 | 141 | this.Json = Json(json) 142 | } 143 | 144 | 145 | //--------------------------------------------------------获取参数------------------------------------- 146 | 147 | // 获取Json参数 148 | func (this *Request)DESParam(keys ...string) *Request { 149 | var key string 150 | var str string 151 | this.Clean() 152 | if (this.Encryption) { 153 | json := *this.Json 154 | for _, v := range keys { 155 | json.Get(v) 156 | key = key + v 157 | } 158 | 159 | this.Jsonparam.val = json 160 | this.Jsonparam.key = key 161 | this.jsonTag = true 162 | } else { 163 | str = this.Context.QueryParam(keys[0]) 164 | 165 | if str == "" { 166 | str = this.Context.FormValue(keys[0]) 167 | } 168 | this.params.val = str 169 | this.params.key = keys[0] 170 | this.jsonTag = false 171 | } 172 | 173 | return this 174 | } 175 | 176 | // 获取Json参数 177 | func (this *Request)JsonParam(keys ...string) *Request { 178 | 179 | var key string 180 | this.Clean() 181 | json := *this.Json 182 | for _, v := range keys { 183 | json.Get(v) 184 | key = key + v 185 | } 186 | 187 | this.Jsonparam.val = json 188 | this.Jsonparam.key = key 189 | this.jsonTag = true 190 | 191 | return this 192 | } 193 | 194 | // 获取Get参数 195 | func (this *Request)GetParam(key string) *Request { 196 | 197 | this.Clean() 198 | str := this.Context.QueryParam(key) 199 | this.params.val = str 200 | this.params.key = key 201 | this.jsonTag = false 202 | 203 | return this 204 | } 205 | 206 | // 获取post参数 207 | func (this *Request)PostParam(key string) *Request { 208 | 209 | this.Clean() 210 | str := this.Context.FormValue(key) 211 | this.params.val = str 212 | this.params.key = key 213 | this.jsonTag = false 214 | 215 | return this 216 | } 217 | 218 | // 获取请求参数顺序get->post 219 | func (this *Request)Param(key string) *Request { 220 | 221 | var str string 222 | this.Clean() 223 | str = this.Context.QueryParam(key) 224 | 225 | if str == "" { 226 | str = this.Context.FormValue(key) 227 | } 228 | 229 | this.params.val = str 230 | this.params.key = key 231 | 232 | return this 233 | } 234 | 235 | func (this *Request)SetDefault(val string) *Request { 236 | if this.jsonTag == true { 237 | defJson := fmt.Sprintf(`{"index":"%s"}`, val) 238 | this.Jsonparam.val = *Json(defJson).Get("index") 239 | /* fmt.Println(defJson) 240 | fmt.Println(this.Jsonparam.val.Tostring())*/ 241 | } else { 242 | this.params.val = val 243 | } 244 | return this 245 | } 246 | 247 | //----------------------------------------------------过滤验证------------------------------------ 248 | 249 | // GET,POST或JSON参数是否必须 250 | func (this *Request)Require(b bool) *Request { 251 | 252 | this.valid.Required(this.getParamVal(), this.getParamKey()).Message("缺少必要参数,参数名称:") 253 | return this 254 | } 255 | 256 | // 设置参数最大值 257 | func (this *Request)Max(i int) *Request { 258 | 259 | this.params.max = i 260 | return this 261 | } 262 | 263 | //设置参数最小值 264 | func (this *Request)Min(i int) *Request { 265 | 266 | this.params.min = i 267 | return this 268 | } 269 | 270 | 271 | //--------------------------------------------GET,POST获取参数------------------------------------ 272 | 273 | // 获取并且验证参数 string类型 适用于GET或POST参数 274 | func (this *Request)GetString() string { 275 | 276 | var str string 277 | 278 | str = this.getParamVal() 279 | if this.params.min != 0 { 280 | this.valid.MinSize(str, this.params.min, this.getParamKey()). 281 | Message("参数异常!参数长度为%d不能小于%d,参数名称:", len([]rune(str)), this.params.min) 282 | } 283 | if this.params.max != 0 { 284 | this.valid.MaxSize(str, this.params.max, this.getParamKey()). 285 | Message("参数异常!参数长度为%d不能大于%d,参数名称:", len([]rune(str)), this.params.max) 286 | } 287 | 288 | return str 289 | } 290 | 291 | // 获取并且验证参数 int类型 适用于GET或POST参数 292 | func (this *Request)GetInt() int { 293 | var ( 294 | i int 295 | err error 296 | ) 297 | 298 | if this.getParamVal() == "" { 299 | i = 0 300 | } else { 301 | i, err = strconv.Atoi(this.getParamVal()) 302 | if err != nil { 303 | this.valid.SetError(this.getParamKey(), "参数异常!参数不是int类型,参数名称:") 304 | } 305 | } 306 | 307 | if this.params.min != 0 { 308 | this.valid.Min(i, this.params.min, this.getParamKey()). 309 | Message("参数异常!参数值为%d不能小于%d,参数名称:", i, this.params.min) 310 | } 311 | if this.params.max != 0 { 312 | this.valid.Max(i, this.params.max, this.getParamKey()). 313 | Message("参数异常!参数值为%d不能大于%d,参数名称:", i, this.params.max) 314 | } 315 | 316 | return i 317 | } 318 | 319 | // 获取并且验证参数 float64类型 适用于GET或POST参数 320 | func (this *Request)GetFloat() float64 { 321 | 322 | var ( 323 | i float64 324 | err error 325 | ) 326 | 327 | if this.getParamVal() == "" { 328 | i = 0 329 | } else { 330 | i, err = strconv.ParseFloat(this.getParamVal(), 64) 331 | if err != nil { 332 | this.valid.SetError(this.getParamKey(), "此参数无法转换为float64类型,参数名称:") 333 | } 334 | } 335 | 336 | if this.params.min != 0 { 337 | this.valid.Min(int(i), this.params.min, this.getParamKey()). 338 | Message("参数异常!参数值为%f不能小于%d,参数名称:", i, this.params.min) 339 | } 340 | if this.params.max != 0 { 341 | this.valid.Max(int(i), this.params.max, this.getParamKey()). 342 | Message("参数异常!参数值为%f不能大于%d,参数名称:", i, this.params.max) 343 | } 344 | 345 | return i 346 | } 347 | 348 | // 邮政编码 349 | func (this *Request)ZipCode() *Request { 350 | 351 | this.valid.ZipCode(this.getParamVal(), this.getParamKey()).Message("参数异常!邮政编码验证失败,参数名称:") 352 | return this 353 | } 354 | 355 | // 手机号或固定电话号 356 | func (this *Request)Phone() *Request { 357 | 358 | this.valid.Phone(this.getParamVal(), this.getParamKey()).Message("参数异常!手机号或固定电话号验证失败,参数名称:") 359 | return this 360 | } 361 | 362 | // 固定电话号 363 | func (this *Request)Tel() *Request { 364 | 365 | this.valid.Tel(this.getParamVal(), this.getParamKey()).Message("参数异常!固定电话号验证失败,参数名称:") 366 | return this 367 | } 368 | 369 | // 手机号 370 | func (this *Request)Mobile() *Request { 371 | 372 | this.valid.Mobile(this.getParamVal(), this.getParamKey()).Message("参数异常!手机号验证失败,参数名称:") 373 | return this 374 | } 375 | 376 | // base64编码 377 | func (this *Request)Base64() *Request { 378 | 379 | this.valid.Base64(this.getParamVal(), this.getParamKey()).Message("参数异常!base64编码验证失败,参数名称:") 380 | return this 381 | } 382 | 383 | // IP格式,目前只支持IPv4格式验证 384 | func (this *Request)IP() *Request { 385 | 386 | this.valid.IP(this.getParamVal(), this.getParamKey()).Message("参数异常!IP格式验证失败,参数名称:") 387 | return this 388 | } 389 | 390 | // 邮箱格式 391 | func (this *Request)Email() *Request { 392 | 393 | this.valid.Email(this.getParamVal(), this.getParamKey()).Message("参数异常!邮箱格式验证失败,参数名称:") 394 | return this 395 | } 396 | 397 | // 正则匹配,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match) 398 | func (this *Request)Match(match string) *Request { 399 | 400 | this.valid.Match(this.getParamVal(), regexp.MustCompile(match), this.getParamKey()).Message("参数异常!正则验证失败,参数名称:") 401 | return this 402 | } 403 | 404 | // 反正则匹配,其他类型都将被转成字符串再匹配(fmt.Sprintf(“%v”, obj).Match) 405 | func (this *Request)NoMatch(match string) *Request { 406 | 407 | this.valid.NoMatch(this.getParamVal(), regexp.MustCompile(match), this.getParamKey()).Message("参数异常!邮箱格式验证失败,参数名称:") 408 | return this 409 | } 410 | 411 | // 数字 412 | func (this *Request)Numeric() *Request { 413 | 414 | this.valid.Numeric(this.getParamVal(), this.getParamKey()).Message("参数异常!数字格式验证失败,参数名称:") 415 | return this 416 | } 417 | 418 | // alpha字符 419 | func (this *Request)Alpha() *Request { 420 | 421 | this.valid.Alpha(this.getParamVal(), this.getParamKey()).Message("参数异常!alpha格式验证失败,参数名称:") 422 | return this 423 | } 424 | 425 | // alpha字符或数字 426 | func (this *Request)AlphaNumeric() *Request { 427 | 428 | this.valid.AlphaNumeric(this.getParamVal(), this.getParamKey()).Message("参数异常!AlphaNumeric格式验证失败,参数名称:") 429 | return this 430 | } 431 | 432 | // alpha字符或数字或横杠-_ 433 | func (this *Request)AlphaDash() *Request { 434 | 435 | this.valid.AlphaDash(this.getParamVal(), this.getParamKey()).Message("参数异常!AlphaDash格式验证失败,参数名称:") 436 | return this 437 | } 438 | 439 | // 返回解析参数的Val 440 | func (this *Request)getParamVal() string { 441 | 442 | if this.jsonTag { 443 | return this.Jsonparam.val.Tostring() 444 | } else { 445 | return this.params.val 446 | } 447 | } 448 | 449 | // 反回解析参数的Key 450 | func (this *Request)getParamKey() string { 451 | if this.jsonTag { 452 | return this.Jsonparam.key 453 | } else { 454 | return this.params.key 455 | } 456 | } 457 | 458 | 459 | // 获取并且验证参数 Json类型 适用于Json参数 460 | func (this *Request)GetJson() Js { 461 | 462 | return this.Jsonparam.val 463 | } 464 | 465 | // 捕获panic异样防止程序终止 并且记录到日志 466 | func (this *Request)ErrorLogRecover() { 467 | 468 | if err := recover(); err != nil { 469 | this.Context.Response().Write([]byte("系统错误!具体原因:" + TurnString(err))) 470 | LogError(err, map[string]interface{}{ 471 | "URL.Path":this.Context.Request().URL.Path, 472 | "QueryParams":this.Context.QueryParams(), 473 | }) 474 | } 475 | } 476 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Response 2 | // 返回json参数,默认结构code,data,msg 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/labstack/echo" 必须基于echo路由 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/labstack/echo" 11 | "net/http" 12 | ) 13 | 14 | type Response struct { 15 | Context echo.Context 16 | parameter *RetParameter 17 | } 18 | 19 | type RetParameter struct { 20 | Code int `json:"code";xml:"code"` 21 | Data interface{} `json:"data";xml:"data"` 22 | Msg string `json:"msg";xml:"msg"` 23 | } 24 | 25 | const DefaultCode = 1 26 | 27 | var HttpStatus = http.StatusOK 28 | 29 | // 初始化Response 30 | func NewResponse(c echo.Context) *Response { 31 | 32 | R := new(Response) 33 | R.Context = c 34 | R.parameter = new(RetParameter) 35 | R.parameter.Data = nil 36 | return R 37 | } 38 | 39 | // 设置返回的Status值默认http.StatusOK 40 | func (this *Response)SetStatus(i int) { 41 | HttpStatus = i 42 | } 43 | 44 | func (this *Response)SetMsg(s string) { 45 | this.parameter.Msg = s 46 | } 47 | 48 | func (this *Response)SetData(d interface{}) { 49 | this.parameter.Data = d 50 | } 51 | 52 | // 返回自定自定义的消息格式 53 | func (this *Response)RetCustomize(code int, d interface{}, msg string) error { 54 | 55 | this.parameter.Code = code 56 | this.parameter.Data = d 57 | this.parameter.Msg = msg 58 | 59 | return this.Ret(this.parameter) 60 | } 61 | 62 | // 返回成功的结果 默认code为1 63 | func (this *Response)RetSuccess(d interface{}) error { 64 | 65 | this.parameter.Code = DefaultCode 66 | this.parameter.Data = d 67 | 68 | return this.Ret(this.parameter) 69 | } 70 | 71 | // 返回失败结果 72 | func (this *Response)RetError(e error, c int) error { 73 | 74 | this.parameter.Code = c 75 | this.parameter.Msg = e.Error() 76 | 77 | return this.Ret(this.parameter) 78 | } 79 | 80 | // 返回结果 81 | func (this *Response)Ret(par interface{}) error { 82 | 83 | switch RetType { 84 | case 2: 85 | return this.Context.XML(HttpStatus, par) 86 | default: 87 | return this.Context.JSON(HttpStatus, par) 88 | } 89 | } 90 | 91 | // 输出返回结果 92 | func (this *Response)Write(b []byte) { 93 | 94 | _, e := this.Context.Response().Write(b) 95 | if e != nil { 96 | print(e.Error()) 97 | } 98 | } 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /tool.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Tool 工具 2 | // 提供便捷的方法进行类型断言转换,打印当前时间,获取类型 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况:无依赖 5 | 6 | package phalgo 7 | 8 | import ( 9 | "time" 10 | "fmt" 11 | "reflect" 12 | "net/url" 13 | "os" 14 | "strings" 15 | "path/filepath" 16 | "strconv" 17 | ) 18 | 19 | //当前项目根目录 20 | var API_ROOT string 21 | 22 | // 打印当前时间 23 | func PrintTime(s string) { 24 | 25 | t := time.Now() 26 | fmt.Printf(s) 27 | fmt.Println(t) 28 | } 29 | 30 | // 打印出接口的时间类型 31 | func PrintType(j interface{}) { 32 | 33 | fmt.Println(reflect.TypeOf(j)) 34 | } 35 | 36 | //------------------------------------------------类型互转-------------------- 37 | 38 | func IntTurnString(i int) string { 39 | return strconv.Itoa(i) 40 | } 41 | 42 | //------------------------------------------------以下都是从接口类型进行类型断言转换--------------------- 43 | 44 | // 从接口类型转换到[]byte 45 | func TurnByte(i interface{}) []byte { 46 | 47 | j, p := i.([]byte) 48 | if p { 49 | return j 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // 从接口类型转换到map[string]interface{} 56 | func TurnMapStringInterface(i interface{}) map[string]interface{} { 57 | 58 | j, p := i.(map[string]interface{}) 59 | if p { 60 | return j 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // 从接口类型转换到String 67 | func TurnString(i interface{}) string { 68 | 69 | j, p := i.(string) 70 | if p { 71 | return j 72 | } 73 | 74 | return "" 75 | } 76 | 77 | // 从接口类型转换到Int 78 | func TurnInt(i interface{}) int { 79 | 80 | j, p := i.(int) 81 | if p { 82 | return j 83 | } 84 | 85 | return 0 86 | } 87 | 88 | // 从接口类型转换到Int64 89 | func TurnInt64(i interface{}) int64 { 90 | 91 | j, p := i.(int64) 92 | if p { 93 | return j 94 | } 95 | 96 | return 0 97 | } 98 | 99 | // 从接口类型转换到int64返回int类型 100 | func Int64TurnInt(i interface{}) int { 101 | 102 | j, p := i.(int64) 103 | if p { 104 | return int(j) 105 | } 106 | 107 | return 0 108 | } 109 | 110 | // 从接口类型转换到Float64 111 | func TurnFloat64(i interface{}) float64 { 112 | 113 | j, p := i.(float64) 114 | if p { 115 | return j 116 | } 117 | 118 | return 0 119 | } 120 | 121 | // 从接口类型转换到接口切片 122 | func TurnSlice(i interface{}) []interface{} { 123 | 124 | j, p := i.([]interface{}) 125 | if p { 126 | return j 127 | } 128 | 129 | return nil 130 | } 131 | 132 | //---------------------urlcode 133 | 134 | // URL编码 135 | func UrlEncode(urls string) (string, error) { 136 | 137 | //UrlEnCode编码 138 | urlStr, err := url.Parse(urls) 139 | if err != nil { 140 | return "", err 141 | } 142 | 143 | return urlStr.RequestURI(), nil 144 | } 145 | 146 | // URL解码 147 | func UrlDecode(urls string) (string, error) { 148 | 149 | //UrlEnCode解码 150 | urlStr, err := url.Parse(urls) 151 | if err != nil { 152 | return "", err 153 | } 154 | 155 | return urlStr.Path, nil 156 | } 157 | 158 | // 获取项目路径 159 | func GetPath() string { 160 | 161 | if API_ROOT != "" { 162 | return API_ROOT 163 | } 164 | 165 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 166 | if err != nil { 167 | print(err.Error()) 168 | } 169 | 170 | API_ROOT = strings.Replace(dir, "\\", "/", -1) 171 | return API_ROOT 172 | } 173 | 174 | //判断文件目录否存在 175 | func IsDirExists(path string) bool { 176 | fi, err := os.Stat(path) 177 | 178 | if err != nil { 179 | return os.IsExist(err) 180 | } else { 181 | return fi.IsDir() 182 | } 183 | 184 | } 185 | 186 | //创建文件 187 | func MkdirFile(path string) error { 188 | 189 | err := os.Mkdir(path, os.ModePerm) //在当前目录下生成md目录 190 | if err != nil { 191 | return err 192 | } 193 | return nil 194 | } 195 | -------------------------------------------------------------------------------- /validation/README.md: -------------------------------------------------------------------------------- 1 | validation 2 | ============== 3 | 4 | validation is a form validation for a data validation and error collecting using Go. 5 | 6 | ## Installation and tests 7 | 8 | Install: 9 | 10 | go get github.com/astaxie/beego/validation 11 | 12 | Test: 13 | 14 | go test github.com/astaxie/beego/validation 15 | 16 | ## Example 17 | 18 | Direct Use: 19 | 20 | import ( 21 | "github.com/astaxie/beego/validation" 22 | "log" 23 | ) 24 | 25 | type User struct { 26 | Name string 27 | Age int 28 | } 29 | 30 | func main() { 31 | u := User{"man", 40} 32 | valid := validation.Validation{} 33 | valid.Required(u.Name, "name") 34 | valid.MaxSize(u.Name, 15, "nameMax") 35 | valid.Range(u.Age, 0, 140, "age") 36 | if valid.HasErrors() { 37 | // validation does not pass 38 | // print invalid message 39 | for _, err := range valid.Errors { 40 | log.Println(err.Key, err.Message) 41 | } 42 | } 43 | // or use like this 44 | if v := valid.Max(u.Age, 140, "ageMax"); !v.Ok { 45 | log.Println(v.Error.Key, v.Error.Message) 46 | } 47 | } 48 | 49 | Struct Tag Use: 50 | 51 | import ( 52 | "github.com/astaxie/beego/validation" 53 | ) 54 | 55 | // validation function follow with "valid" tag 56 | // functions divide with ";" 57 | // parameters in parentheses "()" and divide with "," 58 | // Match function's pattern string must in "//" 59 | type user struct { 60 | Id int 61 | Name string `valid:"Required;Match(/^(test)?\\w*@;com$/)"` 62 | Age int `valid:"Required;Range(1, 140)"` 63 | } 64 | 65 | func main() { 66 | valid := validation.Validation{} 67 | u := user{Name: "test", Age: 40} 68 | b, err := valid.Valid(u) 69 | if err != nil { 70 | // handle error 71 | } 72 | if !b { 73 | // validation does not pass 74 | // blabla... 75 | } 76 | } 77 | 78 | Use custom function: 79 | 80 | import ( 81 | "github.com/astaxie/beego/validation" 82 | ) 83 | 84 | type user struct { 85 | Id int 86 | Name string `valid:"Required;IsMe"` 87 | Age int `valid:"Required;Range(1, 140)"` 88 | } 89 | 90 | func IsMe(v *validation.Validation, obj interface{}, key string) { 91 | name, ok:= obj.(string) 92 | if !ok { 93 | // wrong use case? 94 | return 95 | } 96 | 97 | if name != "me" { 98 | // valid false 99 | v.SetError("Name", "is not me!") 100 | } 101 | } 102 | 103 | func main() { 104 | valid := validation.Validation{} 105 | if err := validation.AddCustomFunc("IsMe", IsMe); err != nil { 106 | // hadle error 107 | } 108 | u := user{Name: "test", Age: 40} 109 | b, err := valid.Valid(u) 110 | if err != nil { 111 | // handle error 112 | } 113 | if !b { 114 | // validation does not pass 115 | // blabla... 116 | } 117 | } 118 | 119 | Struct Tag Functions: 120 | 121 | Required 122 | Min(min int) 123 | Max(max int) 124 | Range(min, max int) 125 | MinSize(min int) 126 | MaxSize(max int) 127 | Length(length int) 128 | Alpha 129 | Numeric 130 | AlphaNumeric 131 | Match(pattern string) 132 | AlphaDash 133 | Email 134 | IP 135 | Base64 136 | Mobile 137 | Tel 138 | Phone 139 | ZipCode 140 | 141 | 142 | ## LICENSE 143 | 144 | BSD License http://creativecommons.org/licenses/BSD/ 145 | -------------------------------------------------------------------------------- /validation/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "regexp" 21 | "strconv" 22 | "strings" 23 | ) 24 | 25 | const ( 26 | // ValidTag struct tag 27 | ValidTag = "valid" 28 | ) 29 | 30 | var ( 31 | // key: function name 32 | // value: the number of parameters 33 | funcs = make(Funcs) 34 | 35 | // doesn't belong to validation functions 36 | unFuncs = map[string]bool{ 37 | "Clear": true, 38 | "HasErrors": true, 39 | "ErrorMap": true, 40 | "Error": true, 41 | "apply": true, 42 | "Check": true, 43 | "Valid": true, 44 | "NoMatch": true, 45 | } 46 | ) 47 | 48 | func init() { 49 | v := &Validation{} 50 | t := reflect.TypeOf(v) 51 | for i := 0; i < t.NumMethod(); i++ { 52 | m := t.Method(i) 53 | if !unFuncs[m.Name] { 54 | funcs[m.Name] = m.Func 55 | } 56 | } 57 | } 58 | 59 | // CustomFunc is for custom validate function 60 | type CustomFunc func(v *Validation, obj interface{}, key string) 61 | 62 | // AddCustomFunc Add a custom function to validation 63 | // The name can not be: 64 | // Clear 65 | // HasErrors 66 | // ErrorMap 67 | // Error 68 | // Check 69 | // Valid 70 | // NoMatch 71 | // If the name is same with exists function, it will replace the origin valid function 72 | func AddCustomFunc(name string, f CustomFunc) error { 73 | if unFuncs[name] { 74 | return fmt.Errorf("invalid function name: %s", name) 75 | } 76 | 77 | funcs[name] = reflect.ValueOf(f) 78 | return nil 79 | } 80 | 81 | // ValidFunc Valid function type 82 | type ValidFunc struct { 83 | Name string 84 | Params []interface{} 85 | } 86 | 87 | // Funcs Validate function map 88 | type Funcs map[string]reflect.Value 89 | 90 | // Call validate values with named type string 91 | func (f Funcs) Call(name string, params ...interface{}) (result []reflect.Value, err error) { 92 | defer func() { 93 | if r := recover(); r != nil { 94 | err = fmt.Errorf("%v", r) 95 | } 96 | }() 97 | if _, ok := f[name]; !ok { 98 | err = fmt.Errorf("%s does not exist", name) 99 | return 100 | } 101 | if len(params) != f[name].Type().NumIn() { 102 | err = fmt.Errorf("The number of params is not adapted") 103 | return 104 | } 105 | in := make([]reflect.Value, len(params)) 106 | for k, param := range params { 107 | in[k] = reflect.ValueOf(param) 108 | } 109 | result = f[name].Call(in) 110 | return 111 | } 112 | 113 | func isStruct(t reflect.Type) bool { 114 | return t.Kind() == reflect.Struct 115 | } 116 | 117 | func isStructPtr(t reflect.Type) bool { 118 | return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct 119 | } 120 | 121 | func getValidFuncs(f reflect.StructField) (vfs []ValidFunc, err error) { 122 | tag := f.Tag.Get(ValidTag) 123 | if len(tag) == 0 { 124 | return 125 | } 126 | if vfs, tag, err = getRegFuncs(tag, f.Name); err != nil { 127 | return 128 | } 129 | fs := strings.Split(tag, ";") 130 | for _, vfunc := range fs { 131 | var vf ValidFunc 132 | if len(vfunc) == 0 { 133 | continue 134 | } 135 | vf, err = parseFunc(vfunc, f.Name) 136 | if err != nil { 137 | return 138 | } 139 | vfs = append(vfs, vf) 140 | } 141 | return 142 | } 143 | 144 | // Get Match function 145 | // May be get NoMatch function in the future 146 | func getRegFuncs(tag, key string) (vfs []ValidFunc, str string, err error) { 147 | tag = strings.TrimSpace(tag) 148 | index := strings.Index(tag, "Match(/") 149 | if index == -1 { 150 | str = tag 151 | return 152 | } 153 | end := strings.LastIndex(tag, "/)") 154 | if end < index { 155 | err = fmt.Errorf("invalid Match function") 156 | return 157 | } 158 | reg, err := regexp.Compile(tag[index+len("Match(/") : end]) 159 | if err != nil { 160 | return 161 | } 162 | vfs = []ValidFunc{{"Match", []interface{}{reg, key + ".Match"}}} 163 | str = strings.TrimSpace(tag[:index]) + strings.TrimSpace(tag[end+len("/)"):]) 164 | return 165 | } 166 | 167 | func parseFunc(vfunc, key string) (v ValidFunc, err error) { 168 | defer func() { 169 | if r := recover(); r != nil { 170 | err = fmt.Errorf("%v", r) 171 | } 172 | }() 173 | 174 | vfunc = strings.TrimSpace(vfunc) 175 | start := strings.Index(vfunc, "(") 176 | var num int 177 | 178 | // doesn't need parameter valid function 179 | if start == -1 { 180 | if num, err = numIn(vfunc); err != nil { 181 | return 182 | } 183 | if num != 0 { 184 | err = fmt.Errorf("%s require %d parameters", vfunc, num) 185 | return 186 | } 187 | v = ValidFunc{vfunc, []interface{}{key + "." + vfunc}} 188 | return 189 | } 190 | 191 | end := strings.Index(vfunc, ")") 192 | if end == -1 { 193 | err = fmt.Errorf("invalid valid function") 194 | return 195 | } 196 | 197 | name := strings.TrimSpace(vfunc[:start]) 198 | if num, err = numIn(name); err != nil { 199 | return 200 | } 201 | 202 | params := strings.Split(vfunc[start+1:end], ",") 203 | // the num of param must be equal 204 | if num != len(params) { 205 | err = fmt.Errorf("%s require %d parameters", name, num) 206 | return 207 | } 208 | 209 | tParams, err := trim(name, key+"."+name, params) 210 | if err != nil { 211 | return 212 | } 213 | v = ValidFunc{name, tParams} 214 | return 215 | } 216 | 217 | func numIn(name string) (num int, err error) { 218 | fn, ok := funcs[name] 219 | if !ok { 220 | err = fmt.Errorf("doesn't exsits %s valid function", name) 221 | return 222 | } 223 | // sub *Validation obj and key 224 | num = fn.Type().NumIn() - 3 225 | return 226 | } 227 | 228 | func trim(name, key string, s []string) (ts []interface{}, err error) { 229 | ts = make([]interface{}, len(s), len(s)+1) 230 | fn, ok := funcs[name] 231 | if !ok { 232 | err = fmt.Errorf("doesn't exsits %s valid function", name) 233 | return 234 | } 235 | for i := 0; i < len(s); i++ { 236 | var param interface{} 237 | // skip *Validation and obj params 238 | if param, err = parseParam(fn.Type().In(i+2), strings.TrimSpace(s[i])); err != nil { 239 | return 240 | } 241 | ts[i] = param 242 | } 243 | ts = append(ts, key) 244 | return 245 | } 246 | 247 | // modify the parameters's type to adapt the function input parameters' type 248 | func parseParam(t reflect.Type, s string) (i interface{}, err error) { 249 | switch t.Kind() { 250 | case reflect.Int: 251 | i, err = strconv.Atoi(s) 252 | case reflect.String: 253 | i = s 254 | case reflect.Ptr: 255 | if t.Elem().String() != "regexp.Regexp" { 256 | err = fmt.Errorf("does not support %s", t.Elem().String()) 257 | return 258 | } 259 | i, err = regexp.Compile(s) 260 | default: 261 | err = fmt.Errorf("does not support %s", t.Kind().String()) 262 | } 263 | return 264 | } 265 | 266 | func mergeParam(v *Validation, obj interface{}, params []interface{}) []interface{} { 267 | return append([]interface{}{v, obj}, params...) 268 | } 269 | -------------------------------------------------------------------------------- /validation/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | ) 21 | 22 | type user struct { 23 | ID int 24 | Tag string `valid:"Maxx(aa)"` 25 | Name string `valid:"Required;"` 26 | Age int `valid:"Required;Range(1, 140)"` 27 | match string `valid:"Required; Match(/^(test)?\\w*@(/test/);com$/);Max(2)"` 28 | } 29 | 30 | func TestGetValidFuncs(t *testing.T) { 31 | u := user{Name: "test", Age: 1} 32 | tf := reflect.TypeOf(u) 33 | var vfs []ValidFunc 34 | var err error 35 | 36 | f, _ := tf.FieldByName("ID") 37 | if vfs, err = getValidFuncs(f); err != nil { 38 | t.Fatal(err) 39 | } 40 | if len(vfs) != 0 { 41 | t.Fatal("should get none ValidFunc") 42 | } 43 | 44 | f, _ = tf.FieldByName("Tag") 45 | if vfs, err = getValidFuncs(f); err.Error() != "doesn't exsits Maxx valid function" { 46 | t.Fatal(err) 47 | } 48 | 49 | f, _ = tf.FieldByName("Name") 50 | if vfs, err = getValidFuncs(f); err != nil { 51 | t.Fatal(err) 52 | } 53 | if len(vfs) != 1 { 54 | t.Fatal("should get 1 ValidFunc") 55 | } 56 | if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 { 57 | t.Error("Required funcs should be got") 58 | } 59 | 60 | f, _ = tf.FieldByName("Age") 61 | if vfs, err = getValidFuncs(f); err != nil { 62 | t.Fatal(err) 63 | } 64 | if len(vfs) != 2 { 65 | t.Fatal("should get 2 ValidFunc") 66 | } 67 | if vfs[0].Name != "Required" && len(vfs[0].Params) != 0 { 68 | t.Error("Required funcs should be got") 69 | } 70 | if vfs[1].Name != "Range" && len(vfs[1].Params) != 2 { 71 | t.Error("Range funcs should be got") 72 | } 73 | 74 | f, _ = tf.FieldByName("match") 75 | if vfs, err = getValidFuncs(f); err != nil { 76 | t.Fatal(err) 77 | } 78 | if len(vfs) != 3 { 79 | t.Fatal("should get 3 ValidFunc but now is", len(vfs)) 80 | } 81 | } 82 | 83 | func TestCall(t *testing.T) { 84 | u := user{Name: "test", Age: 180} 85 | tf := reflect.TypeOf(u) 86 | var vfs []ValidFunc 87 | var err error 88 | f, _ := tf.FieldByName("Age") 89 | if vfs, err = getValidFuncs(f); err != nil { 90 | t.Fatal(err) 91 | } 92 | valid := &Validation{} 93 | vfs[1].Params = append([]interface{}{valid, u.Age}, vfs[1].Params...) 94 | if _, err = funcs.Call(vfs[1].Name, vfs[1].Params...); err != nil { 95 | t.Fatal(err) 96 | } 97 | if len(valid.Errors) != 1 { 98 | t.Error("age out of range should be has an error") 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /validation/validation.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package validation for validations 16 | // 17 | // import ( 18 | // "github.com/astaxie/beego/validation" 19 | // "log" 20 | // ) 21 | // 22 | // type User struct { 23 | // Name string 24 | // Age int 25 | // } 26 | // 27 | // func main() { 28 | // u := User{"man", 40} 29 | // valid := validation.Validation{} 30 | // valid.Required(u.Name, "name") 31 | // valid.MaxSize(u.Name, 15, "nameMax") 32 | // valid.Range(u.Age, 0, 140, "age") 33 | // if valid.HasErrors() { 34 | // // validation does not pass 35 | // // print invalid message 36 | // for _, err := range valid.Errors { 37 | // log.Println(err.Key, err.Message) 38 | // } 39 | // } 40 | // // or use like this 41 | // if v := valid.Max(u.Age, 140, "ageMax"); !v.Ok { 42 | // log.Println(v.Error.Key, v.Error.Message) 43 | // } 44 | // } 45 | // 46 | // more info: http://beego.me/docs/mvc/controller/validation.md 47 | package validation 48 | 49 | import ( 50 | "fmt" 51 | "reflect" 52 | "regexp" 53 | "strings" 54 | ) 55 | 56 | // ValidFormer valid interface 57 | type ValidFormer interface { 58 | Valid(*Validation) 59 | } 60 | 61 | // Error show the error 62 | type Error struct { 63 | Message, Key, Name, Field, Tmpl string 64 | Value interface{} 65 | LimitValue interface{} 66 | } 67 | 68 | // String Returns the Message. 69 | func (e *Error) String() string { 70 | if e == nil { 71 | return "" 72 | } 73 | return e.Message 74 | } 75 | 76 | // Result is returned from every validation method. 77 | // It provides an indication of success, and a pointer to the Error (if any). 78 | type Result struct { 79 | Error *Error 80 | Ok bool 81 | } 82 | 83 | // Key Get Result by given key string. 84 | func (r *Result) Key(key string) *Result { 85 | if r.Error != nil { 86 | r.Error.Key = key 87 | } 88 | return r 89 | } 90 | 91 | // Message Set Result message by string or format string with args 92 | func (r *Result) Message(message string, args ...interface{}) *Result { 93 | if r.Error != nil { 94 | if len(args) == 0 { 95 | r.Error.Message = message 96 | } else { 97 | r.Error.Message = fmt.Sprintf(message, args...) 98 | } 99 | } 100 | return r 101 | } 102 | 103 | // A Validation context manages data validation and error messages. 104 | type Validation struct { 105 | Errors []*Error 106 | ErrorsMap map[string]*Error 107 | } 108 | 109 | // Clear Clean all ValidationError. 110 | func (v *Validation) Clear() { 111 | v.Errors = []*Error{} 112 | v.ErrorsMap = nil 113 | } 114 | 115 | // HasErrors Has ValidationError nor not. 116 | func (v *Validation) HasErrors() bool { 117 | return len(v.Errors) > 0 118 | } 119 | 120 | // ErrorMap Return the errors mapped by key. 121 | // If there are multiple validation errors associated with a single key, the 122 | // first one "wins". (Typically the first validation will be the more basic). 123 | func (v *Validation) ErrorMap() map[string]*Error { 124 | return v.ErrorsMap 125 | } 126 | 127 | // Error Add an error to the validation context. 128 | func (v *Validation) Error(message string, args ...interface{}) *Result { 129 | result := (&Result{ 130 | Ok: false, 131 | Error: &Error{}, 132 | }).Message(message, args...) 133 | v.Errors = append(v.Errors, result.Error) 134 | return result 135 | } 136 | 137 | // Required Test that the argument is non-nil and non-empty (if string or list) 138 | func (v *Validation) Required(obj interface{}, key string) *Result { 139 | return v.apply(Required{key}, obj) 140 | } 141 | 142 | // Min Test that the obj is greater than min if obj's type is int 143 | func (v *Validation) Min(obj interface{}, min int, key string) *Result { 144 | return v.apply(Min{min, key}, obj) 145 | } 146 | 147 | // Max Test that the obj is less than max if obj's type is int 148 | func (v *Validation) Max(obj interface{}, max int, key string) *Result { 149 | return v.apply(Max{max, key}, obj) 150 | } 151 | 152 | // Range Test that the obj is between mni and max if obj's type is int 153 | func (v *Validation) Range(obj interface{}, min, max int, key string) *Result { 154 | return v.apply(Range{Min{Min: min}, Max{Max: max}, key}, obj) 155 | } 156 | 157 | // MinSize Test that the obj is longer than min size if type is string or slice 158 | func (v *Validation) MinSize(obj interface{}, min int, key string) *Result { 159 | return v.apply(MinSize{min, key}, obj) 160 | } 161 | 162 | // MaxSize Test that the obj is shorter than max size if type is string or slice 163 | func (v *Validation) MaxSize(obj interface{}, max int, key string) *Result { 164 | return v.apply(MaxSize{max, key}, obj) 165 | } 166 | 167 | // Length Test that the obj is same length to n if type is string or slice 168 | func (v *Validation) Length(obj interface{}, n int, key string) *Result { 169 | return v.apply(Length{n, key}, obj) 170 | } 171 | 172 | // Alpha Test that the obj is [a-zA-Z] if type is string 173 | func (v *Validation) Alpha(obj interface{}, key string) *Result { 174 | return v.apply(Alpha{key}, obj) 175 | } 176 | 177 | // Numeric Test that the obj is [0-9] if type is string 178 | func (v *Validation) Numeric(obj interface{}, key string) *Result { 179 | return v.apply(Numeric{key}, obj) 180 | } 181 | 182 | // AlphaNumeric Test that the obj is [0-9a-zA-Z] if type is string 183 | func (v *Validation) AlphaNumeric(obj interface{}, key string) *Result { 184 | return v.apply(AlphaNumeric{key}, obj) 185 | } 186 | 187 | // Match Test that the obj matches regexp if type is string 188 | func (v *Validation) Match(obj interface{}, regex *regexp.Regexp, key string) *Result { 189 | return v.apply(Match{regex, key}, obj) 190 | } 191 | 192 | // NoMatch Test that the obj doesn't match regexp if type is string 193 | func (v *Validation) NoMatch(obj interface{}, regex *regexp.Regexp, key string) *Result { 194 | return v.apply(NoMatch{Match{Regexp: regex}, key}, obj) 195 | } 196 | 197 | // AlphaDash Test that the obj is [0-9a-zA-Z_-] if type is string 198 | func (v *Validation) AlphaDash(obj interface{}, key string) *Result { 199 | return v.apply(AlphaDash{NoMatch{Match: Match{Regexp: alphaDashPattern}}, key}, obj) 200 | } 201 | 202 | // Email Test that the obj is email address if type is string 203 | func (v *Validation) Email(obj interface{}, key string) *Result { 204 | return v.apply(Email{Match{Regexp: emailPattern}, key}, obj) 205 | } 206 | 207 | // IP Test that the obj is IP address if type is string 208 | func (v *Validation) IP(obj interface{}, key string) *Result { 209 | return v.apply(IP{Match{Regexp: ipPattern}, key}, obj) 210 | } 211 | 212 | // Base64 Test that the obj is base64 encoded if type is string 213 | func (v *Validation) Base64(obj interface{}, key string) *Result { 214 | return v.apply(Base64{Match{Regexp: base64Pattern}, key}, obj) 215 | } 216 | 217 | // Mobile Test that the obj is chinese mobile number if type is string 218 | func (v *Validation) Mobile(obj interface{}, key string) *Result { 219 | return v.apply(Mobile{Match{Regexp: mobilePattern}, key}, obj) 220 | } 221 | 222 | // Tel Test that the obj is chinese telephone number if type is string 223 | func (v *Validation) Tel(obj interface{}, key string) *Result { 224 | return v.apply(Tel{Match{Regexp: telPattern}, key}, obj) 225 | } 226 | 227 | // Phone Test that the obj is chinese mobile or telephone number if type is string 228 | func (v *Validation) Phone(obj interface{}, key string) *Result { 229 | return v.apply(Phone{Mobile{Match: Match{Regexp: mobilePattern}}, 230 | Tel{Match: Match{Regexp: telPattern}}, key}, obj) 231 | } 232 | 233 | // ZipCode Test that the obj is chinese zip code if type is string 234 | func (v *Validation) ZipCode(obj interface{}, key string) *Result { 235 | return v.apply(ZipCode{Match{Regexp: zipCodePattern}, key}, obj) 236 | } 237 | 238 | func (v *Validation) apply(chk Validator, obj interface{}) *Result { 239 | if chk.IsSatisfied(obj) { 240 | return &Result{Ok: true} 241 | } 242 | 243 | // Add the error to the validation context. 244 | key := chk.GetKey() 245 | Name := key 246 | Field := "" 247 | 248 | parts := strings.Split(key, ".") 249 | if len(parts) == 2 { 250 | Field = parts[0] 251 | Name = parts[1] 252 | } 253 | 254 | err := &Error{ 255 | Message: chk.DefaultMessage(), 256 | Key: key, 257 | Name: Name, 258 | Field: Field, 259 | Value: obj, 260 | Tmpl: MessageTmpls[Name], 261 | LimitValue: chk.GetLimitValue(), 262 | } 263 | v.setError(err) 264 | 265 | // Also return it in the result. 266 | return &Result{ 267 | Ok: false, 268 | Error: err, 269 | } 270 | } 271 | 272 | func (v *Validation) setError(err *Error) { 273 | v.Errors = append(v.Errors, err) 274 | if v.ErrorsMap == nil { 275 | v.ErrorsMap = make(map[string]*Error) 276 | } 277 | if _, ok := v.ErrorsMap[err.Field]; !ok { 278 | v.ErrorsMap[err.Field] = err 279 | } 280 | } 281 | 282 | // SetError Set error message for one field in ValidationError 283 | func (v *Validation) SetError(fieldName string, errMsg string) *Error { 284 | err := &Error{Key: fieldName, Field: fieldName, Tmpl: errMsg, Message: errMsg} 285 | v.setError(err) 286 | return err 287 | } 288 | 289 | // Check Apply a group of validators to a field, in order, and return the 290 | // ValidationResult from the first one that fails, or the last one that 291 | // succeeds. 292 | func (v *Validation) Check(obj interface{}, checks ...Validator) *Result { 293 | var result *Result 294 | for _, check := range checks { 295 | result = v.apply(check, obj) 296 | if !result.Ok { 297 | return result 298 | } 299 | } 300 | return result 301 | } 302 | 303 | // Valid Validate a struct. 304 | // the obj parameter must be a struct or a struct pointer 305 | func (v *Validation) Valid(obj interface{}) (b bool, err error) { 306 | objT := reflect.TypeOf(obj) 307 | objV := reflect.ValueOf(obj) 308 | switch { 309 | case isStruct(objT): 310 | case isStructPtr(objT): 311 | objT = objT.Elem() 312 | objV = objV.Elem() 313 | default: 314 | err = fmt.Errorf("%v must be a struct or a struct pointer", obj) 315 | return 316 | } 317 | 318 | for i := 0; i < objT.NumField(); i++ { 319 | var vfs []ValidFunc 320 | if vfs, err = getValidFuncs(objT.Field(i)); err != nil { 321 | return 322 | } 323 | for _, vf := range vfs { 324 | if _, err = funcs.Call(vf.Name, 325 | mergeParam(v, objV.Field(i).Interface(), vf.Params)...); err != nil { 326 | return 327 | } 328 | } 329 | } 330 | 331 | if !v.HasErrors() { 332 | if form, ok := obj.(ValidFormer); ok { 333 | form.Valid(v) 334 | } 335 | } 336 | 337 | return !v.HasErrors(), nil 338 | } 339 | 340 | // RecursiveValid Recursively validate a struct. 341 | // Step1: Validate by v.Valid 342 | // Step2: If pass on step1, then reflect obj's fields 343 | // Step3: Do the Recursively validation to all struct or struct pointer fields 344 | func (v *Validation) RecursiveValid(objc interface{}) (bool, error) { 345 | //Step 1: validate obj itself firstly 346 | // fails if objc is not struct 347 | pass, err := v.Valid(objc) 348 | if err != nil || false == pass { 349 | return pass, err // Stop recursive validation 350 | } 351 | // Step 2: Validate struct's struct fields 352 | objT := reflect.TypeOf(objc) 353 | objV := reflect.ValueOf(objc) 354 | 355 | if isStructPtr(objT) { 356 | objT = objT.Elem() 357 | objV = objV.Elem() 358 | } 359 | 360 | for i := 0; i < objT.NumField(); i++ { 361 | 362 | t := objT.Field(i).Type 363 | 364 | // Recursive applies to struct or pointer to structs fields 365 | if isStruct(t) || isStructPtr(t) { 366 | // Step 3: do the recursive validation 367 | // Only valid the Public field recursively 368 | if objV.Field(i).CanInterface() { 369 | pass, err = v.RecursiveValid(objV.Field(i).Interface()) 370 | } 371 | } 372 | } 373 | return pass, err 374 | } 375 | -------------------------------------------------------------------------------- /validation/validation_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "regexp" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | func TestRequired(t *testing.T) { 24 | valid := Validation{} 25 | 26 | if valid.Required(nil, "nil").Ok { 27 | t.Error("nil object should be false") 28 | } 29 | if !valid.Required(true, "bool").Ok { 30 | t.Error("Bool value should always return true") 31 | } 32 | if !valid.Required(false, "bool").Ok { 33 | t.Error("Bool value should always return true") 34 | } 35 | if valid.Required("", "string").Ok { 36 | t.Error("\"'\" string should be false") 37 | } 38 | if !valid.Required("astaxie", "string").Ok { 39 | t.Error("string should be true") 40 | } 41 | if valid.Required(0, "zero").Ok { 42 | t.Error("Integer should not be equal 0") 43 | } 44 | if !valid.Required(1, "int").Ok { 45 | t.Error("Integer except 0 should be true") 46 | } 47 | if !valid.Required(time.Now(), "time").Ok { 48 | t.Error("time should be true") 49 | } 50 | if valid.Required([]string{}, "emptySlice").Ok { 51 | t.Error("empty slice should be false") 52 | } 53 | if !valid.Required([]interface{}{"ok"}, "slice").Ok { 54 | t.Error("slice should be true") 55 | } 56 | } 57 | 58 | func TestMin(t *testing.T) { 59 | valid := Validation{} 60 | 61 | if valid.Min(-1, 0, "min0").Ok { 62 | t.Error("-1 is less than the minimum value of 0 should be false") 63 | } 64 | if !valid.Min(1, 0, "min0").Ok { 65 | t.Error("1 is greater or equal than the minimum value of 0 should be true") 66 | } 67 | } 68 | 69 | func TestMax(t *testing.T) { 70 | valid := Validation{} 71 | 72 | if valid.Max(1, 0, "max0").Ok { 73 | t.Error("1 is greater than the minimum value of 0 should be false") 74 | } 75 | if !valid.Max(-1, 0, "max0").Ok { 76 | t.Error("-1 is less or equal than the maximum value of 0 should be true") 77 | } 78 | } 79 | 80 | func TestRange(t *testing.T) { 81 | valid := Validation{} 82 | 83 | if valid.Range(-1, 0, 1, "range0_1").Ok { 84 | t.Error("-1 is between 0 and 1 should be false") 85 | } 86 | if !valid.Range(1, 0, 1, "range0_1").Ok { 87 | t.Error("1 is between 0 and 1 should be true") 88 | } 89 | } 90 | 91 | func TestMinSize(t *testing.T) { 92 | valid := Validation{} 93 | 94 | if valid.MinSize("", 1, "minSize1").Ok { 95 | t.Error("the length of \"\" is less than the minimum value of 1 should be false") 96 | } 97 | if !valid.MinSize("ok", 1, "minSize1").Ok { 98 | t.Error("the length of \"ok\" is greater or equal than the minimum value of 1 should be true") 99 | } 100 | if valid.MinSize([]string{}, 1, "minSize1").Ok { 101 | t.Error("the length of empty slice is less than the minimum value of 1 should be false") 102 | } 103 | if !valid.MinSize([]interface{}{"ok"}, 1, "minSize1").Ok { 104 | t.Error("the length of [\"ok\"] is greater or equal than the minimum value of 1 should be true") 105 | } 106 | } 107 | 108 | func TestMaxSize(t *testing.T) { 109 | valid := Validation{} 110 | 111 | if valid.MaxSize("ok", 1, "maxSize1").Ok { 112 | t.Error("the length of \"ok\" is greater than the maximum value of 1 should be false") 113 | } 114 | if !valid.MaxSize("", 1, "maxSize1").Ok { 115 | t.Error("the length of \"\" is less or equal than the maximum value of 1 should be true") 116 | } 117 | if valid.MaxSize([]interface{}{"ok", false}, 1, "maxSize1").Ok { 118 | t.Error("the length of [\"ok\", false] is greater than the maximum value of 1 should be false") 119 | } 120 | if !valid.MaxSize([]string{}, 1, "maxSize1").Ok { 121 | t.Error("the length of empty slice is less or equal than the maximum value of 1 should be true") 122 | } 123 | } 124 | 125 | func TestLength(t *testing.T) { 126 | valid := Validation{} 127 | 128 | if valid.Length("", 1, "length1").Ok { 129 | t.Error("the length of \"\" must equal 1 should be false") 130 | } 131 | if !valid.Length("1", 1, "length1").Ok { 132 | t.Error("the length of \"1\" must equal 1 should be true") 133 | } 134 | if valid.Length([]string{}, 1, "length1").Ok { 135 | t.Error("the length of empty slice must equal 1 should be false") 136 | } 137 | if !valid.Length([]interface{}{"ok"}, 1, "length1").Ok { 138 | t.Error("the length of [\"ok\"] must equal 1 should be true") 139 | } 140 | } 141 | 142 | func TestAlpha(t *testing.T) { 143 | valid := Validation{} 144 | 145 | if valid.Alpha("a,1-@ $", "alpha").Ok { 146 | t.Error("\"a,1-@ $\" are valid alpha characters should be false") 147 | } 148 | if !valid.Alpha("abCD", "alpha").Ok { 149 | t.Error("\"abCD\" are valid alpha characters should be true") 150 | } 151 | } 152 | 153 | func TestNumeric(t *testing.T) { 154 | valid := Validation{} 155 | 156 | if valid.Numeric("a,1-@ $", "numeric").Ok { 157 | t.Error("\"a,1-@ $\" are valid numeric characters should be false") 158 | } 159 | if !valid.Numeric("1234", "numeric").Ok { 160 | t.Error("\"1234\" are valid numeric characters should be true") 161 | } 162 | } 163 | 164 | func TestAlphaNumeric(t *testing.T) { 165 | valid := Validation{} 166 | 167 | if valid.AlphaNumeric("a,1-@ $", "alphaNumeric").Ok { 168 | t.Error("\"a,1-@ $\" are valid alpha or numeric characters should be false") 169 | } 170 | if !valid.AlphaNumeric("1234aB", "alphaNumeric").Ok { 171 | t.Error("\"1234aB\" are valid alpha or numeric characters should be true") 172 | } 173 | } 174 | 175 | func TestMatch(t *testing.T) { 176 | valid := Validation{} 177 | 178 | if valid.Match("suchuangji@gmail", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok { 179 | t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be false") 180 | } 181 | if !valid.Match("suchuangji@gmail.com", regexp.MustCompile("^\\w+@\\w+\\.\\w+$"), "match").Ok { 182 | t.Error("\"suchuangji@gmail\" match \"^\\w+@\\w+\\.\\w+$\" should be true") 183 | } 184 | } 185 | 186 | func TestNoMatch(t *testing.T) { 187 | valid := Validation{} 188 | 189 | if valid.NoMatch("123@gmail", regexp.MustCompile("[^\\w\\d]"), "nomatch").Ok { 190 | t.Error("\"123@gmail\" not match \"[^\\w\\d]\" should be false") 191 | } 192 | if !valid.NoMatch("123gmail", regexp.MustCompile("[^\\w\\d]"), "match").Ok { 193 | t.Error("\"123@gmail\" not match \"[^\\w\\d@]\" should be true") 194 | } 195 | } 196 | 197 | func TestAlphaDash(t *testing.T) { 198 | valid := Validation{} 199 | 200 | if valid.AlphaDash("a,1-@ $", "alphaDash").Ok { 201 | t.Error("\"a,1-@ $\" are valid alpha or numeric or dash(-_) characters should be false") 202 | } 203 | if !valid.AlphaDash("1234aB-_", "alphaDash").Ok { 204 | t.Error("\"1234aB\" are valid alpha or numeric or dash(-_) characters should be true") 205 | } 206 | } 207 | 208 | func TestEmail(t *testing.T) { 209 | valid := Validation{} 210 | 211 | if valid.Email("not@a email", "email").Ok { 212 | t.Error("\"not@a email\" is a valid email address should be false") 213 | } 214 | if !valid.Email("suchuangji@gmail.com", "email").Ok { 215 | t.Error("\"suchuangji@gmail.com\" is a valid email address should be true") 216 | } 217 | } 218 | 219 | func TestIP(t *testing.T) { 220 | valid := Validation{} 221 | 222 | if valid.IP("11.255.255.256", "IP").Ok { 223 | t.Error("\"11.255.255.256\" is a valid ip address should be false") 224 | } 225 | if !valid.IP("01.11.11.11", "IP").Ok { 226 | t.Error("\"suchuangji@gmail.com\" is a valid ip address should be true") 227 | } 228 | } 229 | 230 | func TestBase64(t *testing.T) { 231 | valid := Validation{} 232 | 233 | if valid.Base64("suchuangji@gmail.com", "base64").Ok { 234 | t.Error("\"suchuangji@gmail.com\" are a valid base64 characters should be false") 235 | } 236 | if !valid.Base64("c3VjaHVhbmdqaUBnbWFpbC5jb20=", "base64").Ok { 237 | t.Error("\"c3VjaHVhbmdqaUBnbWFpbC5jb20=\" are a valid base64 characters should be true") 238 | } 239 | } 240 | 241 | func TestMobile(t *testing.T) { 242 | valid := Validation{} 243 | 244 | if valid.Mobile("19800008888", "mobile").Ok { 245 | t.Error("\"19800008888\" is a valid mobile phone number should be false") 246 | } 247 | if !valid.Mobile("18800008888", "mobile").Ok { 248 | t.Error("\"18800008888\" is a valid mobile phone number should be true") 249 | } 250 | if !valid.Mobile("18000008888", "mobile").Ok { 251 | t.Error("\"18000008888\" is a valid mobile phone number should be true") 252 | } 253 | if !valid.Mobile("8618300008888", "mobile").Ok { 254 | t.Error("\"8618300008888\" is a valid mobile phone number should be true") 255 | } 256 | if !valid.Mobile("+8614700008888", "mobile").Ok { 257 | t.Error("\"+8614700008888\" is a valid mobile phone number should be true") 258 | } 259 | } 260 | 261 | func TestTel(t *testing.T) { 262 | valid := Validation{} 263 | 264 | if valid.Tel("222-00008888", "telephone").Ok { 265 | t.Error("\"222-00008888\" is a valid telephone number should be false") 266 | } 267 | if !valid.Tel("022-70008888", "telephone").Ok { 268 | t.Error("\"022-70008888\" is a valid telephone number should be true") 269 | } 270 | if !valid.Tel("02270008888", "telephone").Ok { 271 | t.Error("\"02270008888\" is a valid telephone number should be true") 272 | } 273 | if !valid.Tel("70008888", "telephone").Ok { 274 | t.Error("\"70008888\" is a valid telephone number should be true") 275 | } 276 | } 277 | 278 | func TestPhone(t *testing.T) { 279 | valid := Validation{} 280 | 281 | if valid.Phone("222-00008888", "phone").Ok { 282 | t.Error("\"222-00008888\" is a valid phone number should be false") 283 | } 284 | if !valid.Mobile("+8614700008888", "phone").Ok { 285 | t.Error("\"+8614700008888\" is a valid phone number should be true") 286 | } 287 | if !valid.Tel("02270008888", "phone").Ok { 288 | t.Error("\"02270008888\" is a valid phone number should be true") 289 | } 290 | } 291 | 292 | func TestZipCode(t *testing.T) { 293 | valid := Validation{} 294 | 295 | if valid.ZipCode("", "zipcode").Ok { 296 | t.Error("\"00008888\" is a valid zipcode should be false") 297 | } 298 | if !valid.ZipCode("536000", "zipcode").Ok { 299 | t.Error("\"536000\" is a valid zipcode should be true") 300 | } 301 | } 302 | 303 | func TestValid(t *testing.T) { 304 | type user struct { 305 | ID int 306 | Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` 307 | Age int `valid:"Required;Range(1, 140)"` 308 | } 309 | valid := Validation{} 310 | 311 | u := user{Name: "test@/test/;com", Age: 40} 312 | b, err := valid.Valid(u) 313 | if err != nil { 314 | t.Fatal(err) 315 | } 316 | if !b { 317 | t.Error("validation should be passed") 318 | } 319 | 320 | uptr := &user{Name: "test", Age: 40} 321 | valid.Clear() 322 | b, err = valid.Valid(uptr) 323 | if err != nil { 324 | t.Fatal(err) 325 | } 326 | if b { 327 | t.Error("validation should not be passed") 328 | } 329 | if len(valid.Errors) != 1 { 330 | t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) 331 | } 332 | if valid.Errors[0].Key != "Name.Match" { 333 | t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key) 334 | } 335 | 336 | u = user{Name: "test@/test/;com", Age: 180} 337 | valid.Clear() 338 | b, err = valid.Valid(u) 339 | if err != nil { 340 | t.Fatal(err) 341 | } 342 | if b { 343 | t.Error("validation should not be passed") 344 | } 345 | if len(valid.Errors) != 1 { 346 | t.Fatalf("valid errors len should be 1 but got %d", len(valid.Errors)) 347 | } 348 | if valid.Errors[0].Key != "Age.Range" { 349 | t.Errorf("Message key should be `Name.Match` but got %s", valid.Errors[0].Key) 350 | } 351 | } 352 | 353 | func TestRecursiveValid(t *testing.T) { 354 | type User struct { 355 | ID int 356 | Name string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` 357 | Age int `valid:"Required;Range(1, 140)"` 358 | } 359 | 360 | type AnonymouseUser struct { 361 | ID2 int 362 | Name2 string `valid:"Required;Match(/^(test)?\\w*@(/test/);com$/)"` 363 | Age2 int `valid:"Required;Range(1, 140)"` 364 | } 365 | 366 | type Account struct { 367 | Password string `valid:"Required"` 368 | U User 369 | AnonymouseUser 370 | } 371 | valid := Validation{} 372 | 373 | u := Account{Password: "abc123_", U: User{}} 374 | b, err := valid.RecursiveValid(u) 375 | if err != nil { 376 | t.Fatal(err) 377 | } 378 | if b { 379 | t.Error("validation should not be passed") 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /validation/validators.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validation 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "regexp" 21 | "time" 22 | "unicode/utf8" 23 | ) 24 | 25 | // MessageTmpls store commond validate template 26 | var MessageTmpls = map[string]string{ 27 | "Required": "Can not be empty", 28 | "Min": "Minimum is %d", 29 | "Max": "Maximum is %d", 30 | "Range": "Range is %d to %d", 31 | "MinSize": "Minimum size is %d", 32 | "MaxSize": "Maximum size is %d", 33 | "Length": "Required length is %d", 34 | "Alpha": "Must be valid alpha characters", 35 | "Numeric": "Must be valid numeric characters", 36 | "AlphaNumeric": "Must be valid alpha or numeric characters", 37 | "Match": "Must match %s", 38 | "NoMatch": "Must not match %s", 39 | "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", 40 | "Email": "Must be a valid email address", 41 | "IP": "Must be a valid ip address", 42 | "Base64": "Must be valid base64 characters", 43 | "Mobile": "Must be valid mobile number", 44 | "Tel": "Must be valid telephone number", 45 | "Phone": "Must be valid telephone or mobile phone number", 46 | "ZipCode": "Must be valid zipcode", 47 | } 48 | 49 | // SetDefaultMessage set default messages 50 | // if not set, the default messages are 51 | // "Required": "Can not be empty", 52 | // "Min": "Minimum is %d", 53 | // "Max": "Maximum is %d", 54 | // "Range": "Range is %d to %d", 55 | // "MinSize": "Minimum size is %d", 56 | // "MaxSize": "Maximum size is %d", 57 | // "Length": "Required length is %d", 58 | // "Alpha": "Must be valid alpha characters", 59 | // "Numeric": "Must be valid numeric characters", 60 | // "AlphaNumeric": "Must be valid alpha or numeric characters", 61 | // "Match": "Must match %s", 62 | // "NoMatch": "Must not match %s", 63 | // "AlphaDash": "Must be valid alpha or numeric or dash(-_) characters", 64 | // "Email": "Must be a valid email address", 65 | // "IP": "Must be a valid ip address", 66 | // "Base64": "Must be valid base64 characters", 67 | // "Mobile": "Must be valid mobile number", 68 | // "Tel": "Must be valid telephone number", 69 | // "Phone": "Must be valid telephone or mobile phone number", 70 | // "ZipCode": "Must be valid zipcode", 71 | func SetDefaultMessage(msg map[string]string) { 72 | if len(msg) == 0 { 73 | return 74 | } 75 | 76 | for name := range msg { 77 | MessageTmpls[name] = msg[name] 78 | } 79 | } 80 | 81 | // Validator interface 82 | type Validator interface { 83 | IsSatisfied(interface{}) bool 84 | DefaultMessage() string 85 | GetKey() string 86 | GetLimitValue() interface{} 87 | } 88 | 89 | // Required struct 90 | type Required struct { 91 | Key string 92 | } 93 | 94 | // IsSatisfied judge whether obj has value 95 | func (r Required) IsSatisfied(obj interface{}) bool { 96 | if obj == nil { 97 | return false 98 | } 99 | 100 | if str, ok := obj.(string); ok { 101 | return len(str) > 0 102 | } 103 | if _, ok := obj.(bool); ok { 104 | return true 105 | } 106 | if i, ok := obj.(int); ok { 107 | return i != 0 108 | } 109 | if i, ok := obj.(uint); ok { 110 | return i != 0 111 | } 112 | if i, ok := obj.(int8); ok { 113 | return i != 0 114 | } 115 | if i, ok := obj.(uint8); ok { 116 | return i != 0 117 | } 118 | if i, ok := obj.(int16); ok { 119 | return i != 0 120 | } 121 | if i, ok := obj.(uint16); ok { 122 | return i != 0 123 | } 124 | if i, ok := obj.(uint32); ok { 125 | return i != 0 126 | } 127 | if i, ok := obj.(int32); ok { 128 | return i != 0 129 | } 130 | if i, ok := obj.(int64); ok { 131 | return i != 0 132 | } 133 | if i, ok := obj.(uint64); ok { 134 | return i != 0 135 | } 136 | if t, ok := obj.(time.Time); ok { 137 | return !t.IsZero() 138 | } 139 | v := reflect.ValueOf(obj) 140 | if v.Kind() == reflect.Slice { 141 | return v.Len() > 0 142 | } 143 | return true 144 | } 145 | 146 | // DefaultMessage return the default error message 147 | func (r Required) DefaultMessage() string { 148 | return fmt.Sprint(MessageTmpls["Required"]) 149 | } 150 | 151 | // GetKey return the r.Key 152 | func (r Required) GetKey() string { 153 | return r.Key 154 | } 155 | 156 | // GetLimitValue return nil now 157 | func (r Required) GetLimitValue() interface{} { 158 | return nil 159 | } 160 | 161 | // Min check struct 162 | type Min struct { 163 | Min int 164 | Key string 165 | } 166 | 167 | // IsSatisfied judge whether obj is valid 168 | func (m Min) IsSatisfied(obj interface{}) bool { 169 | num, ok := obj.(int) 170 | if ok { 171 | return num >= m.Min 172 | } 173 | return false 174 | } 175 | 176 | // DefaultMessage return the default min error message 177 | func (m Min) DefaultMessage() string { 178 | return fmt.Sprintf(MessageTmpls["Min"], m.Min) 179 | } 180 | 181 | // GetKey return the m.Key 182 | func (m Min) GetKey() string { 183 | return m.Key 184 | } 185 | 186 | // GetLimitValue return the limit value, Min 187 | func (m Min) GetLimitValue() interface{} { 188 | return m.Min 189 | } 190 | 191 | // Max validate struct 192 | type Max struct { 193 | Max int 194 | Key string 195 | } 196 | 197 | // IsSatisfied judge whether obj is valid 198 | func (m Max) IsSatisfied(obj interface{}) bool { 199 | num, ok := obj.(int) 200 | if ok { 201 | return num <= m.Max 202 | } 203 | return false 204 | } 205 | 206 | // DefaultMessage return the default max error message 207 | func (m Max) DefaultMessage() string { 208 | return fmt.Sprintf(MessageTmpls["Max"], m.Max) 209 | } 210 | 211 | // GetKey return the m.Key 212 | func (m Max) GetKey() string { 213 | return m.Key 214 | } 215 | 216 | // GetLimitValue return the limit value, Max 217 | func (m Max) GetLimitValue() interface{} { 218 | return m.Max 219 | } 220 | 221 | // Range Requires an integer to be within Min, Max inclusive. 222 | type Range struct { 223 | Min 224 | Max 225 | Key string 226 | } 227 | 228 | // IsSatisfied judge whether obj is valid 229 | func (r Range) IsSatisfied(obj interface{}) bool { 230 | return r.Min.IsSatisfied(obj) && r.Max.IsSatisfied(obj) 231 | } 232 | 233 | // DefaultMessage return the default Range error message 234 | func (r Range) DefaultMessage() string { 235 | return fmt.Sprintf(MessageTmpls["Range"], r.Min.Min, r.Max.Max) 236 | } 237 | 238 | // GetKey return the m.Key 239 | func (r Range) GetKey() string { 240 | return r.Key 241 | } 242 | 243 | // GetLimitValue return the limit value, Max 244 | func (r Range) GetLimitValue() interface{} { 245 | return []int{r.Min.Min, r.Max.Max} 246 | } 247 | 248 | // MinSize Requires an array or string to be at least a given length. 249 | type MinSize struct { 250 | Min int 251 | Key string 252 | } 253 | 254 | // IsSatisfied judge whether obj is valid 255 | func (m MinSize) IsSatisfied(obj interface{}) bool { 256 | if str, ok := obj.(string); ok { 257 | return utf8.RuneCountInString(str) >= m.Min 258 | } 259 | v := reflect.ValueOf(obj) 260 | if v.Kind() == reflect.Slice { 261 | return v.Len() >= m.Min 262 | } 263 | return false 264 | } 265 | 266 | // DefaultMessage return the default MinSize error message 267 | func (m MinSize) DefaultMessage() string { 268 | return fmt.Sprintf(MessageTmpls["MinSize"], m.Min) 269 | } 270 | 271 | // GetKey return the m.Key 272 | func (m MinSize) GetKey() string { 273 | return m.Key 274 | } 275 | 276 | // GetLimitValue return the limit value 277 | func (m MinSize) GetLimitValue() interface{} { 278 | return m.Min 279 | } 280 | 281 | // MaxSize Requires an array or string to be at most a given length. 282 | type MaxSize struct { 283 | Max int 284 | Key string 285 | } 286 | 287 | // IsSatisfied judge whether obj is valid 288 | func (m MaxSize) IsSatisfied(obj interface{}) bool { 289 | if str, ok := obj.(string); ok { 290 | return utf8.RuneCountInString(str) <= m.Max 291 | } 292 | v := reflect.ValueOf(obj) 293 | if v.Kind() == reflect.Slice { 294 | return v.Len() <= m.Max 295 | } 296 | return false 297 | } 298 | 299 | // DefaultMessage return the default MaxSize error message 300 | func (m MaxSize) DefaultMessage() string { 301 | return fmt.Sprintf(MessageTmpls["MaxSize"], m.Max) 302 | } 303 | 304 | // GetKey return the m.Key 305 | func (m MaxSize) GetKey() string { 306 | return m.Key 307 | } 308 | 309 | // GetLimitValue return the limit value 310 | func (m MaxSize) GetLimitValue() interface{} { 311 | return m.Max 312 | } 313 | 314 | // Length Requires an array or string to be exactly a given length. 315 | type Length struct { 316 | N int 317 | Key string 318 | } 319 | 320 | // IsSatisfied judge whether obj is valid 321 | func (l Length) IsSatisfied(obj interface{}) bool { 322 | if str, ok := obj.(string); ok { 323 | return utf8.RuneCountInString(str) == l.N 324 | } 325 | v := reflect.ValueOf(obj) 326 | if v.Kind() == reflect.Slice { 327 | return v.Len() == l.N 328 | } 329 | return false 330 | } 331 | 332 | // DefaultMessage return the default Length error message 333 | func (l Length) DefaultMessage() string { 334 | return fmt.Sprintf(MessageTmpls["Length"], l.N) 335 | } 336 | 337 | // GetKey return the m.Key 338 | func (l Length) GetKey() string { 339 | return l.Key 340 | } 341 | 342 | // GetLimitValue return the limit value 343 | func (l Length) GetLimitValue() interface{} { 344 | return l.N 345 | } 346 | 347 | // Alpha check the alpha 348 | type Alpha struct { 349 | Key string 350 | } 351 | 352 | // IsSatisfied judge whether obj is valid 353 | func (a Alpha) IsSatisfied(obj interface{}) bool { 354 | if str, ok := obj.(string); ok { 355 | for _, v := range str { 356 | if ('Z' < v || v < 'A') && ('z' < v || v < 'a') { 357 | return false 358 | } 359 | } 360 | return true 361 | } 362 | return false 363 | } 364 | 365 | // DefaultMessage return the default Length error message 366 | func (a Alpha) DefaultMessage() string { 367 | return fmt.Sprint(MessageTmpls["Alpha"]) 368 | } 369 | 370 | // GetKey return the m.Key 371 | func (a Alpha) GetKey() string { 372 | return a.Key 373 | } 374 | 375 | // GetLimitValue return the limit value 376 | func (a Alpha) GetLimitValue() interface{} { 377 | return nil 378 | } 379 | 380 | // Numeric check number 381 | type Numeric struct { 382 | Key string 383 | } 384 | 385 | // IsSatisfied judge whether obj is valid 386 | func (n Numeric) IsSatisfied(obj interface{}) bool { 387 | if str, ok := obj.(string); ok { 388 | for _, v := range str { 389 | if '9' < v || v < '0' { 390 | return false 391 | } 392 | } 393 | return true 394 | } 395 | return false 396 | } 397 | 398 | // DefaultMessage return the default Length error message 399 | func (n Numeric) DefaultMessage() string { 400 | return fmt.Sprint(MessageTmpls["Numeric"]) 401 | } 402 | 403 | // GetKey return the n.Key 404 | func (n Numeric) GetKey() string { 405 | return n.Key 406 | } 407 | 408 | // GetLimitValue return the limit value 409 | func (n Numeric) GetLimitValue() interface{} { 410 | return nil 411 | } 412 | 413 | // AlphaNumeric check alpha and number 414 | type AlphaNumeric struct { 415 | Key string 416 | } 417 | 418 | // IsSatisfied judge whether obj is valid 419 | func (a AlphaNumeric) IsSatisfied(obj interface{}) bool { 420 | if str, ok := obj.(string); ok { 421 | for _, v := range str { 422 | if ('Z' < v || v < 'A') && ('z' < v || v < 'a') && ('9' < v || v < '0') { 423 | return false 424 | } 425 | } 426 | return true 427 | } 428 | return false 429 | } 430 | 431 | // DefaultMessage return the default Length error message 432 | func (a AlphaNumeric) DefaultMessage() string { 433 | return fmt.Sprint(MessageTmpls["AlphaNumeric"]) 434 | } 435 | 436 | // GetKey return the a.Key 437 | func (a AlphaNumeric) GetKey() string { 438 | return a.Key 439 | } 440 | 441 | // GetLimitValue return the limit value 442 | func (a AlphaNumeric) GetLimitValue() interface{} { 443 | return nil 444 | } 445 | 446 | // Match Requires a string to match a given regex. 447 | type Match struct { 448 | Regexp *regexp.Regexp 449 | Key string 450 | } 451 | 452 | // IsSatisfied judge whether obj is valid 453 | func (m Match) IsSatisfied(obj interface{}) bool { 454 | return m.Regexp.MatchString(fmt.Sprintf("%v", obj)) 455 | } 456 | 457 | // DefaultMessage return the default Match error message 458 | func (m Match) DefaultMessage() string { 459 | return fmt.Sprintf(MessageTmpls["Match"], m.Regexp.String()) 460 | } 461 | 462 | // GetKey return the m.Key 463 | func (m Match) GetKey() string { 464 | return m.Key 465 | } 466 | 467 | // GetLimitValue return the limit value 468 | func (m Match) GetLimitValue() interface{} { 469 | return m.Regexp.String() 470 | } 471 | 472 | // NoMatch Requires a string to not match a given regex. 473 | type NoMatch struct { 474 | Match 475 | Key string 476 | } 477 | 478 | // IsSatisfied judge whether obj is valid 479 | func (n NoMatch) IsSatisfied(obj interface{}) bool { 480 | return !n.Match.IsSatisfied(obj) 481 | } 482 | 483 | // DefaultMessage return the default NoMatch error message 484 | func (n NoMatch) DefaultMessage() string { 485 | return fmt.Sprintf(MessageTmpls["NoMatch"], n.Regexp.String()) 486 | } 487 | 488 | // GetKey return the n.Key 489 | func (n NoMatch) GetKey() string { 490 | return n.Key 491 | } 492 | 493 | // GetLimitValue return the limit value 494 | func (n NoMatch) GetLimitValue() interface{} { 495 | return n.Regexp.String() 496 | } 497 | 498 | var alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]") 499 | 500 | // AlphaDash check not Alpha 501 | type AlphaDash struct { 502 | NoMatch 503 | Key string 504 | } 505 | 506 | // DefaultMessage return the default AlphaDash error message 507 | func (a AlphaDash) DefaultMessage() string { 508 | return fmt.Sprint(MessageTmpls["AlphaDash"]) 509 | } 510 | 511 | // GetKey return the n.Key 512 | func (a AlphaDash) GetKey() string { 513 | return a.Key 514 | } 515 | 516 | // GetLimitValue return the limit value 517 | func (a AlphaDash) GetLimitValue() interface{} { 518 | return nil 519 | } 520 | 521 | var emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?") 522 | 523 | // Email check struct 524 | type Email struct { 525 | Match 526 | Key string 527 | } 528 | 529 | // DefaultMessage return the default Email error message 530 | func (e Email) DefaultMessage() string { 531 | return fmt.Sprint(MessageTmpls["Email"]) 532 | } 533 | 534 | // GetKey return the n.Key 535 | func (e Email) GetKey() string { 536 | return e.Key 537 | } 538 | 539 | // GetLimitValue return the limit value 540 | func (e Email) GetLimitValue() interface{} { 541 | return nil 542 | } 543 | 544 | var ipPattern = regexp.MustCompile("^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$") 545 | 546 | // IP check struct 547 | type IP struct { 548 | Match 549 | Key string 550 | } 551 | 552 | // DefaultMessage return the default IP error message 553 | func (i IP) DefaultMessage() string { 554 | return fmt.Sprint(MessageTmpls["IP"]) 555 | } 556 | 557 | // GetKey return the i.Key 558 | func (i IP) GetKey() string { 559 | return i.Key 560 | } 561 | 562 | // GetLimitValue return the limit value 563 | func (i IP) GetLimitValue() interface{} { 564 | return nil 565 | } 566 | 567 | var base64Pattern = regexp.MustCompile("^(?:[A-Za-z0-99+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") 568 | 569 | // Base64 check struct 570 | type Base64 struct { 571 | Match 572 | Key string 573 | } 574 | 575 | // DefaultMessage return the default Base64 error message 576 | func (b Base64) DefaultMessage() string { 577 | return fmt.Sprint(MessageTmpls["Base64"]) 578 | } 579 | 580 | // GetKey return the b.Key 581 | func (b Base64) GetKey() string { 582 | return b.Key 583 | } 584 | 585 | // GetLimitValue return the limit value 586 | func (b Base64) GetLimitValue() interface{} { 587 | return nil 588 | } 589 | 590 | // just for chinese mobile phone number 591 | var mobilePattern = regexp.MustCompile("^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$") 592 | 593 | // Mobile check struct 594 | type Mobile struct { 595 | Match 596 | Key string 597 | } 598 | 599 | // DefaultMessage return the default Mobile error message 600 | func (m Mobile) DefaultMessage() string { 601 | return fmt.Sprint(MessageTmpls["Mobile"]) 602 | } 603 | 604 | // GetKey return the m.Key 605 | func (m Mobile) GetKey() string { 606 | return m.Key 607 | } 608 | 609 | // GetLimitValue return the limit value 610 | func (m Mobile) GetLimitValue() interface{} { 611 | return nil 612 | } 613 | 614 | // just for chinese telephone number 615 | var telPattern = regexp.MustCompile("^(0\\d{2,3}(\\-)?)?\\d{7,8}$") 616 | 617 | // Tel check telephone struct 618 | type Tel struct { 619 | Match 620 | Key string 621 | } 622 | 623 | // DefaultMessage return the default Tel error message 624 | func (t Tel) DefaultMessage() string { 625 | return fmt.Sprint(MessageTmpls["Tel"]) 626 | } 627 | 628 | // GetKey return the t.Key 629 | func (t Tel) GetKey() string { 630 | return t.Key 631 | } 632 | 633 | // GetLimitValue return the limit value 634 | func (t Tel) GetLimitValue() interface{} { 635 | return nil 636 | } 637 | 638 | // Phone just for chinese telephone or mobile phone number 639 | type Phone struct { 640 | Mobile 641 | Tel 642 | Key string 643 | } 644 | 645 | // IsSatisfied judge whether obj is valid 646 | func (p Phone) IsSatisfied(obj interface{}) bool { 647 | return p.Mobile.IsSatisfied(obj) || p.Tel.IsSatisfied(obj) 648 | } 649 | 650 | // DefaultMessage return the default Phone error message 651 | func (p Phone) DefaultMessage() string { 652 | return fmt.Sprint(MessageTmpls["Phone"]) 653 | } 654 | 655 | // GetKey return the p.Key 656 | func (p Phone) GetKey() string { 657 | return p.Key 658 | } 659 | 660 | // GetLimitValue return the limit value 661 | func (p Phone) GetLimitValue() interface{} { 662 | return nil 663 | } 664 | 665 | // just for chinese zipcode 666 | var zipCodePattern = regexp.MustCompile("^[1-9]\\d{5}$") 667 | 668 | // ZipCode check the zip struct 669 | type ZipCode struct { 670 | Match 671 | Key string 672 | } 673 | 674 | // DefaultMessage return the default Zip error message 675 | func (z ZipCode) DefaultMessage() string { 676 | return fmt.Sprint(MessageTmpls["ZipCode"]) 677 | } 678 | 679 | // GetKey return the z.Key 680 | func (z ZipCode) GetKey() string { 681 | return z.Key 682 | } 683 | 684 | // GetLimitValue return the limit value 685 | func (z ZipCode) GetLimitValue() interface{} { 686 | return nil 687 | } 688 | -------------------------------------------------------------------------------- /viper.go: -------------------------------------------------------------------------------- 1 | // PhalGo-Config 2 | // 使用spf13大神的viper配置文件获取工具作为phalgo的配置文件工具 3 | // 喵了个咪 2016/5/11 4 | // 依赖情况: 5 | // "github.com/spf13/viper" 6 | 7 | package phalgo 8 | 9 | import ( 10 | "github.com/spf13/viper" 11 | "path" 12 | ) 13 | 14 | var Config *viper.Viper 15 | 16 | //初始化配置文件 17 | func NewConfig(filePath string, fileName string) { 18 | 19 | Config = viper.New() 20 | Config.WatchConfig() 21 | Config.SetConfigName(fileName) 22 | //filePath支持相对路径和绝对路径 etc:"/a/b" "b" "./b" 23 | if (filePath[:1] != "/"){ 24 | Config.AddConfigPath(path.Join(GetPath(),filePath)) 25 | }else{ 26 | Config.AddConfigPath(filePath) 27 | } 28 | 29 | // 找到并读取配置文件并且 处理错误读取配置文件 30 | if err := Config.ReadInConfig(); err != nil { 31 | panic(err) 32 | } 33 | 34 | } 35 | 36 | 37 | --------------------------------------------------------------------------------