启动:
bee run -gendoc=true -downdoc=true
文章内容中有的注释是个人的理解,可能不严谨
使用 mysql 的话记得导入包:
_ "github.com/go-sql-driver/mysql"
1. models包
1.1 jwt.go
// JWT : header payload signature // json web token: 标头 有效负载 签名 const ( SecretKEY string = "JWT-Secret-Key" DEFAULT_EXPIRE_SECONDS int = 600 // 默认10分钟 PasswordHashBytes = 16 ) // MyCustomClaims // This struct is the payload // 此结构是有效负载 type MyCustomClaims struct { UserID int `json:"userID"` jwt.StandardClaims } // JwtPayload // This struct is the parsing of token payload // 此结构是对token有效负载的解析 type JwtPayload struct { Username string `json:"username"` UserID int `json:"userID"` IssuedAt int64 `json:"iat"` // 发布日期 ExpiresAt int64 `json:"exp"` // 过期时间 } // GenerateToken // @Title GenerateToken // @Description "生成token" // @Param loginInfo *models.LoginRequest "登录请求" // @Param userID int "用户ID" // @Param expiredSeconds int "过期时间" // @return tokenString string "编码后的token" // @return err error "错误信息" func GenerateToken(loginInfo *LoginRequest, userID int, expiredSeconds int) (tokenString string, err error) { // 如果没设置过期时间,默认为 DEFAULT_EXPIRE_SECONDS 600s if expiredSeconds == 0 { expiredSeconds = DEFAULT_EXPIRE_SECONDS } // 创建声明 mySigningKey := []byte(SecretKEY) // 过期时间 = 当前时间(/s)+ expiredSeconds(/s) expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix() logs.Info("Token 将到期于:", time.Unix(expireAt, 0)) user := *loginInfo claims := MyCustomClaims{ userID, jwt.StandardClaims{ Issuer: user.Username, // 发行者 IssuedAt: time.Now().Unix(), // 发布时间 ExpiresAt: expireAt, // 过期时间 }, } // 利用上面创建的声明 生成token // NewWithClaims(签名算法 SigningMethod, 声明 Claims) *Token token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 利用密钥对token签名 tokenStr, err := token.SignedString(mySigningKey) if err != nil { return "", errors.New("错误: token生成失败!") } return tokenStr, nil } // ValidateToken // @Title ValidateToken // @Description "验证token" // @Param tokenString string "编码后的token" // @return *JwtPayload "Jwt有效负载的解析" // @return error "错误信息" func ValidateToken(tokenString string) (*JwtPayload, error) { // 获取编码前的token信息 token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(SecretKEY), nil }) // 获取payload-声明内容 claims, ok := token.Claims.(*MyCustomClaims) if ok && token.Valid { logs.Info("%v %v", claims.UserID, claims.StandardClaims.ExpiresAt, // 过期时间 ) logs.Info("Token 将过期于:", time.Unix(claims.StandardClaims.ExpiresAt, 0), ) return &JwtPayload{ Username: claims.StandardClaims.Issuer, // 用户名:发行者 UserID: claims.UserID, IssuedAt: claims.StandardClaims.IssuedAt, ExpiresAt: claims.StandardClaims.ExpiresAt, }, nil } else { logs.Info(err.Error()) return nil, errors.New("错误: token验证失败") } } // RefreshToken // @Title RefreshToken // @Description "更新token" // @Param tokenString string "编码后的token" // @return newTokenString string "编码后的新的token" // @return err error "错误信息" func RefreshToken(tokenString string) (newTokenString string, err error) { // 获取上一个token token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(SecretKEY), nil }) // 获取上一个token 的 payload-声明 claims, ok := token.Claims.(*MyCustomClaims) if !ok || !token.Valid { return "", err } // 创建新的声明 mySigningKey := []byte(SecretKEY) expireAt := time.Now().Add(time.Second * time.Duration(DEFAULT_EXPIRE_SECONDS)).Unix() //new expired newClaims := MyCustomClaims{ claims.UserID, jwt.StandardClaims{ Issuer: claims.StandardClaims.Issuer, //name of token issue IssuedAt: time.Now().Unix(), //time of token issue ExpiresAt: expireAt, }, } // 利用新的声明,生成新的token newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims) // 利用签名算法对新的token进行签名 tokenStr, err := newToken.SignedString(mySigningKey) if err != nil { return "", errors.New("错误: 新的新json web token 生成失败!") } return tokenStr, nil } // GenerateSalt // @Title GenerateSalt // @Description "生成用户的加密的钥匙|generate salt" // @return salt string "生成用户的加密的钥匙" // @return err error "错误信息" func GenerateSalt() (salt string, err error) { buf := make([]byte, PasswordHashBytes) if _, err := io.ReadFull(rand.Reader, buf); err != nil { return "", errors.New("error: failed to generate user's salt") } return fmt.Sprintf("%x", buf), nil } // GeneratePassHash // @Title GenerateSalt // @Description "对密码加密|generate password hash" // @Param password string "用户登录密码" // @Param salt string "用户的加密的钥匙" // @return hash string "加密后的密码" // @return err error "错误信息" func GeneratePassHash(password string, salt string) (hash string, err error) { h, err := scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, PasswordHashBytes) if err != nil { return "", errors.New("error: failed to generate password hash") } return fmt.Sprintf("%x", h), nil }
1.2 user.go
// TabUser 定义用户格式 type TabUser struct { Id int `json:"id" orm:"column(id);auto"` UserName string `json:"username" orm:"column(username);size(128)"` Password string `json:"password" orm:"column(password);size(128)"` Salt string `json:"salt" orm:"column(salt);size(128)"` } // LoginRequest 定义登录请求格式 type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } // LoginResponse 定义登录响应 type LoginResponse struct { Username string `json:"username"` UserID int `json:"userID"` Token string `json:"token"` } //CreateRequest 定义创建用户请求格式 type CreateRequest struct { Username string `json:"username"` Password string `json:"password"` } //CreateResponse 定义创建用户响应 type CreateResponse struct { UserID int `json:"userID"` Username string `json:"username"` } // DoLogin // @Title DoLogin // @Description "用户登录" // @Param lr *LoginRequest "登录请求" // @return *LoginResponse "登录响应" // @return int "状态码" // @return error "错误信息" func DoLogin(lr *LoginRequest) (*LoginResponse, int, error) { // 获取用户名和密码 username := lr.Username password := lr.Password // 验证用户名和密码是否为空 if len(username) == 0 || len(password) == 0 { return nil, http.StatusBadRequest, errors.New("error: 用户名或密码为空") } // 连接数据库 o := orm.NewOrm() // 检查用户名是否存在 user := &TabUser{UserName: username} err := o.Read(user, "username") if err != nil { return nil, http.StatusBadRequest, // 400 errors.New("error: 用户名不存在") } // 生成hash加密后的密码 hash, err := GeneratePassHash(password, user.Salt) if err != nil { return nil, http.StatusBadRequest, // 400 err } // 比较用户输入的密码+用户的加密钥匙生成的hash密码 与 数据库中存的hash密码 if hash != user.Password { return nil, http.StatusBadRequest, errors.New("错误: 密码错误!") } // 生成token tokenString, err := GenerateToken(lr, user.Id, 0) if err != nil { return nil, http.StatusBadRequest, err } // 生成的token 返回给前端 return &LoginResponse{ Username: user.UserName, UserID: user.Id, Token: tokenString, }, http.StatusOK, nil } // DoCreateUser // @Title DoCreateUser // @Description "创建用户" // @Param cr *CreateRequest "用户创建请求" // @return *CreateResponse "用户创建响应" // @return int "状态码" // @return error "错误信息" func DoCreateUser(cr *CreateRequest) (*CreateResponse, int, error) { // 连接数据库 o := orm.NewOrm() // 检查用户名是否存在 userNameCheck := TabUser{UserName: cr.Username} err := o.Read(&userNameCheck, "username") if err == nil { return nil, http.StatusBadRequest, errors.New("username has already existed") } // 生成 用户的加密的钥匙 saltKey, err := GenerateSalt() if err != nil { logs.Info(err.Error()) return nil, http.StatusBadRequest, err } // 生成hash加密的密码 hash, err := GeneratePassHash(cr.Password, saltKey) if err != nil { logs.Info(err.Error()) return nil, http.StatusBadRequest, err } // 创建用户 user := TabUser{} user.UserName = cr.Username user.Password = hash user.Salt = saltKey _, err = o.Insert(&user) if err != nil { logs.Info(err.Error()) return nil, http.StatusBadRequest, err } return &CreateResponse{ UserID: user.Id, Username: user.UserName, }, http.StatusOK, nil } // 映射mysql数据 func init() { orm.RegisterModel(new(TabUser)) }
2. controller包
2.1 user.go
// UserController 处理与用户相关的请求 // user API type UserController struct { beego.Controller } // 解析请求,并将请求体存储到v中 // unmarshalPayload // @Param v interface{} true "接收解析后的请求体的变量" func (c *UserController) unmarshalPayload(v interface{}) error { // json 解析 // Unmarshal(data []byte, v interface{}) // 将json字符串解码到相应的数据结构 err := json.Unmarshal(c.Ctx.Input.RequestBody, &v) if err != nil { logs.Error("RequestBody 解析失败!") } if err != nil { logs.Error("unmarshal payload of %s error: %s", c.Ctx.Request.URL.Path, err) } return nil } // respond // @Title respond // @Description 返回给前端的信息 // @Param code int true "状态码" // @Param message string true "返回信息" // @Param data ...interface{} true "数据" func (c *UserController) respond(code int, message string, data ...interface{}) { c.Ctx.Output.SetStatus(code) var d interface{} if len(data) > 0 { d = data[0] } c.Data["json"] = struct { Code int `json:"code"` Message string `json:"message"` Data interface{} `json:"data,omitempty"` }{ Code: code, Message: message, Data: d, } c.ServeJSON() } // Login // @Title Login // @Description 处理登录请求 func (c *UserController) Login() { lr := new(models.LoginRequest) if err := c.unmarshalPayload(lr); err != nil { c.respond(http.StatusBadRequest, err.Error()) return } lrs, statusCode, err := models.DoLogin(lr) if err != nil { c.respond(statusCode, err.Error()) return } // 将token设置到Header c.Ctx.Output.Header("Authorization", lrs.Token) c.respond(http.StatusOK, "", lrs) } // CreateUser // @Title CreateUser // @Description 新增用户 // @Success 200 // @router /register [post] func (c *UserController) CreateUser() { cu := new(models.CreateRequest) // 获取request body if err := c.unmarshalPayload(cu); err != nil { c.respond(http.StatusBadRequest, err.Error()) } createUser, statusCode, err := models.DoCreateUser(cu) if err != nil { c.respond(statusCode, err.Error()) return } c.respond(http.StatusOK, "", createUser) }
3.routers包
3.1 router.go
func init() { ns := beego.NewNamespace("/api", beego.NSNamespace("/user", beego.NSRouter("/login", &controllers.UserController{}, "post:Login"), beego.NSRouter("/register", &controllers.UserController{}, "post:CreateUser"), ), ) beego.AddNamespace(ns) }
4. conf包
appname = 项目名称 httpport = 8080 runmode = dev autorender = false copyrequestbody = true EnableDocs = true sqlconn = [mysql] dbHost = "localhost" dbPort = "3306" dbUser = "用户名" dbName = "数据库名称" dbPassword = "数据库密码"
5. main.go
func main() { if beego.BConfig.RunMode == "dev" { beego.BConfig.WebConfig.DirectoryIndex = true beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger" } beego.Run() } func init() { // mysql dbHost := beego.AppConfig.String("dbHost") dbPort := beego.AppConfig.String("dbPort") dbUser := beego.AppConfig.String("dbUser") dbPassword := beego.AppConfig.String("dbPassword") dbName :=beego.AppConfig.String("dbName") dsn := dbUser + ":" + dbPassword +"@tcp("+ dbHost +":"+ dbPort +")/"+ dbName +"?charset=utf8" // 设置数据库基本信息,相当于连接数据库 _ = orm.RegisterDataBase("default","mysql",dsn,30) // 生成表 _ = orm.RunSyncdb("default", false, true) }