启动:
bee run -gendoc=true -downdoc=true
文章内容中有的注释是个人的理解,可能不严谨 使用 mysql 的话记得导入包:_ "github.com/go-sql-driver/mysql"
// 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
}
// 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))
}
// 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)
}
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)
}
appname = 项目名称
httpport = 8080
runmode = dev
autorender = false
copyrequestbody = true
EnableDocs = true
sqlconn =
[mysql]
dbHost = "localhost"
dbPort = "3306"
dbUser = "用户名"
dbName = "数据库名称"
dbPassword = "数据库密码"
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)
}