关于web/目录代码的二三事
又是一长串的代码,感觉说不用代码解释东西,就都差一点意思
// package 的声明,这个包叫web
package web
import (
"errors"
"net/http"
"git.com/gin_basic/webook/internal/domain"
"git.com/gin_basic/webook/internal/service"
regexp "github.com/dlclark/regexp2"
"github.com/gin-gonic/gin"
)
const (
emailRegexPattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
// 和上面比起来,用 ` 看起来就比较清爽
passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&.])[A-Za-z\d$@$!%*#?&.]{8,72}$`
)
// UserHandlers 处理用户相关请求的结构体
type UserHandlers struct {
emailRegExp *regexp.Regexp
passwordRegExp *regexp.Regexp
svc *service.UserService
}
// RegisterRoutes 注册用户相关路由
// 该方法用于将用户操作的路由绑定到对应的处理函数
func NewUserHandlers(svc *service.UserService) *UserHandlers {
return &UserHandlers{
emailRegExp: regexp.MustCompile(emailRegexPattern, regexp.None),
passwordRegExp: regexp.MustCompile(passwordRegexPattern, regexp.None),
svc: svc,
}
}
func (h *UserHandlers) RegisterRoutes(routers *gin.Engine) {
// 用户路由组
rg := routers.Group("/users")
// 注册路由
rg.POST("/signup", h.SignUp)
// 登录路由
rg.POST("/sign", h.Login)
// 用户资料路由
rg.GET("/profile", h.Profile)
// 编辑资料路由
rg.POST("/edit", h.Edit)
}
// Signup 处理用户注册请求
// 该方法用于处理用户注册流程,包括验证数据,创建用户等
// 它接收一个 gin.Context 类型的参数,用于获取请求数据和发送响应
// 定义几个方法,包含注册、登录、编辑、查看
func (h *UserHandlers) SignUp(ctx *gin.Context) {
// 注册
// 字段标签,固定接收json格式,内部类
type SignUpReq struct {
Email string `json:"email"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
}
//Bind 方法,类似是格式校验的一个方法,如果格式不是上面SignUp设置的格式就会报错。
var req SignUpReq
if err := ctx.Bind(&req); err != nil {
ctx.String(200, "输入的格式不合法")
return
}
// 邮箱格式校验
isEmail, err := h.emailRegExp.MatchString(req.Email)
if err != nil {
ctx.String(http.StatusOK, "系统错误")
return
}
if !isEmail {
ctx.String(http.StatusOK, "邮箱式不对")
return
}
// 密码格式校验
isPassword, err := h.passwordRegExp.MatchString(req.Password)
if err != nil {
ctx.String(http.StatusOK, "系统错误")
return
}
if !isPassword {
ctx.String(http.StatusOK, "密码格式不对,密码长度应该不小于8位,且包含特殊符号")
return
}
// 确认密码校验
if req.Password != req.ConfirmPassword {
ctx.String(200, "两次输入密码不一致")
return
}
err = h.svc.SignUp(ctx, domain.User{
Email: req.Email,
Password: req.Password,
})
switch {
case err == nil:
ctx.String(http.StatusOK, "注册成功,")
case errors.Is(err, service.ErrDuplicateEmail):
ctx.String(http.StatusOK, "邮箱冲突,请换一个")
default:
ctx.String(http.StatusOK, "系统错误")
}
ctx.String(http.StatusOK, "你已经完成注册")
}
// Login 处理用户登录请求
// 该方法用于处理用户登录流程,包括验证身份,生成会话等
// 它接收一个 gin.Context 类型的参数,用于获取请求数据和发送响应
// 定义几个方法,包含注册、登录、编辑、查看
func (h *UserHandlers) Login(ctx *gin.Context) {
type LoginReq struct {
Email string `json:"email"`
Password string `json:"password"`
}
//Bind 方法,类似是格式校验的一个方法,如果格式不是上面SignUp设置的格式就会报错。
var req LoginReq
if err := ctx.Bind(&req); err != nil {
return
}
_, err := h.svc.Login(ctx, req.Email, req.Password)
switch {
case err == nil:
ctx.String(http.StatusOK, "登录成功")
case errors.Is(err, service.ErrInvalidUserOrPassword):
ctx.String(http.StatusOK, "用户名称或者密码不对")
default:
ctx.String(http.StatusOK, "系统错误")
}
}
// Profile 处理用户资料查询请求
// 该方法用于处理用户查询自己资料的请求
// 它接收一个 gin.Context 类型的参数,用于获取请求数据和发送响应
func (h *UserHandlers) Profile(ctx *gin.Context) {
// 查看
}
// Edit 处理用户资料编辑请求
// 该方法用于处理用户编辑自己资料的请求
// 它接收一个 gin.Context 类型的参数,用于获取请求数据和发送响应
func (h *UserHandlers) Edit(ctx *gin.Context) {
// 编辑
}
代码的具体解释(仅限于个人理解)
【开篇:第一段代码】
package web
//定义了包的名称:web
import (
// import 里面是引用的内容
"errors"
"net/http"
// git.com 这部分是程序内部的引用,git.com/gon_basic这是相对路径;
"git.com/gin_basic/webook/internal/domain"
"git.com/gin_basic/webook/internal/service"
// regexp 是引用的github里面的regexp2的别名,引用包的时候前面不需要带http字样
// 除了别名以外还有 "_" 前面的这种方式引用,比如数据库的连接
regexp "github.com/dlclark/regexp2"
"github.com/gin-gonic/gin"
)
【第二段:常量的定义】
// 这部分是定义常量,关键字是const,里面的是一组正则表达式。
//这部分是邮箱和密码格式的定义,密码要求必须是8位及以上但是要小于72位,然后要求带有大小写字母特殊符号等
//代码里面引用的regexp是对密码格式的校验,由于gin本身自带的regexp不支持"?=."这种格式,所以引用了regexp2,设置别名为regexp
const (
emailRegexPattern = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"
// 和上面比起来,用 ` 看起来就比较清爽
passwordRegexPattern = `^(?=.*[A-Za-z])(?=.*\d)(?=.*[$@$!%*#?&.])[A-Za-z\d$@$!%*#?&.]{8,72}$`
)
【第三段:结构体的定义】
//这里定义了一个叫 UserHandlers 的结构体,里面包含了如下几个字段。emailRegExp、passwordRegExp 这两个是指向regexp 的指针,用于邮箱和密码的校验,svc是指向用户服务的指针。
type UserHandlers struct {
emailRegExp *regexp.Regexp
passwordRegExp *regexp.Regexp
svc *service.UserService
}
3.1 关于结构体的创建(极简的那种)
我们在GoLand 上面输入关键字type,然后下面就会给出提示,按需自建即可
3.2 关于指针(这个就很迷糊)
大概理解就是字段前面带星号的就是指针例如(*regexp.Regexp)这种,具体的解释可以看我下面灵码的内容
指针的定义
指针是一个变量,其值是另一个变量的内存地址。在 Go 中,你可以声明一个指针类型,例如 int 表示指向整型的指针。
使用 () 运算符来访问指针所指向的值,这被称为解引用
空指针:
如果指针没有被初始化或没有指向任何值,它的默认值是 nil,这被称为空指针。
安全性:
不同于 C 和 C++,Go 的指针不允许执行某些不安全的操作,如指针算术(加减运算)。这增加了语言的安全性和减少了潜在的错误。
传递参数:
在函数调用中,如果参数是引用类型的(如指针),那么对参数的修改将影响到原始变量。这可以避免不必要的数据拷贝,提高效率。
动态内存分配:
虽然 Go 语言中通常使用 make 和 new 函数来动态分配内存,但这些操作背后也涉及到指针的概念。
多级指针:
可以有指向指针的指针,即多级指针,但在 Go 中并不常见,因为语言的设计倾向于简洁和清晰。
栈和堆:
指针可以指向栈上的局部变量,也可以指向堆上动态分配的内存。了解变量的生命周期和作用域对于正确使用指针至关重要。
关于指针的引用和内存地址的打印,以及指针解引用代码如下:
package main
import "fmt"
// main函数是程序的入口点
func main() {
// 声明一个整型变量a并初始化为10
var a int
a = 10
// 输出变量a的值
fmt.Println("a:", a)
// 输出变量a的内存地址
fmt.Printf("a的内存地址是:%p\n", &a)
// 声明一个指向a的指针变量b
b := &a
// 输出指针变量b指向的值
fmt.Printf("b:%v\n", *b)
}
【第四段代码:方法的定义】
代码如下:
// NewUserHandlers 创建一个新的UserHandlers实例,用于处理用户相关的HTTP请求。
// 参数svc是一个UserService的指针,提供了与用户相关的业务逻辑。
// 返回一个UserHandlers的指针,初始化了正则表达式和UserService依赖。
func NewUserHandlers(svc *service.UserService) *UserHandlers {
// 初始化电子邮件地址的正则表达式,用于验证电子邮件格式的正确性。
emailRegExp: regexp.MustCompile(emailRegexPattern, regexp.None),
// 初始化密码的正则表达式,用于验证密码的格式是否符合要求。
passwordRegExp: regexp.MustCompile(passwordRegexPattern, regexp.None),
// 设置UserService的依赖,用于处理与用户相关的业务逻辑。
svc: svc,
// 返回初始化后的UserHandlers实例。
return &UserHandlers{
emailRegExp: regexp.MustCompile(emailRegexPattern, regexp.None),
passwordRegExp: regexp.MustCompile(passwordRegexPattern, regexp.None),
svc: svc,
}
}
把代码拆开来看,其中func是方法定义的关键字,NewUserHandlers是方法的名字,(svc *service.UserService) 是一个指针接收者,svc是接收者的名字,接收类型是指针类型,*UserHandlers 是指针类型的返回值,用于对初始化的&UserHandlers进行解引用,接接收到的地址信息转换为实际的值。
【第五段代码:结构体的实现方法】
在GoLand里面,我们定义好了一个结构体之后,鼠标右键选中,然后点击生成,选择实现方法就可以自动生成一个方法代码,如下代码是对结构体的实现方法的描述:
// RegisterRoutes 将用户相关的路由注册到gin路由器。
// 这个方法定义了用户操作的URL模式,如注册、登录、查看和编辑个人资料。
// 参数:
// routers *gin.Engine - gin路由器实例,用于添加新的路由。
func (h *UserHandlers) RegisterRoutes(routers *gin.Engine) {
// 用户相关的路由组,通过/users路径进行分组。
// 用户路由组
rg := routers.Group("/users")
// 注册路由,用于用户创建新账户。
// 请求方法: POST
// 路径: /users/signup
// 注册路由
rg.POST("/signup", h.SignUp)
// 登录路由,用于用户进行身份验证并获取会话令牌。
// 请求方法: POST
// 路径: /users/sign
// 登录路由
rg.POST("/sign", h.Login)
// 用户个人资料路由,用于用户查看自己的个人信息。
// 请求方法: GET
// 路径: /users/profile
// 用户资料路由
rg.GET("/profile", h.Profile)
// 编辑用户资料路由,用于用户更新自己的个人信息。
// 请求方法: POST
// 路径: /users/edit
// 编辑资料路由
rg.POST("/edit", h.Edit)
}
【第六段代码:路由的实现】
// SignUp 处理用户注册请求。
// 该方法首先解析请求中的注册信息,然后验证邮箱和密码格式,
// 接着检查密码确认是否一致,最后调用服务层进行注册操作。
// 参数:
// ctx *gin.Context: Gin框架的上下文对象,用于处理HTTP请求和响应。
func (h *UserHandlers) SignUp(ctx *gin.Context) {
// 定义注册请求的结构体,包含邮箱、密码和确认密码字段。
// 注册
// 字段标签,固定接收json格式,内部类
type SignUpReq struct {
Email string `json:"email"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
}
// 解析请求体中的注册信息。
//Bind 方法,类似是格式校验的一个方法,如果格式不是上面SignUp设置的格式就会报错。
var req SignUpReq
if err := ctx.Bind(&req); err != nil {
// 如果解析失败,返回格式错误的响应。
ctx.String(200, "输入的格式不合法")
return
}
// 使用正则表达式验证邮箱格式。
// 邮箱格式校验
isEmail, err := h.emailRegExp.MatchString(req.Email)
if err != nil {
// 如果验证出错,返回系统错误的响应。
ctx.String(http.StatusOK, "系统错误")
return
}
if !isEmail {
// 如果邮箱格式不正确,返回邮箱格式错误的响应。
ctx.String(http.StatusOK, "邮箱式不对")
return
}
// 使用正则表达式验证密码格式。
// 密码格式校验
isPassword, err := h.passwordRegExp.MatchString(req.Password)
if err != nil {
// 如果验证出错,返回系统错误的响应。
ctx.String(http.StatusOK, "系统错误")
return
}
if !isPassword {
// 如果密码格式不正确,返回密码格式错误的响应。
ctx.String(http.StatusOK, "密码格式不对,密码长度应该不小于8位,且包含特殊符号")
return
}
// 检查密码和确认密码是否一致。
// 确认密码校验
if req.Password != req.ConfirmPassword {
// 如果不一致,返回密码不一致的响应。
ctx.String(200, "两次输入密码不一致")
return
}
// 调用服务层进行注册操作。
err = h.svc.SignUp(ctx, domain.User{
Email: req.Email,
Password: req.Password,
})
switch {
case err == nil:
// 注册成功,返回成功的响应。
ctx.String(http.StatusOK, "注册成功,")
case errors.Is(err, service.ErrDuplicateEmail):
// 如果邮箱已存在,返回邮箱冲突的响应。
ctx.String(http.StatusOK, "邮箱冲突,请换一个")
default:
// 其他错误,返回系统错误的响应。
ctx.String(http.StatusOK, "系统错误")
}
// 注册流程结束,告知用户注册已完成。
ctx.String(http.StatusOK, "你已经完成注册")
}