美文网首页golang
用fiber写一个restful的博客后端

用fiber写一个restful的博客后端

作者: thepoy | 来源:发表于2021-01-21 16:04 被阅读0次

    前言

    Go的web框架有很多,fiber与gin、beego等的区别在于使用的是fasthttp,而不是标准库net/httpfasthttp的性能可以达到net/http的 10 倍,再加上标准 库net/http的作者已经离开了Go团队,标准库的性能在很长的一段时间内,可能都不会有提升。

    1 博客的结构

    本文写的是一个简单的个人博客,所以只要完成博客的基本功能就可以了。

    1.1 用户

    虽然是个人博客,但可以扩展出用户注册的功能来(万一有热心网友想在这个博客里注册账号发表文章呢?),这个注册的功能更多的还是用来发表评论/提出疑问。

    1.1.1 需要实现的基本功能

    • 注册
    • 登录
    • 退出
    • 注销/软删除
    • 删除/硬删除

    1.1.2 数据库里用户表的基本信息

    • id
    • 用户名
    • 密码(加密)
    • 邮箱
    • 手机号
    • 是否为高级用户/管理员
    • 是否已注销
    • 是否已验证
    • 注册的ip
    • 注册的时间
    • 更新的时间
    • 注销的时间
    • 最后一次登录的ip
    • 最后一次登录的时间

    1.1.3 代码

    1.1.3.1 数据库users表模型
    // 使用gorm连接和管理数据库
    // models/models.go
    type User struct {
        gorm.Model
        Username      string     `json:"username" gorm:"type:varchar(20);not null;unique"`
        Password      string     `json:"password" gorm:"not null;type:varchar(256)"`
        Email         string     `json:"email" gorm:"type:varchar(60);not null;unique"`
        Phone         string     `json:"phone" gorm:"type:varchar(20);not null;unique"`
        IsAdmin       bool       `json:"is_admin" gorm:"type=boolean;not null;defult=false"`
        IsWriteOff    bool       `json:"is_write_off" gorm:"type=boolean;not null;defult=false"`
        IsVerified    bool       `json:"is_verify" gorm:"type=boolean;not null;defult=false"`
        RegisterIP    string     `json:"register_ip" gorm:"type:varchar(15);not null"`
        LastLoginTime *time.Time `json:"last_login_time"`
        LastLoginIP   string     `json:"last_login_ip" gorm:"type:varchar(15)"`
        Blogs         []Blog  // 见 1.2,user与blog为一对多的关系
    }
    
    1.1.3.2 注册

    注册时,要考虑到几点:

    1. 验证前端传来的注册信息
    2. 验证邮件的发送
    3. 以密文保存密码
    4. 是普通用户还是管理员用户
    5. 唯一字段重复时的错误响应
    6. 注册成功的响应

    注册的函数:

    // apis/user.go
    func Register(c *fiber.Ctx) error {
        var registerJSON models.RegisterJSON
        if err := c.BodyParser(&registerJSON); err != nil {
            return utils.ErrorJSON(c, fiber.StatusForbidden, err)
        }
    
        if err := registerJSON.Validate(); err != nil {
            return utils.ErrorJSON(c, fiber.StatusForbidden, err)
        }
    
        user := registerJSON.User
        user.IsAdmin = utils.IsAdmin(user.Username)
        user.RegisterIP = c.IP()
    
        user.IsVerified = utils.ValidateCodeIsValid(registerJSON.Email, registerJSON.ValidateCode)
    
        db := utils.GetDB()
    
        password, err := utils.GeneratePassword(user.Password)
        if err != nil {
            return utils.ErrorJSON(c, fiber.StatusForbidden, err)
        }
        user.Password = password
    
        result := db.Create(&user)
        if result.Error != nil {
            db.Rollback()
            errStr := result.Error.Error()
            if strings.Contains(errStr, "Error 1062: Duplicate entry") {
                err := utils.DatabaseExistError(errStr)
                return utils.ErrorJSON(c, fiber.StatusForbidden, err)
            }
    
        }
    
        return c.Status(fiber.StatusCreated).JSON(fiber.Map{
            "status":      fiber.StatusCreated,
            "msg":         "Account registration is successful",
            "username":    user.Username,
            "user_id":     user.ID,
            "is_verified": user.IsVerified,
        })
    }
    
    // models/json.go
    type RegisterJSON struct {
        User
        ValidateCode string `json:"validate_code"`
    }
    
    func (rj RegisterJSON) Validate() error {
            // validation验证使用的是ozzo-validation
        return validation.ValidateStruct(&rj,
            validation.Field(&rj.Username, validation.Required, validation.Length(3, 20)),
            validation.Field(&rj.Password, validation.Required, validation.Length(8, 40)),
            validation.Field(&rj.Email, validation.Required, is.Email, validation.Length(5, 40)),
            // 手机号的正则规则待改进
            validation.Field(&rj.Phone,
                validation.Required,
                validation.Match(regexp.MustCompile("^1[0-9]{10}$")).Error("must be a string with 11 digits"),
            ),
            validation.Field(&rj.ValidateCode, validation.Required, validation.Length(6, 6)),
        )
    }
    
    // utils/user.go
    func IsAdmin(username string) bool {
        for _, u := range blogConfig.Admins {
            if username == u {
                return true
            }
        }
        return false
    }
    
    func ValidateCodeIsValid(email, code string) bool {
        conn := redisPool.Get()
        defer conn.Close()
    
        res, err := redis.String(conn.Do("Get", email))
        if err != nil {
            return false
        }
    
        if code != res {
            return false
        }
    
        // 验证成功后,不管是否成功删除,都执行一次删除操作
        go func() {
            conn.Do("DEL", email)
        }()
    
        return true
    }
    
    func GeneratePassword(pwd string) (string, error) {
        hash, err := bcrypt.GenerateFromPassword([]byte(pwd), bcrypt.DefaultCost)
        if err != nil {
            return "", err
        }
        return string(hash), nil
    }
    
    func DatabaseExistError(errStr string) error {
        errInfo := strings.Split(errStr, "'")
    
        errCol := strings.Split(errInfo[3], ".")
        var res string
        if strings.Contains(errInfo[3], ".") {
            res = fmt.Sprintf("The `%s` you entered alreadyd exists: [ %s ]", errCol[1], errInfo[1])
        } else {
            res = fmt.Sprintf("The `%s` you entered alreadyd exists: [ %s ]", errCol[0], errInfo[1])
        }
        return errors.New(res)
    }
    
    // utils/common.go
    func ErrorJSON(c *fiber.Ctx, statusCode int, err error) error {
        return c.Status(statusCode).JSON(&models.ErrorResponse{
            Error: err.Error(),
        })
    }
    

    相关文章

      网友评论

        本文标题:用fiber写一个restful的博客后端

        本文链接:https://www.haomeiwen.com/subject/wjqdzktx.html