本次我们接着上四篇文章进行讲解《从0开始,用Go语言搭建一个简单的后端业务系统》、《从1开始,扩展Go语言后端业务系统的RPC功能》、《从2开始,在Go语言后端业务系统中引入缓存》以及《从3开始,在业务系统中增加分页功能》,这次是系统中比较核心的功能——用户登录&注册,这个功能其实本应该是最先实现的,但是由于不同因素的影响,放到了本次进行实现,不过也无伤大雅,后期我们都会不断的进行查漏补缺和优化来使我们的项目总体上更加优雅,话不多说,我们开始正文:
1 用户注册&登录流程
(1)注册流程
(2)登录流程
2 代码实现
user结构:
package model import ( "encoding/json" ) type User struct { Id int64 `json:"id"` Name string `json:"name"` LoginName string `json:"login_name"` Role int64 `json:"role"` Pwd string `json:"pwd"` CreateTime string `json:"create_time"` } func (user User) TableName() string { return "user_info" } func (user User) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "id": user.Id, "name": user.Name, "login_name": user.LoginName, "role": user.Role, "pwd": user.Pwd, "create_time": user.CreateTime, }) } //Redis类似序列化操作 func (user User) MarshalBinary() ([]byte, error) { return json.Marshal(user) } func (user User) UnmarshalBinary(data []byte) error { return json.Unmarshal(data, &user) }
dao层代码:
package dao import ( "context" "count_num/pkg/model" ) type UserDao interface { // 添加一个 CreateUser(ctx context.Context, user model.User) bool // 根据ID查找一个 GetUserByUid(ctx context.Context, uId int64) model.User // 查找全部 GetAll(ctx context.Context, page int, limit int) []model.User // 根据ID修改一个 UpdateUserById(ctx context.Context, user model.User) bool // 根据登录名查找一个 GetUserByLoginName(ctx context.Context, loginName string) model.User }
dao层实现:
package impl import ( "context" "count_num/pkg/cache" "count_num/pkg/config" "count_num/pkg/model" "count_num/pkg/utils" "gorm.io/gorm" ) type UserDaoImpl struct { db *gorm.DB cache *cache.CountNumCacheDAOImpl } func NewUserDaoImpl() *UserDaoImpl { return &UserDaoImpl{db: config.DB, cache: cache.NewCountNumCacheDAOImpl()} } func (impl *UserDaoImpl) CreateUser(ctx context.Context, user model.User) bool { var u model.User impl.db.First(&u, "login_name", user.LoginName) if u.LoginName == user.LoginName { return false } user.Pwd = utils.GetMd5Str(user.Pwd) user.CreateTime = utils.NowTimeStr() impl.db.Save(&user) return true } func (impl *UserDaoImpl) GetUserByUid(ctx context.Context, uId int64) model.User { var user model.User impl.db.First(&user, "id", uId) return user } func (impl *UserDaoImpl) GetAll(ctx context.Context, page int, limit int) []model.User { users := make([]model.User, 0) if page <= 0 || limit <= 0 { impl.db.Find(&users) } else { impl.db.Limit(limit).Offset((page - 1) * limit).Find(&users) } return users } func (impl *UserDaoImpl) UpdateUserById(ctx context.Context, user model.User) bool { impl.db.Model(&model.User{}).Where("id = ?", user.Id).Updates(user) return true } func (impl *UserDaoImpl) GetUserByLoginName(ctx context.Context, loginName string) model.User { var user model.User impl.db.First(&user, "login_name", loginName) return user }
工具方法,用于生成token和MD5加密:
package utils import ( "bytes" "crypto/md5" "encoding/gob" "encoding/hex" "math/rand" "strings" "time" ) func GetMd5Str(str string) string { md5 := md5.New() var buf bytes.Buffer gob.NewEncoder(&buf).Encode(str) md5.Write(buf.Bytes()) return hex.EncodeToString(md5.Sum(nil)) } func GetTokenStr() string { char := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" charArr := strings.Split(char, "") c := len(charArr) ran := rand.New(rand.NewSource(time.Now().Unix())) var str string = "" for i := 1; i <= 18; i++ { str = str + charArr[ran.Intn(c)] } return str }
工具方法:
package utils import ( "time" ) // NowTimeStr return 2022-01-21 12:21:31 func NowTimeStr() string { return time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05") } // NowTimeStamp return 1657255820 func NowTimeStamp() int64 { return time.Now().Unix() } // TimeStamp2NowTimeStr 1657255820 -> 2022-01-21 12:21:31 func TimeStamp2NowTimeStr(stamp int64) string { format := time.Unix(stamp, 0).Format("2006-01-02 15:04:05") return format } // NowTimeStr2TimeStamp 2022-01-21 12:21:31 -> 1657255820 func NowTimeStr2TimeStamp(str string) int64 { var LOC, _ = time.LoadLocation("Asia/Shanghai") tim, _ := time.ParseInLocation("2006-01-02 15:04:05", str, LOC) return tim.Unix() }
controller层:
package controller import ( "count_num/pkg/dao/impl" "count_num/pkg/model" "count_num/pkg/utils" "count_num/pkg/web/auth" "encoding/json" "github.com/gin-gonic/gin" "io/ioutil" ) type UserControllerImpl struct { dao *impl.UserDaoImpl } type UserController interface { CreateUser(c *gin.Context) FindUserByLoginNameAndPwd(c *gin.Context) Register(c *gin.Context) } func NewUserController() *UserControllerImpl { return &UserControllerImpl{dao: impl.NewUserDaoImpl()} } func (impl UserControllerImpl) CreateUser(c *gin.Context) { body := c.Request.Body bytes, err := ioutil.ReadAll(body) user := model.User{} json.Unmarshal(bytes, &user) if err != nil { panic(err) } res := impl.dao.CreateUser(c, user) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": res}) } func (impl UserControllerImpl) FindUserByLoginNameAndPwd(c *gin.Context) { body := c.Request.Body bytes, err := ioutil.ReadAll(body) user := model.User{} json.Unmarshal(bytes, &user) if err != nil { panic(err) } userByLoginName := impl.dao.GetUserByLoginName(c, user.LoginName) //密码通过 if userByLoginName.Pwd == utils.GetMd5Str(user.Pwd) { setToken := auth.SetToken(c, utils.GetTokenStr(), user) c.JSON(200, map[string]interface{}{"code": 0, "msg": setToken, "count": 0, "data": utils.GetTokenStr()}) } else { if userByLoginName.Id == 0 { c.JSON(200, map[string]interface{}{"code": 0, "msg": "账号不存在", "count": 0, "data": "-1"}) } else { c.JSON(200, map[string]interface{}{"code": 0, "msg": "密码错误", "count": 0, "data": "-1"}) } } } func (impl UserControllerImpl) Register(c *gin.Context) { body := c.Request.Body bytes, err := ioutil.ReadAll(body) user := model.User{} json.Unmarshal(bytes, &user) if err != nil { panic(err) } user.Role = 1 res := impl.dao.CreateUser(c, user) c.JSON(200, map[string]interface{}{"code": 0, "msg": "", "count": 0, "data": res}) }
router增加URL:
...... userInfo := r.Group("/user") { userInfo.POST("/save", controller.NewUserController().CreateUser) userInfo.POST("/login", controller.NewUserController().FindUserByLoginNameAndPwd) userInfo.POST("/register", controller.NewUserController().Register) } ......
前端代码:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>登录/注册</title> <link rel="stylesheet" href="./layui/css/layui.css"> <style> #form { width: 30%; margin-left: 35%; margin-top: 200px; padding: 30px; border: 3px solid #c5c5b0; border-radius: 20px; } #form2 { width: 30%; margin-left: 35%; margin-top: 200px; padding: 30px; border: 3px solid #c5c5b0; border-radius: 20px; } .hidden { display: none; } .show { display: block; } </style> </head> <body> <form class="layui-form" id="form"> <h3 style="font-size: 20px;text-align: center;margin-bottom: 30px;">登录</h3> <div class="layui-form-item"> <label class="layui-form-label">账号</label> <div class="layui-input-inline"> <input type="text" id="loginName" placeholder="请输入账号" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">密码</label> <div class="layui-input-inline"> <input type="password" id="loginPwd" placeholder="请输入密码" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button class="layui-btn" type="button" onclick="login()">立即提交</button> <button type="button" onclick="toRegister()" class="layui-btn layui-btn-primary">注册</button> </div> </div> </form> <form class="layui-form hidden" id="form2"> <h3 style="font-size: 20px;text-align: center;margin-bottom: 30px;">注册</h3> <div class="layui-form-item"> <label class="layui-form-label">昵称</label> <div class="layui-input-inline"> <input type="text" id="regName" placeholder="请输入昵称" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">账号</label> <div class="layui-input-inline"> <input type="text" id="regLoginName" placeholder="请输入账号" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">密码</label> <div class="layui-input-inline"> <input type="password" id="regPwd" placeholder="请输入密码" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">确认密码</label> <div class="layui-input-inline"> <input type="password" id="regPwd2" placeholder="请再次输入密码" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">身份</label> <div class="layui-input-block"> <input type="radio" id="role" value="user" title="用户" checked> </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button class="layui-btn" type="button" onclick="register()">立即提交</button> <button type="button" onclick="toLogin()" class="layui-btn layui-btn-primary">登录</button> </div> </div> </form> </body> <script src="./layui/layui.js"></script> <script src="./layui/jquery.min.js"></script> <script> var base_url = 'http://localhost:9888' function login() { var loginName = $("#loginName").val() var loginPwd = $("#loginPwd").val() var data = { 'login_name': loginName, 'pwd': loginPwd } $.ajax({ url: base_url + "/user/login", type: "POST", data: JSON.stringify(data), success: function (res) { if (res.data.length >= 10) { localStorage.setItem("token", res.data) return; } alert(res.msg) return }, error: function (err) { alert(err) return } }) } function register() { var regName = $("#regName").val() var regLoginName = $("#regLoginName").val() var regPwd = $("#regPwd").val() var regPwd2 = $("#regPwd2").val() if (regPwd != regPwd2) { alert("密码不一致鸭") return } var data = { 'name': regName, 'login_name': regLoginName, 'pwd': regPwd } $.ajax({ url: base_url + "/user/register", type: "POST", data: JSON.stringify(data), success: function (res) { if (res.data) { alert("注册成功") } else { alert("注册失败") } }, error: function (err) { console.log(err) } }) } function toLogin() { $("#form2").addClass("hidden") $("#form").removeClass("hidden") } function toRegister() { $("#form").addClass("hidden") $("#form2").removeClass("hidden") } </script> </html>
3 小结
用户的登录和注册功能在一般情况下会使用到验证码,所以这里准备了一篇文章:一文搞懂Go整合captcha实现验证码功能,大家可以自行设计和补充哈。
除此之外,在系统的注册&登录功能背后,往往都会有系统的认证和授权,所以请大家耐心等待我的下一篇文章!