[golang gin框架] 29.Gin 商城项目-用户登录,注册操作

news2024/9/23 5:24:26

一.用户登录,注册界面展示说明

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

  1. 登录页面

  1. 注册页面

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

注册第一步

注册第二步

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

注册第三步

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

  1. 注册成功,或者登录成功后的顶部导航页面

注册成功后,顶部导航页面展示用户账号

二.用户登录,注册代码

  1. 数据表

-- ----------------------------
-- 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;
  1. 数据模型

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"
}
  1. 图形验证码

更新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
    }
}
  1. 路由

在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)
    }
}
  1. 加载公共模块代码完善

完善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)
}
  1. 完善首页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,
    })
}
  1. 登录,注册控制器

该控制器中就是登录注册相关方法
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, "/")
    }
}
  1. 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加密,解密操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/510882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux内核中与“文件系统”相关的数据结构

文件系统相关的数据结构 4.1 file结构体 文件结构体代表一个打开的文件&#xff0c;系统中的每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建&#xff0c;并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后&#xff0c;内核释放这…

【Flink】DataStream API使用之源算子(Source)

源算子 创建环境之后&#xff0c;就可以构建数据的业务处理逻辑了&#xff0c;Flink可以从各种来源获取数据&#xff0c;然后构建DataStream进项转换。一般将数据的输入来源称为数据源&#xff08;data source&#xff09;&#xff0c;而读取数据的算子就叫做源算子&#xff08…

【vue3】06-vue的组件化开发-脚手架创建项目

文章目录 Vue的组件化组件化开发注册组件的方式vue全局组件vue局部组件 Vue的开发模式Vue CLI脚手架安装Vue CLI使用Vue CLI Vue的组件化 Vue是一款前端框架&#xff0c;在这个框架中&#xff0c;组件化开发是非常重要的。Vue的组件化就是将一个页面划分为多个独立的、可复用的…

LeetCode5. 最长回文子串

写在前面&#xff1a; 题目链接&#xff1a;LeetCode5. 最长回文子串 编程语言&#xff1a;C 题目难度&#xff1a;中等 一、题目描述 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例…

算力256TOPS,典型功耗35W,存算一体芯片杀入智能驾驶

作者 | 张祥威 编辑 | 德新 国产智驾芯片有了新玩家 “最高物理算力256 TOPS&#xff0c;典型功耗35W&#xff0c;基于12nm制程工艺。” 5月10日&#xff0c;后摩智能发布首款基于存算一体架构的智驾芯片——鸿途™H30&#xff0c;并公布上述关键指标。 算力、数据和算法&am…

单例模式的饿汉和懒汉写法(基于C++)

目录 单例模式例程饿汉懒汉 对比函数调用线程安全总结 单例模式 单例模式确保一个类只有一个实例&#xff0c;并提供全局访问点。这样可以避免在系统中出现多个相同的对象&#xff0c;从而提高系统的性能和可维护性。 单例模式的实现包括饿汉和懒汉&#xff0c;下面介绍C中这两…

操作系统基础知识之处理器性能方程指标(包含阿达姆定律、CPI、Clock cycle time等)

计算机设计人员通过持续时间或速率来指代时钟周期的时间。程序的 CPU 时间可以用两种方式表示&#xff1a; CPU 时间程序的 CPU 时钟周期 / 时钟频率 除了执行程序所需的时钟周期数外&#xff0c;我们还可以计算执行的指令数。 如果我们知道时钟周期数和指令数&#xff0c;就…

金融学第二版笔记第一章1.1

第1部分 金融和金融体系 第一章金融学 1.1 一、 对金融学进行界定 1.金融 金融是货币流通、信用活动及与之相关的经济行为的总称。 简言之&#xff0c;就是货币资金的融通。一般是指以银行、证券市场等为中心的货币流通和信用调节活动&#xff0c;包括货币的发行和流通、存…

转置卷积(一) 搞懂转置卷积的计算

搞懂转置卷积的计算 0、参考文档1、转置卷积是什么&#xff1f;1.1 定义1.2 需要注意 2、转置卷积的计算2.1 从最简单的开始2.2 考虑stride2.3 考虑padding2.4 考虑dilation 3 转置卷积的加速 文章首发于https://zhaodongyu-ak47.github.io/Transposed_Convolution/ 最近做了一…

数据结构入门-二叉树

树的概念及结构 树的概念 树的一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限节点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一颗倒挂的树&#xff0c;也就是说它树根朝上&#xff0c;而叶子朝下。 有一个特殊的节点&#xff…

Web安全行业:零基础学习网络安全需要掌握哪些知识?(附系统路线+工具笔记)

前言 “没有网络安全就没有国家安全”。当前&#xff0c;网络安全已被提升到国家战略的高度&#xff0c;成为影响国家安全、社会稳定至关重要的因素之一。 一、网络安全行业特点 行业发展空间大&#xff0c;岗位非常多 网络安全行业产业以来&#xff0c;随即新增加了几十个…

单元测试 - 集成H2 Dao测测试

SpringBoot 2.7、Mybatis plus、H2 1. pom引入h2 <dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>2.1.214</version> </dependency> 2. 配置h2数据源 & mapper路径 spring:datas…

地狱级的字节跳动面试,6年测开的我被按在地上摩擦.....

前几天我朋友跟我吐苦水&#xff0c;这波面试又把他打击到了&#xff0c;做了快6年软件测试员。。。为了进大厂&#xff0c;也花了很多时间和精力在面试准备上&#xff0c;也刷了很多题。但题刷多了之后有点怀疑人生&#xff0c;不知道刷的这些题在之后的工作中能不能用到&…

( 位运算 ) 260. 只出现一次的数字 III ——【Leetcode每日一题】

❓260. 只出现一次的数字 III 难度&#xff1a;中等 给你一个整数数组 nums&#xff0c;其中恰好有两个元素只出现一次&#xff0c;其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。 你必须设计并实现线性时间复杂度的算法且仅使用常量额…

精炼计算机网络——数据链路层(一)

文章目录 前言3.1 数据链路和帧3.1.1 数据链路和帧3.1.2 三个基本问题 总结 前言 上篇文章&#xff0c;我们一同学完了物理层的全部内容&#xff0c;在本篇文章中&#xff0c;我们初步学习数据链路层&#xff0c;理解数据链路和帧的相应概念&#xff0c;知晓封装成帧&#xff…

信号完整性分析基础知识之传输线和反射(五):较短阻抗不连续的传输线、残桩和末端容性负载引起的反射

首先来一首定场诗&#xff1a;难难难&#xff0c;道德玄&#xff0c;不对知音不可谈。对了知音谈几句&#xff0c;不对知音枉费舌尖。 较短不连续点引起的反射 很多时候&#xff0c;板载走线的宽度必须要收窄&#xff0c;特别是经过PF区域或者拥挤区域。如果传输线的某一小段…

eSIM证书要求-证书验证-EID

SM-DP 和 SM-DS 应该验证 EUM 和 eUICC 证书中限制的 IIN 和 EID 的一致性&#xff08;参见第 4.5.2.1.0.2 和 4.5.2.1.0.3 节&#xff09;&#xff0c;并考虑 SGP.29 [ 89]。 根据 SGP.29 [89] 颁发的 EID 没有 SGP.02 [2] 中定义的 8 位 IIN。 相反&#xff0c;它们具有可变长…

【计算机视觉 | Python】十个 Python 图像处理工具,建议点赞收藏

文章目录 一、前言二、常见的库2.1 scikit-image2.2 NumPy2.3 SciPy2.4 PIL / Pillow2.5 OpenCV-Python2.6 SimpleCV2.7 Mahotas2.8 SimpleITK2.9 pgmagick2.10 Pycairo 一、前言 这些 Python 库提供了一种简单直观的方法来转换图像并理解底层数据。 今天的世界充满了数据&am…

linux【网络编程】之UDP网络程序模拟实现

linux【网络编程】之UDP网络程序模拟实现 一、开发环境二、服务端实现2.1 接口认识2.1.1 socket创建网络通信套接字2.1.2 bind&#xff1a;绑定Ip和端口号2.1.3 sockaddr_in结构体2.1.4 IP地址转换函数&#xff1a;inet_addr、inet_ntoa2.1.5 recvfrom&#xff1a;读取数据 2.2…

大语言模型进化树重磅发布,感慨技术方向选择的残酷,文末有彩蛋

文 / 高扬&#xff08;微信公众号&#xff1a;量子论&#xff09; 今天说点有深度的内容。五一假期&#xff0c;学习了一篇论文《Harnessing the Power of LLMs in Practice: A Survey on ChatGPT and Beyond》。 这篇论文来自 Amazon 以及 Texas A&M University 研究团队&…