一.用户登录,注册界面展示说明
先看登录,注册界面以及相关流程,再根据流程写代码,一般网站的登录,注册功能都会在一个页面进行操作,还有的是在几个页面进行操作,这里讲解在几个页面进行注册的操作,步骤如下:
登录:
1.点击 '登录'按钮,进入登录界面
2.在'登录界面'输入手机号,密码,图形验证码,点击'立即登录'按钮,进行登录操作(前台,后台都会 校验输入数据的格式,后台校验数据库中 是否存在用户,不存在则返回错误信息)
3.通过登录返回数据,判断结果并根据结果做相关处理( 跳转到具体页面或 者展示错误信息)
注册:
1.点击'注册'按钮,进入到注册页面
2. 注册第一步: 输入注册的手机号以及图形校验码,点击'立即注册'按钮,进入下一步页面操作(其中的逻辑: 判断手机号是否正确, 是否已经注册过,以及 图形校验码是否正确,不正确则展示错误信息,正确则进入下一步操作)
3. 注册第二步:发送 短信验证码到手机,用户输入手机验证码,点击'下一步',进入下一步的操作(还可以重新发送短信验证码,后台会校验验证码是否正确,不正确则展示错误信息,正确则进入下一步操作)
4 .注册第三步: 输入用户密码,以及确认密码,进行最后的注册操作,点击'完成注册'按钮,进行用户注册操作(会判断用户两次密码是否正确,以及用户是否已经注册过),完成后,根据结果显示错误信息或者跳转到对应页面
顶部导航栏登录,注册公共模块

登录页面

注册页面
注册第一步: 输入注册的手机号以及 图形校验码,点击'立即注册'按钮,进入下一步页面操作
其中的逻辑: 判断手机号是否正确, 是否已经注册过,以及 图形校验码是否正确,不正确则展示错误信息,正确则进入下一步操作
注册第一步


注册第二步
注册第二步:发送 短信验证码到手机,用户输入手机验证码,点击'下一步',进入下一步的操作(还可以重新发送短信验证码,后台会校验验证码是否正确,不正确则展示错误信息,正确则进入下一步操作)


注册第三步
注册第三步: 输入用户密码,以及确认密码,进行最后的注册操作,点击'完成注册'按钮,进行用户注册操作(会判断用户两次密码是否正确,以及用户是否已经注册过),完成后,根据结果显示错误信息或者跳转到对应页面

注册成功,或者登录成功后的顶部导航页面
注册成功后,顶部导航页面展示用户账号

二.用户登录,注册代码
数据表
-- ----------------------------
-- Table structure for user 用户表
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`password` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`last_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`add_time` int(0) NULL DEFAULT NULL,
`status` tinyint(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
-- ----------------------------
-- Table structure for user_temp 用户发送短信ip以及次数表:用来判断用户发送短信次数等
-- ----------------------------
DROP TABLE IF EXISTS `user_temp`;
CREATE TABLE `user_temp` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`send_count` int(0) NULL DEFAULT NULL,
`add_day` int(0) NULL DEFAULT NULL,
`add_time` int(0) NULL DEFAULT NULL,
`sign` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 40 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
数据模型
user.go
在models下创建user.go结构体,该结构体记录用户表相关属性
package models
//前端用户表相关
type User struct { // 结构体首字母大写, 和数据库表名对应, 默认访问数据表users, 可以设置访问数据表的方法
Id int
Phone string //手机号
Password string // 密码
AddTime int //增加时间
LastIp string // 最后登录ip
Email string // email
Status int //状态
}
//配置数据库操作的表名称
func (User) TableName() string {
return "user"
}
userTemp.go
在models下创建userTemp.go结构体,该结构体记录用户发送短信相关属性
package models
//用户发送验证码相关
type UserTemp struct {
Id int
Ip string //ip地址
Phone string //手机号
SendCount int //发送次数
AddDay string // 生成日期
AddTime int // 生成时间
Sign string //页面跳转标签
}
func (UserTemp) TableName() string {
return "user_temp"
}
图形验证码
更新models/captcha.go图形验证码方法MakeCaptcha(),可以动态地设置图形验证码相关属性,比如:高度,宽度,长度
package models
//验证码属性: https://captcha.mojotv.cn/
import (
"github.com/mojocn/base64Captcha"
"image/color"
)
//创建store,保存验证码的位置,默认为mem(内存中)单机部署,如果要布置多台服务器,则可以设置保存在redis中
//var store = base64Captcha.DefaultMemStore
//配置RedisStore, 保存验证码的位置为redis, RedisStore实现base64Captcha.Store接口
var store base64Captcha.Store = RedisStore{}
//获取验证码
func MakeCaptcha(height int, width int, length int) (string, string, error) {
//定义一个driver
var driver base64Captcha.Driver
//创建一个字符串类型的验证码驱动DriverString, DriverChinese :中文驱动
driverString := base64Captcha.DriverString{
Height: height, //高度
Width: width, //宽度
NoiseCount: 0, //干扰数
ShowLineOptions: 2 | 4, //展示个数
Length: length, //长度
Source: "1234567890qwertyuioplkjhgfdsazxcvbnm", //验证码随机字符串来源
BgColor: &color.RGBA{ // 背景颜色
R: 3,
G: 102,
B: 214,
A: 125,
},
Fonts: []string{"wqy-microhei.ttc"}, // 字体
}
driver = driverString.ConvertFonts()
//生成验证码
c := base64Captcha.NewCaptcha(driver, store)
id, b64s, err := c.Generate()
return id, b64s, err
}
//校验验证码
func VerifyCaptcha(id string, VerifyValue string) bool {
// 参数说明: id 验证码id, verifyValue 验证码的值, true: 验证成功后是否删除原来的验证码
if store.Verify(id, VerifyValue, true) {
return true
} else {
return false
}
}
路由
在routers/frontendRouters.go中增加 用户登录,注册相关路由
package routers
import (
"goshop/controllers/frontend"
"github.com/gin-gonic/gin"
)
//设置前端路由
func FrontendRoutersInit(r *gin.Engine) {
defaultRouters := r.Group("/")
{
//发送腾讯云短信
defaultRouters.GET("/sms", frontend.BaseController{}.SmsTencent)
//首页
defaultRouters.GET("/", frontend.IndexController{}.Index)
//商品分类对应的商品列表页面
defaultRouters.GET("/category:id", frontend.ProductController{}.Category)
//商品详情页
defaultRouters.GET("/detail", frontend.ProductController{}.Detail)
//获取商品图库信息
defaultRouters.GET("/getImgList", frontend.ProductController{}.GetImgList)
//获取购物车数据
defaultRouters.GET("/cart", frontend.CartController{}.Get)
//增加购物车数据
defaultRouters.GET("/cart/addCart", frontend.CartController{}.AddCart)
//购物车增加成功跳转页面
defaultRouters.GET("/cart/successTip", frontend.CartController{}.AddCartSuccess)
//减少购物车中商品数量
defaultRouters.GET("/cart/decCart", frontend.CartController{}.DecCart)
//增加购物车中商品数量
defaultRouters.GET("/cart/incCart", frontend.CartController{}.IncCart)
//改变一个商品数据的选中状态
defaultRouters.GET("/cart/changeOneCart", frontend.CartController{}.ChangeOneCart)
//全选反选
defaultRouters.GET("/cart/changeAllCart", frontend.CartController{}.ChangeAllCart)
//删除购物车商品数据
defaultRouters.GET("/cart/delCart", frontend.CartController{}.DelCart)
//用户登录页面
defaultRouters.GET("/pass/login", frontend.PassController{}.Login)
//获取图形验证码
defaultRouters.GET("/pass/captcha", frontend.PassController{}.Captcha)
//注册第一步
defaultRouters.GET("/pass/registerStep1", frontend.PassController{}.RegisterStep1)
//注册第二步
defaultRouters.GET("/pass/registerStep2", frontend.PassController{}.RegisterStep2)
//注册第三步
defaultRouters.GET("/pass/registerStep3", frontend.PassController{}.RegisterStep3)
//发送手机验证码
defaultRouters.GET("/pass/sendCode", frontend.PassController{}.SendCode)
//校验手机验证码
defaultRouters.GET("/pass/validateSmsCode", frontend.PassController{}.ValidateSmsCode)
//用户注册操作
defaultRouters.POST("/pass/doRegister", frontend.PassController{}.DoRegister)
//用户登录操作
defaultRouters.POST("/pass/doLogin", frontend.PassController{}.DoLogin)
//登出
defaultRouters.GET("/pass/loginOut", frontend.PassController{}.LoginOut)
}
}
加载公共模块代码完善
完善controllers/BaseController.go的Render()方法,增加用户信息的html(获取Cookie里面保存的用户信息,如果用户登录了,显示用户相关信息,没有登录则展示'登录','注册'等)
package frontend
//基础控制器
import (
"fmt"
"github.com/gin-gonic/gin"
"gopkg.in/ini.v1"
"gorm.io/gorm"
"goshop/models"
"net/http"
"os"
"strings"
)
type BaseController struct{}
/*
加载公共模板方法
tpl string 模板
data map 请求的数据
*/
func (con BaseController) Render(c *gin.Context, tpl string, data map[string]interface{}) {
//实例化redisCache结构体
redisCache := models.RedisCache{}
//获取顶部导航列表
topNavList := []models.Nav{}
//判断redis中是否存在数据
if hasTopNavList := redisCache.Get("topNavList", &topNavList); !hasTopNavList { //不存在数据,则从数据中获取数据,并把数据保存到redis
models.DB.Where("status = 1 AND position = 1").Find(&topNavList)
redisCache.Set("topNavList", topNavList, 3600)
}
//获取分类数据
goodsCateList := []models.GoodsCate{}
if hasGoodsCateList := redisCache.Get("goodsCateList", &goodsCateList); !hasGoodsCateList {
//获取分类列表以及下级分类,并进行排序
models.DB.Where("pid = ? AND status = ?", 0, 1).Order("sort DESC").Preload("GoodsCateItems", func(db *gorm.DB) *gorm.DB {
return db.Where("goods_cate.status = 1").Order("goods_cate.sort DESC")
}).Find(&goodsCateList)
redisCache.Set("goodsCateList", goodsCateList, 3600)
}
//获取中间导航
middleNavList := []models.Nav{}
if hasMiddleNavList := redisCache.Get("middleNavList", &middleNavList); !hasMiddleNavList {
models.DB.Where("status = ? AND position = ? ", 1, 2).Find(&middleNavList)
//循环,获取中间导航对应的商品数据
for i := 0; i < len(middleNavList); i++ {
//获取管理商品
//替换字符串中的中文逗号strings.ReplaceAll()
relation := strings.ReplaceAll(middleNavList[i].Relation, ",", ",")
//把字符串转换成切片
relationIds := strings.Split(relation, ",")
//获取对应的商品信息
goodsList := []models.Goods{}
models.DB.Where("status = ?", 1).Where("id in ?", relationIds).Select("id, title, goods_img, price").Find(&goodsList)
middleNavList[i].GoodsItems = goodsList
}
redisCache.Set("middleNavList", middleNavList, 3600)
}
//获取Cookie里面保存的用户信息
user := models.User{}
models.Cookie.Get(c, "userinfo", &user)
var userinfo string
if len(user.Phone) == 11 {
userinfo = fmt.Sprintf(`<li class="userinfo">
<a href="#">%v</a>
<i class="i"></i>
<ol>
<li><a href="#">个人中心</a></li>
<li><a href="#">喜欢</a></li>
<li><a href="/pass/loginOut">退出登录</a></li>
</ol>
</li> `, user.Phone)
} else {
userinfo = fmt.Sprintf(`<li><a href="/pass/login" >登录</a></li>
<li>|</li>
<li><a href="/pass/registerStep1" target="_blank" >注册</a></li>
<li>|</li>`)
}
renderData := gin.H{
"topNavList": topNavList,
"goodsCateList": goodsCateList,
"middleNavList": middleNavList,
"userinfo": userinfo,
}
for key, v := range data {
renderData[key] = v
}
c.HTML(http.StatusOK, tpl, renderData)
}
完善首页api方法
首页api 结构体继承BaseController,统一使用BaseController.go中的Render()
package frontend
//首页
import (
"github.com/gin-gonic/gin"
"goshop/models"
)
type IndexController struct {
BaseController
}
func (con IndexController) Index(c *gin.Context) {
//实例化redisCache结构体
redisCache := models.RedisCache{}
//获取顶部导航列表, 使用baseController.go中的
//获取网站轮播图数据
focusList := []models.Focus{}
if hasFocusList := redisCache.Get("focusList", &focusList); !hasFocusList {
models.DB.Where("status = 1 AND focus_type = 1").Find(&focusList)
redisCache.Set("focusList", focusList, 3600)
}
//获取分类数据, 使用baseController.go中的
//获取中间导航, 使用baseController.go中的
//获取手机分类下面的商品
phoneList := []models.Goods{}
if hasPhoneList := redisCache.Get("phoneList", &phoneList); !hasPhoneList {
phoneList := models.GetGoodsByCategory(23, "best", 10)
redisCache.Set("phoneList", phoneList, 3600)
}
con.Render(c, "frontend/index/index.html", gin.H{
"focusList": focusList,
"phoneList": phoneList,
})
}
登录,注册控制器
该控制器中就是登录注册相关方法
package frontend
//用户登录,注册相关
import (
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"gopkg.in/ini.v1"
"goshop/models"
"net/http"
"os"
"regexp"
"strings"
)
type PassController struct {
BaseController
}
//获取验证码
func (con PassController) Captcha(c *gin.Context) {
id, b64s, err := models.MakeCaptcha(50, 120, 1)
//判断生成验证码是否错误
if err != nil {
fmt.Println(err)
}
c.JSON(http.StatusOK, gin.H{
"captchaId": id,
"captchaImage": b64s,
})
}
//登录页面
func (con PassController) Login(c *gin.Context) {
//上一页地址:当登录成功后,返回到上一页的地址
prevPage := c.Request.Referer()
c.HTML(http.StatusOK, "frontend/pass/login.html", gin.H{
"prevPage": prevPage,
})
}
//注册第一步页面:
//1.进入注册第一步页面,输入有效手机号以及图形验证码
//2.点击立即注册,进入注册第二步
func (con PassController) RegisterStep1(c *gin.Context) {
c.HTML(http.StatusOK, "frontend/pass/register_step1.html", gin.H{})
}
//注册第二步页面
//校验图形验证码并根据结果跳转到第一步页面或者第二部页面
func (con PassController) RegisterStep2(c *gin.Context) {
//该sign判断是否来自同一个操作
sign := c.Query("sign")
verifyCode := c.Query("verifyCode")
//1、验证图形验证码是否正确
session := sessions.Default(c)
sessionVerifyCode := session.Get("verifyCode")
sessionVerifyCodeStr, ok := sessionVerifyCode.(string)
if !ok || verifyCode != sessionVerifyCodeStr { //不正确,跳转到注册第一步
c.Redirect(http.StatusFound, "/pass/registerStep1")
}
//2、获取sign 判断sign是否合法
userTemp := []models.UserTemp{}
models.DB.Where("sign= ? ", sign).Find(&userTemp)
if len(userTemp) > 0 {
c.HTML(http.StatusOK, "frontend/pass/register_step2.html", gin.H{
"phone": userTemp[0].Phone,
"verifyCode": verifyCode,
"sign": sign,
})
} else {
c.Redirect(http.StatusFound, "/pass/registerStep1")
}
}
//注册第三步:
//校验sign以及短信验证码,判断,并根据结果跳转
func (con PassController) RegisterStep3(c *gin.Context) {
//获取sign页面标签以及短信验证码
sign := c.Query("sign")
smsCode := c.Query("smsCode")
//判断sign是否合法
userTemp := []models.UserTemp{}
models.DB.Where("sign = ?", sign).Find(&userTemp)
if len(userTemp) > 0 {
//验证短信验证码是否正确
//从redis中获取手机短信随机数,并校验短信验证码
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//保存的key
session_key := cfg.Section("sms_tencent").Key("session_key").String()
//保存的key
key := session_key + ":" + userTemp[0].Phone
redisCache := models.RedisCache{}
var obj map[string]string
if result := redisCache.Get(key, &obj); !result {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码已过期,请重新发送",
})
return
}
if smsCode != obj["code"] {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码输入错误",
})
return
}
//跳转到注册第三步页面
c.HTML(http.StatusOK, "frontend/pass/register_step3.html", gin.H{
"smsCode": smsCode,
"sign": sign,
})
} else {
c.Redirect(http.StatusFound, "/pass/registerStep1")
}
}
//注册操作
func (con PassController) DoRegister(c *gin.Context) {
//1、获取表单传过来的数据
sign := c.PostForm("sign")
smsCode := c.PostForm("smsCode")
password := c.PostForm("password")
rpassword := c.PostForm("rpassword")
//3、验证密码是否合法
if len(password) < 6 || password != rpassword {
c.Redirect(http.StatusFound, "/")
}
//4、验证签名是否合法
userTemp := []models.UserTemp{}
models.DB.Where("sign = ?", sign).Find(&userTemp)
if len(userTemp) > 0 {
//验证短信验证码是否正确
//从redis中获取手机短信随机数,并校验短信验证码
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//保存的key
session_key := cfg.Section("sms_tencent").Key("session_key").String()
//保存的key
key := session_key + ":" + userTemp[0].Phone
redisCache := models.RedisCache{}
var obj map[string]string
if result := redisCache.Get(key, &obj); !result {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码已过期,请重新发送",
})
return
}
if smsCode != obj["code"] {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码错误",
})
return
}
//4、完成注册
user := models.User{
Phone: userTemp[0].Phone,
Password: models.Md5(password), //密码要加密
LastIp: userTemp[0].Ip,
AddTime: int(models.GetUnix()),
Status: 1,
}
models.DB.Create(&user)
//5、执行登录
models.Cookie.Set(c, "userinfo", user)
c.Redirect(http.StatusFound, "/")
} else {
c.Redirect(http.StatusFound, "/")
}
}
//发送短信
func (con PassController) SendCode(c *gin.Context) {
//获取手机号,验证码以及验证码id
phone := c.Query("phone")
verifyCode := c.Query("verifyCode")
captchaId := c.Query("captchaId")
if captchaId == "resend" { //重新发送
// 1、注册第二个页面发送验证码的时候需要验证图形验证码
sessionDefault := sessions.Default(c)
sessionVerifyCode := sessionDefault.Get("verifyCode")
sessionVerifyCodeStr, ok := sessionVerifyCode.(string)
if !ok || verifyCode != sessionVerifyCodeStr {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "非法请求",
})
return
}
} else {
// 1、验证图形验证码是否正确 保存图形验证码
if flag := models.VerifyCaptcha(captchaId, verifyCode); !flag {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "验证码输入错误,请重试",
})
return
}
//保存图形验证码
sessionDefault := sessions.Default(c)
sessionDefault.Set("verifyCode", verifyCode)
sessionDefault.Save()
}
/*
2、判断手机格式是否合法
pattern := `^[\d]{11}$`
reg := regexp.MustCompile(pattern)
reg.MatchString(phone)
*/
pattern := `^[\d]{11}$`
reg := regexp.MustCompile(pattern)
if !reg.MatchString(phone) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "手机号格式不合法",
})
return
}
//3、验证手机号是否注册过
userList := []models.User{}
models.DB.Where("phone = ?", phone).Find(&userList)
if len(userList) > 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "手机号已经注册,请直接登录",
})
return
}
//4、判断当前ip地址今天发送短信的次数
ip := c.ClientIP()
currentDay := models.GetDay() //20211211
var sendCount int64
models.DB.Table("user_temp").Where("ip = ? AND add_day = ?", ip, currentDay).Count(&sendCount)
if sendCount > 4 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "此ip今天发送短信的次数已经达到上限,请明天再试",
})
return
}
//5、验证当前手机号今天发送的次数是否合法
userTemp := []models.UserTemp{}
smsCode := models.GetRandomNum()
sign := models.Md5(phone + currentDay) //签名:主要用于页面跳转传值
models.DB.Where("phone = ? AND add_day = ?", phone, currentDay).Find(&userTemp)
if len(userTemp) > 0 {
if userTemp[0].SendCount > 2 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "此手机号今天发送短信的次数已经达到上限,请明天再试",
})
return
} else {
//发送短信
_, err := models.SmsTencent(phone, smsCode)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "发送短信成失败",
})
return
}
//发送成功后处理逻辑
//保存短信验证码到redis,以及记录发送记录等
//保存短信随机数到redis,以便后续校验短信验证码
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//保存的key
session_key := cfg.Section("sms_tencent").Key("session_key").String()
//过期时间
captcha_expiring := cfg.Section("sms_tencent").Key("captcha_expiring").String()
expiration, _ := models.Int(captcha_expiring)
//保存的key
key := session_key + ":" + phone
//保存的值
value := map[string]string{
"code": smsCode,
}
redisCache := models.RedisCache{}
redisCache.Set(key, value, expiration)
//3、更新发送短信的次数
oneUserTemp := models.UserTemp{}
models.DB.Where("id = ?", userTemp[0].Id).Find(&oneUserTemp)
oneUserTemp.SendCount = oneUserTemp.SendCount + 1
oneUserTemp.AddTime = int(models.GetUnix())
models.DB.Save(&oneUserTemp)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "发送短信成功",
"sign": sign,
})
return
}
} else {
//发送短信
_, err := models.SmsTencent(phone, smsCode)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "发送短信成失败",
})
return
}
//发送成功后处理逻辑
//保存短信验证码到redis,以及记录发送记录等
//保存短信随机数到redis,以便后续校验短信验证码
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//保存的key
session_key := cfg.Section("sms_tencent").Key("session_key").String()
//过期时间
captcha_expiring := cfg.Section("sms_tencent").Key("captcha_expiring").String()
expiration, _ := models.Int(captcha_expiring)
//保存的key
key := session_key + ":" + phone
//保存的值
value := map[string]string{
"code": smsCode,
}
redisCache := models.RedisCache{}
redisCache.Set(key, value, expiration)
//保存发送记录到数据库(略)
//3、记录发送短信的次数
oneUserTemp := models.UserTemp{
Ip: ip,
Phone: phone,
SendCount: 1,
AddDay: currentDay,
AddTime: int(models.GetUnix()),
Sign: sign,
}
models.DB.Create(&oneUserTemp)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "发送短信成功",
"sign": sign,
})
return
}
}
//验证验证码
func (con PassController) ValidateSmsCode(c *gin.Context) {
//获取短信验验证码以及页面标签
sign := c.Query("sign")
smsCode := c.Query("smsCode")
//1、验证数据是否合法
userTemp := []models.UserTemp{}
models.DB.Where("sign = ?", sign).Find(&userTemp)
if len(userTemp) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "非法请求",
})
return
}
//2、验证短信验证码是否正确
//从redis中获取手机短信随机数,并校验短信验证码
cfg, err := ini.Load("./conf/app.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
//保存的key
session_key := cfg.Section("sms_tencent").Key("session_key").String()
//保存的key
key := session_key + ":" + userTemp[0].Phone
redisCache := models.RedisCache{}
var obj map[string]string
if result := redisCache.Get(key, &obj); !result {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码已过期",
})
return
}
if smsCode != obj["code"] {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "短信验证码输入错误",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "验证码输入正确",
})
}
//登录操作
func (con PassController) DoLogin(c *gin.Context) {
phone := strings.Trim(c.PostForm("phone"), " ")
password := c.PostForm("password")
captchaId := c.PostForm("captchaId")
captchaVal := c.PostForm("captchaVal")
//1、验证图形验证码是否合法
if flag := models.VerifyCaptcha(captchaId, captchaVal); !flag {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "图形验证码不正确",
})
return
}
//2、验证用户名密码是否正确
password = models.Md5(strings.Trim(password, " "))
userList := []models.User{}
models.DB.Where("phone = ? AND password = ?", phone, password).Find(&userList)
if len(userList) > 0 {
//执行登录
models.Cookie.Set(c, "userinfo", userList[0])
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "用户登录成功",
})
} else {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "用户名或者密码错误",
})
return
}
}
//登出操作: 退出登录后,返回到上一页
func (con PassController) LoginOut(c *gin.Context) {
//删除cookie里面的userinfo执行跳转
models.Cookie.Remove(c, "userinfo")
//上一页地址
prevPage := c.Request.Referer()
if len(prevPage) > 0 {
c.Redirect(http.StatusFound, prevPage)
} else {
c.Redirect(http.StatusFound, "/")
}
}
html,js
js
主要是一些方法:获取图形验证码,改变图形验证码,注册第一步,注册第二步,注册第三步
(function ($) {
$(function () {
loginApp.init();
})
var loginApp = {
init: function () {
this.getCaptcha();
this.captchaImgChage();
this.initRegisterStep1();
this.initRegisterStep2();
this.initRegisterStep3();
this.initDoLogin();
},
getCaptcha: function () { //获取图形验证码
$.get("/pass/captcha?t=" + Math.random(), function (response) {
$("#captchaId").val(response.captchaId)
$("#captchaImg").attr("src", response.captchaImage)
})
},
captchaImgChage: function () { //改变图形验证码
var _that = this;
$("#captchaImg").click(function () {
_that.getCaptcha()
})
},
//注册第一步
//1.输入手机号以及图形验证码,前端校验是否合法
//2.点击 立即注册 按钮, 请求后台api,后台会判断手机号以及图形验证码是否符合要求,并发送手机验证码
initRegisterStep1: function () {
var _that = this;
//发送验证码
$("#registerButton").click(function () {
//验证验证码是否正确
var phone = $('#phone').val();
var verifyCode = $('#verifyCode').val();
var captchaId = $("#captchaId").val();
$(".error").html("")
var reg = /^[\d]{11}$/;
if (!reg.test(phone)) {
$(".error").html("Error:手机号输入错误");
return false;
}
if (verifyCode.length < 1) {
$(".error").html("Error:图形验证码长度不合法")
return false;
}
//请求后台api,校验输入的手机号以及图形验证码,并发送短信
$.get("/pass/sendCode", {
"phone": phone,
"verifyCode": verifyCode,
"captchaId": captchaId
}, function (response) {
if (response.success == true) { //校验成功,进入注册第二步
//跳转到下页面
location.href = "/pass/registerStep2?sign=" + response.sign + "&verifyCode=" + verifyCode;
} else {
//改变验证码
$(".error").html("Error:" + response.message + ",请重新输入!")
//改变验证码
_that.getCaptcha()
}
})
})
},
// 注册第二步
//输入短信验证码,校验,成功则跳转到注册第三步
//还可以重新发送短信验证码
initRegisterStep2: function () {
$(function () {
var timer = 10;
function Countdown() { // 重新发送短信时间
if (timer >= 1) {
timer -= 1;
$("#sendCode").attr('disabled', true);
$("#sendCode").html('重新发送(' + timer + ')');
setTimeout(function () {
Countdown();
}, 1000);
} else {
$("#sendCode").attr('disabled', false)
$("#sendCode").html('重新发送');
}
}
Countdown();
//重新发送短信
$("#sendCode").click(function () {
timer = 10;
Countdown();
var phone = $("#phone").val()
var verifyCode = $("#verifyCode").val()
var captchaId = "resend" //重新发送标签
//重新请求接口发送短信
$.get("/pass/sendCode", {
"phone": phone,
"verifyCode": verifyCode,
"captchaId": captchaId
}, function (response) {
console.log(response)
})
})
})
//验证验证码
$(function () {
$("#nextStep").click(function (e) {
$(".error").html()
var sign = $('#sign').val();
var smsCode = $('#smsCode').val();
//请求api,校验输入短信是否正确,并跳转到注册第三步
$.get('/pass/validateSmsCode', {sign: sign, smsCode: smsCode}, function (response) {
if (response.success == true) {
location.href = "/pass/registerStep3?sign=" + sign + "&smsCode=" + smsCode
} else {
$(".error").html("Error:" + response.message)
}
})
})
$("#returnButton").click(function () {
location.href = "/pass/registerStep1"
})
})
},
initRegisterStep3: function () { //注册第三步: 设置密码注册用户
$(function () {
$("#form").submit(function () {
$(".error").html("")
var password = $('#password').val();
var rpassword = $('#rpassword').val();
if (password.length < 6) {
$(".error").html("Error:密码的长度不能小于6位")
return false;
}
if (password != rpassword) {
$(".error").html("Error:密码和确认密码不一致")
return false;
}
return true;
})
})
},
initDoLogin: function () { //登录操作
var _that = this;
$("#doLogin").click(function (e) {
$(".error").html("")
var phone = $('#phone').val();
var password = $('#password').val();
var captchaId = $('#captchaId').val();
var captchaVal = $("#captchaVal").val();
//获取返回上一页的地址
var prevPage = $("#prevPage").val();
var reg = /^[\d]{11}$/;
if (!reg.test(phone)) {
$(".error").html('Error:手机号输入错误');
return false;
}
if (password.length < 6) {
$(".error").html('Error:密码长度不合法');
return false;
}
if (captchaVal.length < 4) {
$(".error").html('Error:验证码长度不合法');
return false;
}
//ajax请求
$.post('/pass/doLogin', {
phone: phone,
password: password,
captchaVal: captchaVal,
captchaId: captchaId
}, function (response) { //登录成功跳转到首页
if (response.success == true) {
if (prevPage == "") {
location.href = "/";
} else {
location.href = prevPage;
}
} else {
$(".error").html("Error:" + response.message + ",请重新输入!")
//改变验证码
_that.getCaptcha();
}
})
})
}
}
})($)
html
page_header.html
完善page_header.html公共的顶部html,展示用户注册,登录以及用户信息相关html,主要是解析BaseController.go中 Render()里面的用户信息html( 解析方法: Str2Html .userinfo)
{{ define "frontend/public/page_header.html" }}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="order by dede58.com"/>
<title>商城</title>
<link rel="stylesheet" type="text/css" href="/static/frontend/css/style.css">
<link rel="stylesheet" href="/static/frontend/css/swiper.min.css">
<script src="/static/frontend/js/jquery-1.10.1.js"></script>
<script src="/static/frontend/js/swiper.min.js"></script>
<script src="/static/frontend/js/base.js"> </script>
</head>
<body>
<!-- start header -->
<header>
<div class="top center">
<!-- 顶部导航start -->
<div class="left fl">
<ul>
{{$temp := .topNavList | len}}
{{$navLen := Sub $temp 1}}
{{range $key,$value := .topNavList}}
<li><a href="{{$value.Link}}" {{if eq $value.IsOpennew 2}} target="_blank" {{end}}>{{$value.Title}}</a></li>
{{if lt $key $navLen}}
<li>|</li>
{{end}}
{{end}}
<div class="clear"></div>
</ul>
</div>
<!-- 顶部导航end -->
<div class="right fr">
<div class="gouwuche fr"><a href="/cart">购物车</a></div>
<div class="fr">
<ul>
{{Str2Html .userinfo}}
</ul>
</div>
<div class="clear"></div>
</div>
<div class="clear"></div>
</div>
</header>
<!--end header -->
{{end}}
login.html
用户登录页面
{{ define "frontend/pass/login.html" }}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="order by frontend"/>
<title>会员登录</title>
<link rel="stylesheet" href="/static/frontend/css/pass.css" />
<script src="/static/frontend/js/jquery-1.10.1.js"></script>
<script src="/static/frontend/js/pass.js"></script>
</head>
<body class="login_page">
<!-- login -->
<div class="top center">
<div class="logo center">
<a href="/" target="_blank"><img src="/static/frontend/image/mistore_logo.png" alt=""></a>
</div>
</div>
<div class="form">
<div class="login">
<div class="login_center">
<div class="login_top">
<div class="left fl">会员登录</div>
<div class="right fr">您还不是我们的会员?<a href="/pass/registerStep1" target="_self">立即注册</a></div>
<div class="clear"></div>
<div class="xian center"></div>
</div>
<div class="login_main center">
<!--上一url,用于用户登录成功后跳转-->
<input type="hidden" name="prevPage" id="prevPage" value="{{.prevPage}}">
<!--图形验证码id-->
<input type="hidden" name="captchaId" id="captchaId">
<div class="username">手机号:<input class="shurukuang" id="phone" type="text" name="phone" placeholder="请输入你的用户名"/></div>
<div class="username">密 码:<input class="shurukuang" id="password" type="password" name="password" placeholder="请输入你的密码"/></div>
<div class="username">
<div class="left fl">验证码:<input class="yanzhengma" id="captchaVal" type="text" name="captchaVal" placeholder="请输入验证码"/></div>
<div class="right fl">
<img id="captchaImg" style="height: 42px; background: #fff;" src="">
</div>
<div class="clear"></div>
</div>
</div>
<div class="error">
</div>
<div class="login_submit">
<input class="submit" type="button" id="doLogin" value="立即登录" >
</div>
</div>
</div>
</div>
<footer>
<div class="copyright">简体 | 繁体 | English | 常见问题</div>
</footer>
</body>
</html>
{{end}}
register_step1.html
注册第一步:输入有效的手机号以及图形验证码,校验后请求后台api,后台进行相关逻辑判断,根据结果处理请求
{{ define "frontend/pass/register_step1.html" }}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="order by dede58.com"/>
<title>用户注册</title>
<link rel="stylesheet" href="/static/frontend/css/pass.css"/>
<script src="/static/frontend/js/jquery-1.10.1.js"></script>
<script src="/static/frontend/js/pass.js"></script>
</head>
<body class="register_page">
<div class="regist">
<div class="regist_center">
<div class="logo">
<img src="/static/frontend/image/logo_top.png" alt="nest小米">
</div>
<div class="regist_top">
<h2>注册账户</h2>
</div>
<div class="regist_main center">
<div class="error"></div>
<input type="hidden" name="captchaId" id="captchaId">
<input class="form_input" type="text" name="phone" id="phone" placeholder="请填写正确的手机号"/>
<div class="yzm">
<input type="text" id="verifyCode" name="verifyCode" placeholder="请输入图形验证码"/>
<img id="captchaImg" src="">
</div>
<div class="regist_submit">
<button class="submit" id="registerButton">
立即注册
</button>
</div>
<br>
<br>
<div class="privacy_box">
<div class="msg">
<label class="n_checked now select-privacy">
<input type="checkbox" checked="true"/> 注册帐号即表示您同意并愿意遵守 <a
href="https://static.account.xiaomi.com/html/agreement/account/cn.html"
class="inspect_link " title="用户协议" target="_blank">用户协议</a>和<a
href="https://www.mi.com/about/privacy/" class="inspect_link privacy_link"
title=" 隐私政策 " target="_blank"> 隐私政策 </a>
</label>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
{{end}}
register_step2.html
注册第二步:
发送短信验证码(还可以重新发送),输入短信验证码,根据结果处理后续操作
{{ define "frontend/pass/register_step2.html" }}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="order by dede58.com" />
<title>用户注册</title>
<link rel="stylesheet" href="/static/frontend/css/pass.css" />
<script src="/static/frontend/js/jquery-1.10.1.js"></script>
<script src="/static/frontend/js/pass.js"></script>
</head>
<body class="register_page">
<div class="regist">
<div class="regist_center">
<div class="logo">
<img src="/static/frontend/image/logo_top.png" alt="nest小米">
</div>
<div class="regist_top">
<h2>注册账户</h2>
</div>
<div class="regist_main center">
<p>验证码已通过短信发送至{{.phone}},请输入完成验证。手机绑定是为保障用户的信息真实性和帐号安全性</p>
<br>
<br>
<div class="error"></div>
<div class="yzm">
<input type="hidden" id="phone" name="phone" value="{{.phone}}">
<input type="hidden" id="sign" name="sign" value="{{.sign}}">
<input type="hidden" id="verifyCode" name="verifyCode" value="{{.verifyCode}}">
<input type="text" id="smsCode" name="smsCode" placeholder="请输入验证码" />
<button id="sendCode">重新发送</button>
</div>
<div class="regist_submit">
<input class="submit" id="nextStep" type="button" name="submit" value="下一步">
<br>
<input class="return" id="returnButton" type="button" name="return" value="返回">
</div>
</div>
</div>
</div>
</body>
</html>
{{end}}
register_step3.html
注册第三步:
输入用户密码以及确认密码,进行用户注册操作
{{ define "frontend/pass/register_step3.html" }}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="author" content="order by dede58.com"/>
<title>用户注册</title>
<link rel="stylesheet" href="/static/frontend/css/pass.css" />
<script src="/static/frontend/js/jquery-1.10.1.js"></script>
<script src="/static/frontend/js/pass.js"></script>
</head>
<body class="register_page">
<div class="regist">
<div class="regist_center">
<div class="logo">
<img src="/static/frontend/image/logo_top.png" alt="nest小米">
</div>
<div class="regist_top">
<h2>注册账户</h2>
</div>
<div class="regist_main center">
<p>请输入您注册账户的密码</p>
<br>
<br>
<div class="error"></div>
<form action="/pass/doRegister" method="post" id="form">
<input type="hidden" name="sign" value="{{.sign}}" />
<input type="hidden" name="smsCode" value="{{.smsCode}}"/>
<div>
<input class="form_input" type="password" id="password" name="password" placeholder="请输入密码"/>
</div>
<div>
<input class="form_input" type="password" id="rpassword" name="rpassword" placeholder="请输入确认密码"/>
</div>
<div class="regist_submit">
<input class="submit" type="submit" name="submit" value="完成注册" >
</div>
</form>
</div>
</div>
</div>
</body>
</html>
{{end}}
ok,用户登录,注册操作就完成了
[上一节][golang gin框架] 28.Gin 发送短信,DES加密解,Cookie加密,解密操作