当前位置: 首页> 娱乐> 影视 > 正规网站制作全包_建立网站第一步是什么_牛推网_做app找什么公司

正规网站制作全包_建立网站第一步是什么_牛推网_做app找什么公司

时间:2025/7/13 0:38:20来源:https://blog.csdn.net/m0_57408211/article/details/138139977 浏览次数:0次
正规网站制作全包_建立网站第一步是什么_牛推网_做app找什么公司

项目地址:https://github.com/liwook/PublicReview.git

登录有两种方式:

  • 通过手机号码发送验证码登录。
  • 另一种是通过密码进行登录。

通过验证码登录的话,服务端就要存储该手机号码的验证码,这就是典型的键值对(一个号码对应一个验证码),还有要给验证码设置过期时间,所以可以存储在Redis中。

Go语言连接使用Redis

在config.yaml添加Redis的内容

Redis:Host: 43.139.27.107:6379Password: wook1847PoolSize: 20
#.yaml文件添加的时候要留意,可能添加的格式不对导致程序访问不到配置的
#通过颜色来区分是否有错误。Host: 这个后面是需要空一格,颜色才正确,格式才对

在config.go文件添加Redis配置的结构体。

var (RedisOption  *RedisSetting
)type RedisSetting struct {Host     stringPassword stringPoolSize int
}//InitConfig函数添加读取redis的配置
func InitConfig(path string) {....................err = ReadSection("redis", &RedisOption)if err != nil {panic(err)}
}

在db目录创建redis.go文件。使用一个常用的go Redis客户端 go-redis来连接Redis。

//redis.go
var RedisDb *redis.Clientfunc NewRedisClient(config *config.RedisSetting) (*redis.Client, error) {client := redis.NewClient(&redis.Options{Addr:     config.Host,     //自己的redis实例的ip和portPassword: config.Password, //密码,有设置的话,就需要填写PoolSize: config.PoolSize, //最大的可连接数量})val, err := client.Ping(context.Background()).Result() //测试pingif err != nil {return nil, err}fmt.Println("redis测试: ", val)return client, err
}

在main.go中进行创建redis客户端。

func init() {............................//初始化redisdb.RedisClient, err = db.NewRedisClient(config.RedisOption)if err != nil {panic(err)}
}

添加关于登录的函数

在internal目录创建user目录,添加login.go文件。

1.获取验证码的函数

步骤:

  1. 判断手机号是否合法
  2. 生成验证码,并使用redis的string类型保存在redis中,需设置过期时间
  3. 把验证码发送给客户
const (UserNickNamePrefix = "user"phoneKey           = "phone:"loginMethod        = "loginMethod"
)// 得到验证码
// get /user/verificationcode/:phone
func GetVerificationCode(c *gin.Context) {phone := c.Param("phone")if phone == "" || !isPhoneInvalid(phone) {code.WriteResponse(c, code.ErrValidation, "phone is empty or invalid")return}//生成验证码,6位数num := rand.Intn(1000000) + 100000//用redis的string类型保存key := phoneKey + phonesuccess, err := db.RedisClient.SetNX(context.Background(), key, num, 4*time.Minute).Result()if !success || err != nil {code.WriteResponse(c, code.ErrDatabase, nil)return}code.WriteResponse(c, code.ErrSuccess, gin.H{"VerificationCode": num})
}func isPhoneInvalid(phone string) bool {// 匹配规则: ^1第一位为一, [345789]{1} 后接一位345789 的数字// \\d \d的转义 表示数字 {9} 接9位 ,   $ 结束符regRuler := "^1[123456789]{1}\\d{9}$"reg := regexp.MustCompile(regRuler) // 正则调用规则// 返回 MatchString 是否匹配return reg.MatchString(phone)
}

2.登录

现在的登录/注册,基本都是通过手机号码进行的。而登录的时候选择密码登录,也是通过手机号码和密码一同登录的。

登录的数据是json格式,存储在请求体中。

const (UserNickNamePrefix = "user"phoneKey           = "phone:"
)type LoginRequest struct {Phone       string `json:"name" binding:"required"`CodeOrPwd   string `json:"codeOrPwd" binding:"required"`LoginMethod string `json:"loginMethod" binding:"required"`
}// post /user/login
func Login(c *gin.Context) {var login LoginRequesterr := c.BindJSON(&login)if err != nil {slog.Error("codelogin bind bad", "err", err)code.WriteResponse(c, code.ErrBind, nil)return}if !isPhoneInvalid(login.Phone) {code.WriteResponse(c, code.ErrValidation, "phone is invalid")return}switch login.LoginMethod {case "code":loginCode(c, login, token)case "password":loginPassword(c, login, token)default:code.WriteResponse(c, code.ErrValidation, "loginMethod bad")}
}

 验证码登录

  1. 从redis中得到phone保存的验证码进行对比
  2. 之后从MySQL中判断该用户是否是新用户,若是新用户,就需要创建用户,存储到数据库中
  3. 发送给客户端登录成功。
func loginCode(c *gin.Context, login LoginRequest) {//为空是返回error中的,值为redis.Nil//对比号码是否有验证码val, err := db.RedisClient.Get(context.Background(), phoneKey+login.Phone).Result()if err == redis.Nil {code.WriteResponse(c, code.ErrExpired, "验证码过期或没有该验证码")return}if err != nil {slog.Error("redis get bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}if val != login.CodeOrPwd {code.WriteResponse(c, code.ErrExpired, "验证码错误")return}//之后判断是否是新用户,若是新用户,就创建u := query.TbUsercount, err := u.Where(u.Phone.Eq(login.Phone)).Count()if err != nil {slog.Error("find by phone bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}if count == 0 {err := u.Create(&model.TbUser{Phone: login.Phone, NickName: UserNickNamePrefix + strconv.Itoa(rand.Intn(100000))})if err != nil {slog.Error("create user failed", "err", err)code.WriteResponse(c, code.ErrDatabase, "create user failed")return}}code.WriteResponse(c, code.ErrSuccess, "login success")
}

账号密码登录

在数据库中判断发送过来的phone和password是否正确,若正确,回复登录成功;否则回复登录失败

func loginPassword(c *gin.Context, login LoginRequest) {if login.Password == "" {code.WriteResponse(c, code.ErrValidation, "password is empty")return}//从mysql中判断账号和密码是否正确u := query.TbUsercount, err := u.Where(u.Phone.Eq(login.Phone), u.Password.Eq(login.CodeOrPwd)).Count()if err != nil {slog.Error("find by phone and password bad", "err", err)code.WriteResponse(c, code.ErrDatabase, nil)return}if count == 0 {code.WriteResponse(c, code.ErrPasswordIncorrect, "phone or password is Incorrect")return}code.WriteResponse(c, code.ErrSuccess, "login success")
}

对接口进行访问控制,保存登录状态

大家在使用软件的时候,一般是登录一次,以后多次使用或者在一段时间内是不用再次登录的。这个是怎么做到的呢?在网页登录后,每次请求都会带有可以证明该客户端身份的token。服务端会进行判断,从而每次请求正常。

还有,在完成了相关的业务接口的开发后,我们正打算放到服务器上给其他同事查看时,你又想到了一个问题,这些 API 接口,没有鉴权功能,那就是所有知道地址的人都可以请求该项目的 API 接口,甚至有可能会被网络上的端口扫描器扫描到后滥用,这非常的不安全,怎么办呢。实际上,我们应该要考虑做纵深防御,对 API 接口进行访问控制。

这里就可以用到JWT。

JWT

JSON Web 令牌(JWT)是一个开放标准(RFC7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。 由于此信息是经过数字签名的,因此可以被验证和信任。 可以使用使用 RSA 或 ECDSA 的公用/专用密钥对对 JWT 进行签名。

jwt的结构体

假设jwt原始的payload如下,username,exp为过期时间,nbf为生效时间,iat为签发时间。第一个是业务非敏感参数,后三者是jwt标准的参数。

{"username": "zhangsan","exp": 1681869394,"nbf": 1681782994,"iat": 1681782994
}

创建internal/middleware文件夹,在该文件夹添加jwt.go。添加如下结构体

type UserClaims struct {Phone                stringjwt.RegisteredClaims // v5版本新加的方法
}

在config.yaml添加关于jwt的配置

JWT:Secret: helloIssuer: dianping-serviceExpire: 7200  #秒

添加关于jwt的配置结构体和变量

// config.go
var (..........JwtOption    *JWTSetting
)type JWTSetting struct {Secret stringIssuer stringExpire time.Duration
}func InitConfig(path string) {..................err = ReadSection("jwt", &JwtOption)if err != nil {panic(err)}
}

生成并解析jwt

入参就是上面结构体UserClaims中的Phone。

  • 避免在 JWT 的 payload 中存储敏感的用户信息。因为 JWT 通常是可解码的,虽然签名可以保证其完整性,但不能保证其保密性。如果需要存储一些用户相关的信息,可以使用加密的方式存储在服务器端,并在 JWT 中存储一个引用或标识符。
  • 所以要对号码进行加密,或者使用其他不敏感的信息。
func GetJWTSecret() []byte {return []byte(config.JwtOption.Secret)
}func GenerateToken(phone string) (string, error) {//sha1加密phonehash := sha1.New()hash.Write([]byte(phone))claims := UserClaims{Phone: string(hash.Sum(nil)),RegisteredClaims: jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(config.JwtOption.Expire)),Issuer:    config.JwtOption.Issuer,NotBefore: jwt.NewNumericDate(time.Now()), //生效时间},}//使用指定的加密方式(hs256)和声明类型创建新令牌tokenStruct := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)//获得完整的签名的令牌return tokenStruct.SignedString(GetJWTSecret())
}func ParseToken(token string) (*UserClaims, error) {tokenClaims, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (any, error) {return GetJWTSecret(), nil})if err != nil {return nil, err}if tokenClaims != nil {if claims, ok := tokenClaims.Claims.(*UserClaims); ok && tokenClaims.Valid {return claims, nil}}return nil, err
}

使用形式

以中间件形式使用。要注意的一点是登录和获取验证码是不用JWT验证的。

func JWT() gin.HandlerFunc {return func(c *gin.Context) {//登录和获取验证码是不用JWT验证的if c.Request.RequestURI == "/user/login" || c.Request.RequestURI == "/user/getcode" {return}ecode := code.ErrSuccesstoken := c.GetHeader("token")if token == "" {ecode = code.ErrInvalidAuthHeader} else {_, err := ParseToken(token)if err != nil {ecode = code.ErrTokenInvalid}}if ecode != code.ErrSuccess {code.WriteResponse(c, ecode, nil)c.Abort()return}c.Next()}
}

使用jwt

那就需要修改登录回复的流程,登录成功,服务端就返回该token,后续该客户使用的时候都要带上该token。

func loginCode(c *gin.Context, login LoginRequest) {..................if count == 0 {err := u.Create(&model.TbUser{Phone: login.Phone, NickName: UserNickNamePrefix + strconv.Itoa(rand.Intn(100000))})if err != nil {slog.Error("create user failed", "err", err)code.WriteResponse(c, code.ErrDatabase, "create user failed")return}}generateTokenResponse(c, login.Phone)
}func loginPassword(c *gin.Context, login LoginRequest) {//从mysql中判断账号和密码是否正确...................if count == 0 {code.WriteResponse(c, code.ErrPasswordIncorrect, "phone or password is Incorrect")return}generateTokenResponse(c, login.Phone)
}func generateTokenResponse(c *gin.Context, phone string) {token, err := middleware.GenerateToken(phone)if err != nil {slog.Error("generate token bad", "err", err)code.WriteResponse(c, code.ErrTokenGenerationFailed, nil)return}code.WriteResponse(c, code.ErrSuccess, gin.H{"token": token})
}

在router.go中使用JWT中间件。

func NewRouter() *gin.Engine {gin.SetMode(gin.ReleaseMode)r := gin.Default()r.Use(middleware.JWT()) //使用jwt中间件r.GET("/ping", func(c *gin.Context) {code.WriteResponse(c, code.ErrSuccess, "pong")})r.GET("/user/verificationcode/:phone", user.GetVerificationCode)r.POST("/user/login", user.Login)return r
}

登录成功后,用户每次发送请求都需要在header中添加token,值是服务器端返回的token。

关键字:正规网站制作全包_建立网站第一步是什么_牛推网_做app找什么公司

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: