目前网上找得相关Beego资料大部分都不完整和比较陈旧,因为项目需要,编写了一个简单的用户流程。其中包括Beego 用户登陆、注册、注销、密码加密(Model模型、Session保持登陆状态、Valid包表单汉化和自定义验证)
一、前言
目前网上找得相关Beego资料大部分都不完整和比较陈旧,因为项目需要,编写了一个简单的用户流程。
1、系统环境
Beego : 1.12.0
GoVersion : go1.12.9
二、路由Router
路径:项目/routers/user.go
package routers
import (
"github.com/astaxie/beego"
"项目/controllers"
)
func init() {
// 登陆路由
beego.Router("/user/login/", &controllers.UserLoginController{})
// 注销路由
beego.Router("/user/logout/", &controllers.UserLogoutController{})
// 注册路由
beego.Router("/user/register/", &controllers.UserRegisterController{})
}
三、用户模型Model
路径: 项目/model/user.go
type User struct {
Id int
Username string // 用户名
Password string // 密码
CreateTime time.Time `orm:"auto_now_add;type(datetime)"` // 创建时间
}
四、用户状态控制器UserStatusController
1、结构体
路径:项目名/controller/user.go
type UserStatusController struct {
beego.Controller
isLogin bool // 登陆状态标记
User models.User // 登陆的用户
}
2、方法
这里重写的是对象的 Prepare
方法,这样可以在其他控制器继承该方法来判断用户是否已经登陆,实现 保持登陆状态 功能。
func (c *UserStatusController) Prepare() {
// 设置默认值
c.isLogin = false
c.Data["isLogin"] = false
c.User = models.User{}
// 获取Session信息 - 注意这里返回的是interface类型
useridInterface := c.GetSession("userid")
userid, ok := useridInterface.(int)
// 优化性能,如果Id不存在就不需要再获取Password
if !ok {
return
}
// 接下来是验证Password是否被更新 - 防止数据库密码更新后仍保持Session
passwordInterface := c.GetSession("password")
password, _ := passwordInterface.(string)
user := models.User{Id: userid}
// ORM对象
o := orm.NewOrm()
// 这里如果在其他地方没有用到用户其他字段的话
// 建议改成One方法仅返回密码来判断
// 即 o.QueryTable("user").Filter("id", &user.id).One(&user,"password")
err := o.Read(&user)
// 如果无法读取到用户就结束
if err != nil {
return
}
// 判断密码是否修改过
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
return
}
// 最后才设置登陆状态
c.isLogin = true
c.Data["isLogin"] = true
c.User = user
}
五、登陆控制器LoginController
路径:项目名/controller/user.go
1、结构体
继承于UserStatusController
,实现检查用户是否已经登陆,防止重复登陆。
type UserLoginController struct {
UserStatusController
}
2、方法
表单提交使用 POST 方法
func (c *UserLoginController) Post() {
// 创建logs(记录器)
l := logs.BeeLogger{}
l.SetPrefix("用户登陆API:")
// 判断是否已经登陆 - 已登陆就终结登陆流程
if c.isLogin == true {
l.Debug("用户已登陆")
c.Ctx.ResponseWriter.WriteHeader(202)
// util.CreateMsg - 这个是我自己写的方法,请参考我博文下面的源代码
c.Data["json"] = util.CreateMsg(202, "用户已登陆")
c.ServeJSON()
return
}
// 用户登陆
username := strings.TrimSpace(c.GetString("username"))
password := strings.TrimSpace(c.GetString("password"))
// 验证表单数据 - 这里使用官方表单数据验证包(结合自定义验证)
// github.com/astaxie/beego/validation
// 可以下面附加的验证工具,或者直接把这一段删掉
u := &valid.ValidateUser{
Username: username,
Password: password,
}
err := u.ValidUser()
if err != nil {
l.Debug("用户注册失败,原因:%s", err.Error())
c.Ctx.ResponseWriter.WriteHeader(403)
c.Data["json"] = util.CreateMsg(403, err.Error())
c.ServeJSON()
return
}
// 创建User对象 - 不设置密码
user := models.User{Username: username}
// 创建ORM对象
o := orm.NewOrm()
// 读取用户对象 - 这里使用One方法而不使用Read可以避免读取整个User对象,稍微优化
// 虽然在这里没有什么卵用,但是如果用户模型比较复杂的情况下还是有效果的
err = o.QueryTable("user").Filter("username", &user.Username).One(&user, "id","password")
if err != nil {
l.Debug("用户不存在")
c.Ctx.ResponseWriter.WriteHeader(401)
c.Data["json"] = util.CreateMsg(401, "用户不存在")
c.ServeJSON()
return
}
// 验证用户密码
// 第一个参数是数据库存储的加密密码,第二个参数是原密码
// 密码是单方面加密,不可解密
// bcrypt包提供的安全加密可以应对大部分的安全要求,具体实现细节请自行百度
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
l.Debug("用户密码错误")
c.Ctx.ResponseWriter.WriteHeader(401)
c.Data["json"] = util.CreateMsg(401, "密码错误")
c.ServeJSON()
return
}
// 设置服务器Session存储
c.SetSession("userid", user.Id)
c.SetSession("password", password) // 这里存储的是原始密码
// 返回成功信息
c.Data["json"] = util.CreateMsg(200, "用户登陆成功")
c.ServeJSON()
}
六、注册控制器UserRegisterController
路径:项目名/controller/user.go
注册功能只是简单演示,这里直接在App.conf(项目配置文件)里面添加了一个配置参数registertoken
,实现内部注册验证,如果要使用邮箱注册请自己再琢磨一下,注册环节
1、结构体
因为注册不需要验证是否已经登陆,直接继承beego原本的控制器就好了。如果需要验证登陆状态的话,直接改成继承UserStatusController
然后像登陆一样,判断 c.isLogin
是否为真,然后return就好。
type UserRegisterController struct {
beego.Controller
}
2、方法
表单提交使用 POST 方法
func (c *UserRegisterController) Post() {
l := logs.GetBeeLogger()
l.SetPrefix("用户注册API:")
// 内部用户注册
// 获取Post表单数据
registerToken := strings.TrimSpace(c.GetString("token"))
// 验证Token - 这里是在App.conf里面设置的registertoken
if registerToken != beego.AppConfig.String("registertoken") {
l.Debug("注册测试Token验证失败,用户输入:%s,系统环境设置:%s", registerToken, beego.AppConfig.String("registertoken"))
c.Ctx.ResponseWriter.WriteHeader(403)
// util.CreateMsg - 这个是我自己写的方法,请参考我博文下面的源代码
c.Data["json"] = util.CreateMsg(403, "token验证失败")
c.ServeJSON()
return
}
// 获取表单数据 - 去除两边的空格
username := strings.TrimSpace(c.GetString("username"))
password := strings.TrimSpace(c.GetString("password"))
// 验证表单数据 - 这里使用官方表单数据验证包(结合自定义验证)
// github.com/astaxie/beego/validation
// 可以下面附加的验证工具,或者直接把这一段删掉
u := &valid.ValidateUser{
Username: username,
Password: password,
}
err := u.ValidUser()
if err != nil {
l.Debug("用户注册失败,原因:%s", err.Error())
c.Ctx.ResponseWriter.WriteHeader(403)
c.Data["json"] = util.CreateMsg(403, err.Error())
c.ServeJSON()
return
}
// 生成加密密码 - 这里使用了Golang官方加密包
// golang.org/x/crypto/bcrypt
// 注意 - 每次生成的密码都不一样,但是没关系
// 登陆是验证原密码再加密后是否能和存储的加密是否一致
// bcrypt.DefaultCost = 10
encodePW, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
l.Debug("密码加密失败", err)
c.Ctx.ResponseWriter.WriteHeader(500)
c.Data["json"] = util.CreateMsg(500, "密码生成失败")
c.ServeJSON()
return
}
// 创建User对象
user := models.User{Username: username, Password: string(encodePW)}
// 创建ORM对象
o := orm.NewOrm()
// ReadOrCreate - 先读取再创建
status, id, err := o.ReadOrCreate(&user, "username")
// 数据库操作失败
if err != nil {
l.Error("发生错误:%s", err)
c.Ctx.ResponseWriter.WriteHeader(500)
c.Data["json"] = util.CreateMsg(500, "数据库连接失败,用户注册失败。")
c.ServeJSON()
return
}
// 创建失败 - 用户名已经存在
if !status {
l.Debug("用户名已经存在,id为%d", id)
c.Ctx.ResponseWriter.WriteHeader(403)
c.Data["json"] = util.CreateMsg(403, "用户注册失败:用户已存在")
c.ServeJSON()
return
}
// 如果需要注册后直接保持登陆状态
// 设置服务器Session存储
// c.SetSession("userid", user.Id)
// c.SetSession("password", password) // 这里存储的是原始密码
// 创建用户成功 - 返回用户名
c.Data["json"] = util.CreateMsg(200, fmt.Sprintf("用户%s注册成功", username))
c.ServeJSON()
}
七、注销控制器UserLogoutController
路径:项目名/controller/user.go
1、结构体
继承 UserStatusController
来判断用户的登陆状态
type UserLogoutController struct {
UserStatusController
}
2、方法
func (c *UserLogoutController) Get() {
// 用户注销
if !c.isLogin {
c.Ctx.ResponseWriter.WriteHeader(403)
// util.CreateMsg - 这个是我自己写的方法,请参考我博文下面的源代码
c.Data["json"] = util.CreateMsg(403, "用户未登陆")
c.ServeJSON()
return
}
// 删除Session - session删除后就不能使用 UserStatusController的 Prepare
// 来保持登陆状态,即实现注销方法
c.DestroySession()
c.Data["json"] = util.CreateMsg(200, "用户注销成功")
c.ServeJSON()
}
八、表单验证
表单验证参考了 fengke549015 的 beego validate验证包的修改原有验证提示信息与使用自定义函数验证的方法 ,非常感谢。这里用简便的方式来实现Valid包的提示信息汉化。
1、汉化Valid包
路径:项目/valid/init.go
package valid
import (
"github.com/astaxie/beego/validation"
"reflect"
)
func init() {
// 汉化官方Validation
var MessageTmpls = map[string]string{
"Required": "不能为空",
"Min": "最小为 %d",
"Max": "最大为 %d",
"Range": "范围在 %d 至 %d",
"MinSize": "最小长度为 %d",
"MaxSize": "最大长度为 %d",
"Length": "长度必须是 %d",
"Alpha": "必须是有效的字母字符",
"Numeric": "必须是有效的数字字符",
"AlphaNumeric": "必须是有效的字母或数字字符",
"Match": "必须匹配格式 %s",
"NoMatch": "必须不匹配格式 %s",
"AlphaDash": "必须是有效的字母或数字或破折号(-_)字符",
"Email": "必须是有效的邮件地址",
"IP": "必须是有效的IP地址",
"Base64": "必须是有效的base64字符",
"Mobile": "必须是有效手机号码",
"Tel": "必须是有效电话号码",
"Phone": "必须是有效的电话号码或者手机号码",
"ZipCode": "必须是有效的邮政编码",
}
if len(MessageTmpls) == 0 {
return
}
// 替换原本的 MessageTmpls,有兴趣的直接看一下源代码,要注意import的顺序。
for k, _ := range MessageTmpls {
validation.MessageTmpls[k] = MessageTmpls[k]
}
}
// 这里将 fengke549015 中获取Alias的方法提取出来,个人感觉更优雅,如果其他
// 自定义验证需要使用该方法,可以直接调用
func GetAlias(i interface{}, field string) (alias string) {
// 获取字段名
// i - 结构体
// field - 字段名
st := reflect.TypeOf(i)
filed, _ := st.FieldByName(field)
return filed.Tag.Get("alias")
}
然后在 main.go
中 import
加入valid模块的初始化可以了,个人感觉比 fengke549015 的方法优雅一点。
import (
"github.com/astaxie/beego"
_ "项目/models"
_ "项目/routers"
_ "项目/valid" // 就是这玩意
)
2、自定义User表单验证
路径: 项目/valid/user.go
type ValidateUser struct {
Username string `alias:"用户名" valid:"Required;MinSize(3);MaxSize(20)"`
Password string `alias:"密码" valid:"Required;MinSize(3);MaxSize(20)"`
}
func (u *ValidateUser) ValidUser() (err error) {
// 先创建一个Validation
valid := validation.Validation{}
// 验证传入的User信息
status, _ := valid.Valid(u)
// 如果验证失败
if !status {
for _, err := range valid.Errors {
// 获取字段别称
var alias = GetAlias(ValidateUser{}, err.Field)
// 返回验证的错误信息
return errors.New(alias + err.Message)
}
}
return nil
}
九、util.CreateMsg
这个方法其实就是返回 Json
内容,具体实现细节比较基础,百度比较详细,下面是源代码。
路径: 项目/util/msg.go
package util
// Json信息返回模版
type JsonMsg struct {
Code int
Msg string
}
func CreateMsg(code int, msg string) (m *JsonMsg) {
m = &JsonMsg{
Code: code,
Msg: msg,
}
return
}