分类 Go 下的文章

这个报错:

json: Unmarshal(non-pointer model.Submit)

出现在语句:

err := c.ShouldBind(codedata)

说明在调用 ShouldBind 时传入了一个 非指针类型 的参数,而它需要一个指针类型来把 JSON 数据反序列化进去。


问题详解:

在 Go 的 encoding/json 包(Gin 的绑定底层也是用它)中,json.Unmarshal 必须接收一个指针,否则无法修改原变量的值。

例如:

type Submit struct {
    Code string `json:"code"`
}

错误写法(非指针):

var codedata model.Submit // 这是值类型
err := c.ShouldBind(codedata) // 这里会报错:json: Unmarshal(non-pointer model.Submit)

正确写法(指针):

var codedata model.Submit
err := c.ShouldBind(&codedata) // 正确,传指针

正确写法总结:

var codedata model.Submit
err := c.ShouldBind(&codedata)
if err != nil {
    c.JSON(400, gin.H{"error": err.Error()})
    return
}

在 Go 语言中,flag 是一个用于解析命令行参数的标准库。通过这个库,你可以很方便地从命令行中获取用户传入的参数,比如字符串、整数、布尔值等。

主要用途:

处理命令行参数,例如:

go run main.go -name=Tom -age=25 -debug=true

基本使用示例:

package main

import (
    "flag"
    "fmt"
)

func main() {
    // 定义命令行参数
    name := flag.String("name", "default", "请输入用户名")
    age := flag.Int("age", 18, "请输入年龄")
    debug := flag.Bool("debug", false, "是否开启调试模式")

    // 解析命令行参数
    flag.Parse()

    fmt.Println("name:", *name)
    fmt.Println("age:", *age)
    fmt.Println("debug:", *debug)
}

常用函数:

函数用途
flag.String(name, default, usage)定义字符串参数
flag.Int(name, default, usage)定义整数参数
flag.Bool(name, default, usage)定义布尔参数
flag.Parse()开始解析命令行参数
flag.Args()获取非 flag 的参数
flag.NArg()获取非 flag 参数个数

需要注意,flag 默认只支持 -key=value-key value 的形式,不支持 --key 的双横线写法(这在某些 CLI 工具中是常见的)。

使用上面的写法获取到的变量是一个指针,所以在访问的时候如果要访问对应的值需要在变量之前加一个*号

如果想要直接得到变量也可以是用下面这种写法

var name string
flag.StringVar(&name, "name", "default", "请输入用户名")

这种写法是自己定义一个变量,然后将它的地址传进去


flag 后面的提示文字(也叫 usage 或说明文本)会在以下几种情况下自动显示:

1. 用户执行程序时传入了 h-help

这是最常见的触发方式,flag 包会自动帮你注册 -h--help 参数,触发时会显示所有参数的说明信息。例如:

go run main.go -h

输出会像这样:

Usage of /tmp/go-build.../main:
  -age int
        请输入年龄 (default 18)
  -debug
        是否开启调试模式 (default false)
  -name string
        请输入用户名 (default "default")

2. 程序中手动调用 flag.Usage()

你可以在代码里手动触发帮助信息显示,例如参数验证失败时:

if *name == "" {
    fmt.Println("name 参数不能为空")
    flag.Usage()
    os.Exit(1)
}

3. 覆盖默认的帮助输出内容(可选)

你也可以自定义 flag.Usage 的行为,比如美化输出:

flag.Usage = func() {
    fmt.Println("用法说明:")
    fmt.Println("  -name string    请输入用户名 (默认值:default)")
    fmt.Println("  -age int        请输入年龄 (默认值:18)")
    fmt.Println("  -debug          是否开启调试模式 (默认值:false)")
}

总结一下:

方式是否自动显示帮助信息
运行时使用 -h--help✅ 自动显示
手动调用 flag.Usage()✅ 手动触发
用户没有传必要参数❌ 不会自动显示(需要你手动判断并触发)

下面给出两种常见的做法,关键在于如何统计某个标签被多少篇文章引用:

方法一:使用 Join Table 直接查询统计

假设你的多对多关系存储在 article_tags 表中,并且标签模型(Tag)类似如下:

type Tag struct {
    ID   uint   `gorm:"primaryKey"`
    Name string
    // 其他字段...
}

那么在删除文章时,你可以通过查询 article_tags 表来统计当前标签是否被其他文章引用:

func DeletePost(uid string) error {
    var article Article
    db := sql.GetDB()

    // 预加载 Tags, 这样后续能遍历对应的标签
    if err := db.Where("uid = ?", uid).Preload("Tags").First(&article).Error; err != nil {
        return err
    }
    
    // 遍历每个标签,检查是否还存在其他文章关联
    for _, tag := range article.Tags {
        var count int64
        // 查询 join table 中该 tag 被其他文章引用的次数
        // 请确保 "article_tags" 为你实际使用的表名
        db.Table("article_tags").Where("tag_id = ?", tag.ID).Count(&count)
        if count == 1 {
            // 如果仅有一个关联,则删除该标签
            if err := db.Delete(&tag).Error; err != nil {
                return err
            }
        }
    }

    // 移除关联(删除 join table 中对应记录)
    if err := db.Model(&article).Association("Tags").Clear(); err != nil {
        return err
    }

    // 删除文章记录
    if err := db.Delete(&article).Error; err != nil {
        return err
    }
    return nil
}

方法二:借助 GORM Association Mode 的 Count 方法

如果你在标签的模型中也定义了与文章的关联(比如在 Tag 模型中加入 Articles []Article gorm:"many2many:article_tags;"`),你就可以利用 GORM 内置的 Association("Articles").Count()` 来查询关联数量。示例如下:

标签模型:

type Tag struct {
    ID       uint      `gorm:"primaryKey"`
    Name     string
    Articles []Article `gorm:"many2many:article_tags;"`
}

在删除文章的函数中:

func DeletePost(uid string) error {
    var article Article
    db := sql.GetDB()

    // 预加载 Tags,确保能遍历所有标签
    if err := db.Where("uid = ?", uid).Preload("Tags").First(&article).Error; err != nil {
        return err
    }

    // 先清除文章与标签的关联关系
    if err := db.Model(&article).Association("Tags").Clear(); err != nil {
        return err
    }

        // 检查每个标签是否已经没有文章关联,如果是则删除标签
    for _, tag := range article.Tags {
        // 使用 Association Mode 的 Count 方法
        count := db.Model(&tag).Association("Articles").Count()
        if count == 1 {
            if err := db.Delete(&tag).Error; err != nil {
                return err
            }
        }
    }
    
    // 删除文章记录
    if err := db.Delete(&article).Error; err != nil {
        return err
    }
    return nil
}

总结

  • 查询统计方式:可以直接根据 join table(如 article_tags)进行查询统计,代码简单直观,不需要在 Tag 模型中额外定义关联。
  • Association Count 方式:需要在 Tag 模型中定义反向的多对多关联,但借助 GORM 的 Association().Count() 方法,代码更加面向对象,调用简单。

两种方法各有优劣,可以根据你的项目情况(模型定义是否包含双向关联、代码风格偏好等)来选择。

在 Gin 中,不论是 DELETE 还是 GET、POST 等请求方式,底层都是通过传入一个 *gin.Context 来获取请求参数,因此它们在参数获取上使用的机制基本是一致的。

不过需要注意的是:

  • URL 参数和查询字符串参数:DELETE 接口可以同样通过路径参数(例如通过 :id 定义的参数)和查询字符串参数(使用 c.Query()c.DefaultQuery() 方法)来接收参数,这与 GET 接口没有区别。
  • 请求体参数:虽然 Gin 支持通过 c.Bind() 等方法解析 JSON、XML 或表单数据到结构体中(这在 POST 请求中比较常见),但 HTTP 规范中 DELETE 请求一般不建议携带请求体,所以如果需要通过请求体传参,可能会遇到客户端或服务器处理上的限制。实际上,Gin 并没有做出特殊处理,只是删除请求通常不包含 body,使用时需要根据实际需求选用合适的参数传递方式。

所以总结来说:Gin 中 DELETE 类型接口的参数接收方式在技术实现上与 GET、POST 是类似的,都依赖于 *gin.Context,但在具体使用时需要注意 DELETE 请求通常更偏向于使用路径或查询参数,而不推荐使用请求体。

在 Go 语言中,不能直接使用简单的类型转换将整型转换为字符串,因为 Go 是一种静态类型语言,变量的类型一经定义就不能随意改变。常见的做法有以下两种方法:

  1. 使用 fmt.Sprintf
    可借助 fmt.Sprintf 函数将整型格式化为字符串,例如:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        num := 123
        str := fmt.Sprintf("%d", num) // %d 表示十进制整数
        fmt.Println(str) // 输出 "123"
    }
    

    这种方法灵活性较高,可以轻松实现不同进制的转换.

  2. 使用 strconv.Itoa
    Go 标准库 strconv 提供了 Itoa(Integer to ASCII 的缩写)函数,专门用于将整型转换为字符串:

    package main
    
    import (
        "fmt"
        "strconv"
    )
    
    func main() {
        num := 123
        str := strconv.Itoa(num)
        fmt.Println(str) // 输出 "123"
    }
    

    这种方法更加直接简单,适用于需要将整数转换为十进制字符串的场景。

两种方法各有优劣,根据实际业务需求选择合适的方案即可.