├── conf ├── .gitignore ├── readme.md └── app.dev.conf ├── routers ├── .gitignore └── router.go ├── .gitignore ├── readme.md ├── controllers ├── default.go ├── base.go ├── error.go └── user.go ├── utils ├── rand.go ├── hashUtils.go └── jwt.go ├── main.go ├── tests └── default_test.go └── models ├── base.go └── user.go /conf/.gitignore: -------------------------------------------------------------------------------- 1 | app.conf -------------------------------------------------------------------------------- /routers/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !router.go 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /beego_api 2 | /lastupdate.tmp 3 | /*.tmp 4 | -------------------------------------------------------------------------------- /conf/readme.md: -------------------------------------------------------------------------------- 1 | # 配置文件 2 | ## 配置文件使用 3 | * 请教 app.dev.conf 复制一份并命名为 app.conf 4 | 5 | ## 配置文件说明 6 | * runmode 表示项目运行环境 dev / prod 7 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Beego Api 2 | 3 | ### beego restful api, jwt 4 | 5 | ## 使用 6 | 7 | * git clone github.com/khlipeng/beego_api 8 | * bee run 9 | 10 | ## 测试路由 11 | ### 注册 12 | * POST /v1/user/reg 13 | 14 | ### 登录 15 | * POST /v1/user/login 16 | 17 | ### 认证测试 18 | * GET /v1/user/auth 19 | -------------------------------------------------------------------------------- /conf/app.dev.conf: -------------------------------------------------------------------------------- 1 | appname = api 2 | httpport = 8080 3 | runmode = dev 4 | autorender = false 5 | copyrequestbody = true 6 | EnableDocs = true 7 | 8 | [database] 9 | db_host = 10 | db_port = 11 | db_user = 12 | db_passwd = 13 | db_name = 14 | db_charset = utf8mb4 15 | db_prefix = api_ 16 | 17 | [jwt] 18 | token = 1BD6C43CA0BBF4B7ABA5E486D6A5AA2D -------------------------------------------------------------------------------- /controllers/default.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | // Operations about object 4 | type DefaultController struct { 5 | BaseController 6 | } 7 | 8 | // @Title 欢迎信息 9 | // @Description API 欢迎信息 10 | // @Success 200 {object} 11 | // @router / [any] 12 | func (o *DefaultController) GetAll() { 13 | o.Data["json"] = Response{0, "success.", "API 1.0"} 14 | o.ServeJSON() 15 | } 16 | -------------------------------------------------------------------------------- /utils/rand.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | // 随机数生成 10 | // @Param min int 最小值 11 | // @Param max int 最大值 12 | // @return string 13 | func RandInt(min int, max int) string { 14 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 15 | num := r.Intn((max - min)) + min 16 | if num < min || num > max { 17 | RandInt(min, max) 18 | } 19 | return fmt.Sprintf("%d", num) 20 | } 21 | -------------------------------------------------------------------------------- /routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/khlipeng/beego_api/controllers" 6 | ) 7 | 8 | // 使用注释路由 9 | func init() { 10 | 11 | beego.Router("/", &controllers.DefaultController{}, "*:GetAll") 12 | ns := beego.NewNamespace("/v1", 13 | beego.NSNamespace("/user", 14 | beego.NSInclude( 15 | &controllers.UserController{}, 16 | ), 17 | ), 18 | ) 19 | beego.AddNamespace(ns) 20 | } 21 | -------------------------------------------------------------------------------- /controllers/base.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | ) 6 | 7 | type BaseController struct { 8 | beego.Controller 9 | } 10 | 11 | //Response 结构体 12 | type Response struct { 13 | Errcode int `json:"errcode"` 14 | Errmsg string `json:"errmsg"` 15 | Data interface{} `json:"data"` 16 | } 17 | 18 | //Response 结构体 19 | type ErrResponse struct { 20 | Errcode int `json:"errcode"` 21 | Errmsg interface{} `json:"errmsg"` 22 | } 23 | -------------------------------------------------------------------------------- /utils/hashUtils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/base64" 6 | "fmt" 7 | ) 8 | 9 | func Sha1(input string) string { 10 | if input == "" { 11 | return "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc" 12 | } 13 | return fmt.Sprintf("%x", sha1.Sum([]byte(input))) 14 | } 15 | 16 | func Secret2Password(username, secret string) string { 17 | return Sha1(Sha1(secret[:8]) + Sha1(username) + Sha1(secret[8:])) 18 | } 19 | 20 | func Base64(input string) string { 21 | return base64.StdEncoding.EncodeToString([]byte(input)) 22 | } 23 | -------------------------------------------------------------------------------- /controllers/error.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | ) 6 | 7 | type ErrorController struct { 8 | beego.Controller 9 | } 10 | 11 | func (c *ErrorController) Error404() { 12 | c.Data["json"] = Response{ 13 | Errcode: 404, 14 | Errmsg: "Not Found", 15 | } 16 | c.ServeJSON() 17 | } 18 | func (c *ErrorController) Error401() { 19 | c.Data["json"] = Response{ 20 | Errcode: 401, 21 | Errmsg: "Permission denied", 22 | } 23 | c.ServeJSON() 24 | } 25 | func (c *ErrorController) Error403() { 26 | c.Data["json"] = Response{ 27 | Errcode: 403, 28 | Errmsg: "Forbidden", 29 | } 30 | c.ServeJSON() 31 | } 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/context" 6 | "github.com/astaxie/beego/orm" 7 | "github.com/khlipeng/beego_api/controllers" 8 | "github.com/khlipeng/beego_api/models" 9 | _ "github.com/khlipeng/beego_api/routers" 10 | "time" 11 | ) 12 | 13 | func init() { 14 | models.Init() 15 | corsHandler := func(ctx *context.Context) { 16 | ctx.Output.Header("Access-Control-Allow-Origin", ctx.Input.Domain()) 17 | ctx.Output.Header("Access-Control-Allow-Methods", "*") 18 | } 19 | beego.InsertFilter("*", beego.BeforeRouter, corsHandler) 20 | } 21 | func main() { 22 | if beego.BConfig.RunMode == "dev" { 23 | beego.BConfig.WebConfig.DirectoryIndex = true 24 | beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger" 25 | orm.Debug = true 26 | } 27 | orm.DefaultTimeLoc = time.UTC 28 | beego.ErrorController(&controllers.ErrorController{}) 29 | beego.BConfig.ServerName = "snail server 1.0" 30 | beego.Run() 31 | } 32 | -------------------------------------------------------------------------------- /tests/default_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "runtime" 8 | "path/filepath" 9 | _ "github.com/khlipeng/beego_api/routers" 10 | 11 | "github.com/astaxie/beego" 12 | . "github.com/smartystreets/goconvey/convey" 13 | ) 14 | 15 | func init() { 16 | _, file, _, _ := runtime.Caller(1) 17 | apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".." + string(filepath.Separator)))) 18 | beego.TestBeegoInit(apppath) 19 | } 20 | 21 | // TestGet is a sample to run an endpoint test 22 | func TestGet(t *testing.T) { 23 | r, _ := http.NewRequest("GET", "/v1/object", nil) 24 | w := httptest.NewRecorder() 25 | beego.BeeApp.Handlers.ServeHTTP(w, r) 26 | 27 | beego.Trace("testing", "TestGet", "Code[%d]\n%s", w.Code, w.Body.String()) 28 | 29 | Convey("Subject: Test Station Endpoint\n", t, func() { 30 | Convey("Status Code Should Be 200", func() { 31 | So(w.Code, ShouldEqual, 200) 32 | }) 33 | Convey("The Result Should Not Be Empty", func() { 34 | So(w.Body.Len(), ShouldBeGreaterThan, 0) 35 | }) 36 | }) 37 | } 38 | 39 | -------------------------------------------------------------------------------- /models/base.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "github.com/astaxie/beego/config" 6 | "github.com/astaxie/beego/orm" 7 | _ "github.com/go-sql-driver/mysql" 8 | ) 9 | 10 | // 数据库连接初始化 11 | func Init() { 12 | appConf, err := config.NewConfig("ini", "conf/app.conf") 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | conn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s", 18 | appConf.String("database::db_user"), 19 | appConf.String("database::db_passwd"), 20 | appConf.String("database::db_host"), 21 | appConf.String("database::db_port"), 22 | appConf.String("database::db_name"), 23 | appConf.String("database::db_charset")) 24 | orm.RegisterDataBase("default", "mysql", conn) 25 | 26 | //自动建表 27 | name := "default" 28 | force := false 29 | verbose := true 30 | err = orm.RunSyncdb(name, force, verbose) 31 | if err != nil { 32 | fmt.Println(err) 33 | } 34 | orm.RunCommand() 35 | } 36 | 37 | //返回带前缀的表名 38 | func TableName(str string) string { 39 | appConf, err := config.NewConfig("ini", "conf/app.conf") 40 | if err != nil { 41 | panic(err) 42 | } 43 | return appConf.String("database::db_prefix") + str 44 | } 45 | -------------------------------------------------------------------------------- /utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | "github.com/astaxie/beego/config" 6 | "github.com/dgrijalva/jwt-go" 7 | "log" 8 | ) 9 | 10 | type EasyToken struct { 11 | Username string 12 | Uid int64 13 | Expires int64 14 | } 15 | 16 | var ( 17 | verifyKey string 18 | ErrAbsent = "token absent" // 令牌不存在 19 | ErrInvalid = "token invalid" // 令牌无效 20 | ErrExpired = "token expired" // 令牌过期 21 | ErrOther = "other error" // 其他错误 22 | ) 23 | 24 | func init() { 25 | appConf, err := config.NewConfig("ini", "conf/app.conf") 26 | if err != nil { 27 | panic(err) 28 | } 29 | verifyKey = appConf.String("jwt::token") 30 | } 31 | 32 | func (e EasyToken) GetToken() (string, error) { 33 | claims := &jwt.StandardClaims{ 34 | ExpiresAt: e.Expires, //time.Unix(c.ExpiresAt, 0) 35 | Issuer: e.Username, 36 | } 37 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 38 | tokenString, err := token.SignedString([]byte(verifyKey)) 39 | if err != nil { 40 | log.Println(err) 41 | } 42 | return tokenString, err 43 | } 44 | 45 | func (e EasyToken) ValidateToken(tokenString string) (bool, error) { 46 | if tokenString == "" { 47 | return false, errors.New(ErrAbsent) 48 | } 49 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 50 | return []byte(verifyKey), nil 51 | }) 52 | if token == nil { 53 | return false, errors.New(ErrInvalid) 54 | } 55 | if token.Valid { 56 | 57 | return true, nil 58 | } else if ve, ok := err.(*jwt.ValidationError); ok { 59 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 60 | return false, errors.New(ErrInvalid) 61 | } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 62 | return false, errors.New(ErrExpired) 63 | } else { 64 | return false, errors.New(ErrOther) 65 | } 66 | } else { 67 | return false, errors.New(ErrOther) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/astaxie/beego/orm" 5 | // "log" 6 | // "fmt" 7 | "time" 8 | ) 9 | 10 | type User struct { 11 | Id int64 `json:"id" orm:"column(id);pk;auto;unique"` 12 | Phone string `json:"phone" orm:"column(phone);unique;size(11)"` 13 | Nickname string `json:"nickname" orm:"column(nickname);unique;size(40);"` 14 | Password string `json:"-" orm:"column(password);size(40)"` 15 | Created time.Time `json:"create_at" orm:"column(create_at);auto_now_add;type(datetime)"` 16 | Updated time.Time `json:"-" orm:"column(update_at);auto_now;type(datetime)"` 17 | } 18 | 19 | func (u *User) TableName() string { 20 | return TableName("user") 21 | } 22 | func init() { 23 | orm.RegisterModel(new(User)) 24 | } 25 | func Users() orm.QuerySeter { 26 | return orm.NewOrm().QueryTable(new(User)) 27 | } 28 | 29 | // 检测手机号是否注册 30 | func CheckUserPhone(phone string) bool { 31 | exist := Users().Filter("phone", phone).Exist() 32 | return exist 33 | } 34 | 35 | // 检测用户昵称是否存在 36 | func CheckUserNickname(nickname string) bool { 37 | exist := Users().Filter("nickname", nickname).Exist() 38 | return exist 39 | } 40 | 41 | //创建用户 42 | func CreateUser(user User) User { 43 | o := orm.NewOrm() 44 | o.Insert(&user) 45 | return user 46 | } 47 | 48 | //检测手机和昵称是否注册 49 | func CheckUserPhoneOrNickname(phone string, nickname string) bool { 50 | cond := orm.NewCondition() 51 | count, _ := Users().SetCond(cond.And("phone", phone).Or("nickname", nickname)).Count() 52 | if count <= int64(0) { 53 | return false 54 | } 55 | return true 56 | } 57 | func CheckUserAuth(nickname string, password string) (User, bool) { 58 | o := orm.NewOrm() 59 | user := User{ 60 | Nickname: nickname, 61 | Password: password, 62 | } 63 | err := o.Read(&user, "Nickname", "Password") 64 | if err != nil { 65 | return user, false 66 | } 67 | return user, true 68 | } 69 | 70 | // User database CRUD methods include Insert, Read, Update and Delete 71 | func (usr *User) Insert() error { 72 | if _, err := orm.NewOrm().Insert(usr); err != nil { 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | func (usr *User) Read(fields ...string) error { 79 | if err := orm.NewOrm().Read(usr, fields...); err != nil { 80 | return err 81 | } 82 | return nil 83 | } 84 | 85 | func (usr *User) Update(fields ...string) error { 86 | if _, err := orm.NewOrm().Update(usr, fields...); err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | 92 | func (usr *User) Delete() error { 93 | if _, err := orm.NewOrm().Delete(usr); err != nil { 94 | return err 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | // "crypto" 5 | "fmt" 6 | "github.com/astaxie/beego/validation" 7 | "github.com/khlipeng/beego_api/models" 8 | "github.com/khlipeng/beego_api/utils" 9 | // "log" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | var ( 15 | ErrPhoneIsRegis = ErrResponse{422001, "手机用户已经注册"} 16 | ErrNicknameIsRegis = ErrResponse{422002, "用户名已经被注册"} 17 | ErrNicknameOrPasswd = ErrResponse{422003, "账号或密码错误。"} 18 | ) 19 | 20 | type UserController struct { 21 | BaseController 22 | } 23 | type LoginToken struct { 24 | User models.User `json:"user"` 25 | Token string `json:"token"` 26 | } 27 | 28 | // @Title 注册新用户 29 | // @Description 用户注册 30 | // @Param phone formData string true "用户手机号" 31 | // @Param nickname formData string true "用户昵称" 32 | // @Param password formData string true "密码(需要前端 Md5 后传输)" 33 | // @Success 200 {object} 34 | // @Failure 403 参数错误:缺失或格式错误 35 | // @Faulure 422 已被注册 36 | // @router /reg [post] 37 | func (this *UserController) Registered() { 38 | phone := this.GetString("phone") 39 | nickname := this.GetString("nickname") 40 | password := this.GetString("password") 41 | 42 | valid := validation.Validation{} 43 | //表单验证 44 | valid.Required(phone, "phone").Message("手机必填") 45 | valid.Required(nickname, "nickname").Message("用户昵称必填") 46 | valid.Required(password, "password").Message("密码必填") 47 | valid.Mobile(phone, "phone").Message("手机号码不正确") 48 | valid.MinSize(nickname, 2, "nickname").Message("用户名最小长度为 2") 49 | valid.MaxSize(nickname, 40, "nickname").Message("用户名最大长度为 40") 50 | valid.Length(password, 32, "password").Message("密码格式不对") 51 | 52 | if valid.HasErrors() { 53 | // 如果有错误信息,证明验证没通过 54 | for _, err := range valid.Errors { 55 | this.Ctx.ResponseWriter.WriteHeader(403) 56 | this.Data["json"] = ErrResponse{403001, map[string]string{err.Key: err.Message}} 57 | this.ServeJSON() 58 | return 59 | } 60 | } 61 | if models.CheckUserPhone(phone) { 62 | this.Ctx.ResponseWriter.WriteHeader(422) 63 | this.Data["json"] = ErrPhoneIsRegis 64 | this.ServeJSON() 65 | return 66 | } 67 | if models.CheckUserNickname(nickname) { 68 | this.Ctx.ResponseWriter.WriteHeader(422) 69 | this.Data["json"] = ErrNicknameIsRegis 70 | this.ServeJSON() 71 | return 72 | } 73 | 74 | user := models.User{ 75 | Phone: phone, 76 | Nickname: nickname, 77 | Password: password, 78 | } 79 | this.Data["json"] = Response{0, "success.", models.CreateUser(user)} 80 | this.ServeJSON() 81 | 82 | } 83 | 84 | // @Title 登录 85 | // @Description 账号登录 86 | // @Success 200 {object} 87 | // @Failure 404 no enough input 88 | // @Failure 401 No Admin 89 | // @router /login [post] 90 | func (this *UserController) Login() { 91 | nickname := this.GetString("nickname") 92 | password := this.GetString("password") 93 | 94 | user, ok := models.CheckUserAuth(nickname, password) 95 | if !ok { 96 | this.Data["json"] = ErrNicknameOrPasswd 97 | this.ServeJSON() 98 | return 99 | } 100 | 101 | et := utils.EasyToken{ 102 | Username: user.Nickname, 103 | Uid: user.Id, 104 | Expires: time.Now().Unix() + 3600, 105 | } 106 | 107 | token, err := et.GetToken() 108 | if token == "" || err != nil { 109 | this.Data["json"] = ErrResponse{-0, err} 110 | } else { 111 | this.Data["json"] = Response{0, "success.", LoginToken{user, token}} 112 | } 113 | 114 | this.ServeJSON() 115 | } 116 | 117 | // @Title 认证测试 118 | // @Description 测试错误码 119 | // @Success 200 {object} 120 | // @Failure 401 unauthorized 121 | // @router /auth [get] 122 | func (this *UserController) Auth() { 123 | et := utils.EasyToken{} 124 | authtoken := strings.TrimSpace(this.Ctx.Request.Header.Get("Authorization")) 125 | valido, err := et.ValidateToken(authtoken) 126 | if !valido { 127 | this.Ctx.ResponseWriter.WriteHeader(401) 128 | this.Data["json"] = ErrResponse{-1, fmt.Sprintf("%s", err)} 129 | this.ServeJSON() 130 | return 131 | } 132 | 133 | this.Data["json"] = Response{0, "success.", "is login"} 134 | this.ServeJSON() 135 | } 136 | --------------------------------------------------------------------------------