给老板打打小工…………

需求


  • 批量导入项目学生污点

    实现要求 :

    1. 可以通过excel导入学生的成绩
    2. 可以通过学生的Id
    3. 可以通过学年-学期-班级导入(未实现)

    同时需要实现

    1. 导入成绩是批量导入的,且每批学生都要有个batch,可以直接删除某批学生
    2. 当学生的id重复时,需要提醒老师是合并还是连接污点

    APIfox接口 /project/{id}/user/batch

    form-data :

    参数名 参数值 说明
    file upload(excel) 上传excel文件
    uploadType id/excel/class 分别对应三种上传形式
    year 学年
    class 班级
    semester 学期
    coverType merge/cover 覆盖/添加
    data [{“staff_id”:”55600”,”taints”:[“a”,”b”]},{“staff_id”:”300”,”taints”:[“a”,”b”]}] 学号+污点切片

    Param:

    id : ……………….(这里是项目id)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    //相应格式
    {
    "code": 0,
    "message": "success",
    "data": [
    {
    "ID": 63,
    "CreatedAt": "2024-02-19T00:04:43.5914489+08:00",
    "UpdatedAt": "2024-02-19T00:04:43.5914489+08:00",
    "DeletedAt": null,
    "ProjectId": "13",
    "StaffId": "55600",
    "Batch": "1708272283-860",
    "Taints": [
    {
    "ID": 63,
    "CreatedAt": "2024-02-19T00:04:43.5945104+08:00",
    "UpdatedAt": "2024-02-19T00:04:43.5945104+08:00",
    "DeletedAt": null,
    "ProjectId": "13",
    "StaffId": "55600",
    "Name": "a,b"
    }
    ]
    },
    {
    "ID": 64,
    "CreatedAt": "2024-02-19T00:04:43.6123751+08:00",
    "UpdatedAt": "2024-02-19T00:04:43.6123751+08:00",
    "DeletedAt": null,
    "ProjectId": "13",
    "StaffId": "300",
    "Batch": "1708272283-215",
    "Taints": [
    {
    "ID": 64,
    "CreatedAt": "2024-02-19T00:04:43.615909+08:00",
    "UpdatedAt": "2024-02-19T00:04:43.615909+08:00",
    "DeletedAt": null,
    "ProjectId": "13",
    "StaffId": "300",
    "Name": "a,b"
    }
    ]
    }
    ]
    }
    • 其实应该做成一个学号下面全是污点的类型的,但是我不会。。。
  • 批量删除项目学生污点

    接口 /project/{id}/user/batch

    Body/JSON :

    1
    2
    3
    >{
    "batch":"1708269266-923"
    >}

    param: 与上面相同

    1
    2
    3
    4
    5
    6
    //响应格式
    {
    "code": 0,
    "message": "success",
    "data": "删除成功"
    }

代码实现


路由

1
2
3
// internal/app/project/router/project.go
e.Post("/batch", handler.HandleBatchImportStudent) // 批量导入项目学生
e.Delete("/batch", binding.JSON(dto.BatchDeleteStudent{}), handler.HandleBatchDeleteStudent) // 批量删除项目学生

模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// internal/app/project/model/project.go
type ProjectUser struct {
gorm.Model
ProjectId string `gorm:"uniqueIndex:idx_project_staff;type:char(26);comment:项目id"`
StaffId string `gorm:"uniqueIndex:idx_project_staff;index;size:19;comment:用户工号"`
Batch *string `gorm:"comment:批次"` // 每次导入的数据都会有一个批次号,用于在误操作时进行数据回滚
Taints []ProjectUserTaint `gorm:"foreignKey:ProjectId,StaffId;references:ProjectId,StaffId;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;comment:污点"`
}

type ProjectUserTaint struct {
gorm.Model
ProjectId string `gorm:"type:char(26);comment:项目id"`
StaffId string `gorm:"size:19;comment:用户工号"`
Name string `gorm:"comment:污点名称"`
}

  • 这边有个uniqueIndex,非常恶心,这个表示索引唯一性,就是她这边ProjectId,StaffId是索引,因此两个索引都不能重复
  • has many : 这里使用了has many的映射关系,官网查了好久反正都没什么用,ProjectUser是父表,ProjectUserTaint是子表,不能擅自修改子表的数据,因此不可能直接存入数据。具体用处就是一对多,具体操作就是你把ProjectUserTaint插到ProjectUser的Taints切片中,然后再把ProjectUser保存到数据库,ProjectUserTaint就会自动保存到数据库中,这就是外键引用

导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// internal/app/project/handler/project.go
func HandleBatchImportStudent(c flamego.Context, r flamego.Render) {
uploadType := c.Request().FormValue("uploadType")
projectID := c.Param("id")
if uploadType == "excel" {
err, slice := service2.ParseExcel(c, projectID)
if err != nil {
response.HTTPFail(r, 400001, "上传失败", err)
return
}
response.HTTPSuccess(r, slice)
} else if uploadType == "id" {
err, projectUser := service2.UploadById(c, projectID)
if err != nil {
response.HTTPFail(r, 400001, "上传失败", err)
return
}
response.HTTPSuccess(r, projectUser)
} else if uploadType == "class" {
c.Request().FormValue("class")
c.Request().FormValue("year")
c.Request().FormValue("semester")
//未实现
} else {
response.HTTPFail(r, 400001, "上传类型错误")
}
}

三种类型,三个判断

JSON输入

1
2
3
4
5
6
7
8
9
// internal/app/project/dto/project.go
type UploadTaint struct {
StaffId string `json:"staff_id" binding:"required"`
Taints []string `json:"taints" binding:"required"`
}

type BatchDeleteStudent struct {
Batch string `json:"batch" valid:"required"`
}

这个就是dto包的作用,就是通过自定义结构体来吸收传入的JSON或者form-data数据,非常适用,重点

excel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// internal/app/project/service/gw/service.go
func ParseExcel(c flamego.Context, pid string) (error, []model.ProjectUser) {
f, _, err := c.Request().FormFile("file")
if err != nil {
return err, nil
}
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()

xlsx, err := excelize.OpenReader(f)
if err != nil {
fmt.Println("无法读取文件")
return err, nil
}
var slice []model.ProjectUser
sheetList := xlsx.GetSheetList()
for _, sheet := range sheetList {
rows, err := xlsx.GetRows(sheet)
if err != nil {
return err, nil
}
for i, row := range rows {
if i > 0 {
projectUserTaint := model.ProjectUserTaint{
ProjectId: pid,
StaffId: row[0],
Name: row[1],
}
tt := []model.ProjectUserTaint{}
projectUser := model.ProjectUser{
ProjectId: pid,
StaffId: row[0],
Batch: dao.GenBatch(),
Taints: append(tt, projectUserTaint),
}
err := dao.Project.SaveProjectUser(projectUser)
if err != nil {
return err, nil
}
slice = append(slice, projectUser)
}
}
}
return nil, slice
}

具体怎么操作就问GPT好了,很简单,就是第一行不录入(输入学号/污点),后面录入第一列和第二列,同时还可以获取每张表(sheet)遍历

id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// internal/app/project/service/gw/service.go
func UploadById(c flamego.Context, projectID string) (error, []model.ProjectUser) {
v := c.Request().FormValue("data")
taintData := []dto.UploadTaint{}
err := json.Unmarshal([]byte(v), &taintData)
if err != nil {
return err, nil
}
slice := make([]model.ProjectUser, 0)
for _, t := range taintData {
if dao.Project.IfExistProjectUser(t.StaffId, projectID) {
ct := c.Request().FormValue("coverType")
if ct == "cover" {
var projectUser model.ProjectUser
err := dao.Project.DB.Where("project_id = ? and staff_id = ?", projectID, t.StaffId).First(&projectUser).Error
if err != nil {
return err, nil
}
projectUser.Batch = dao.GenBatch()
err = dao.Project.DB.Save(&projectUser).Error
if err != nil {
return err, nil
}
slice = append(slice, projectUser)
} else if ct == "merge" {
var projectUser model.ProjectUser
err := dao.Project.DB.Where("project_id = ? and staff_id = ?", projectID, t.StaffId).First(&projectUser).Error
if err != nil {
return err, nil
}
tt := projectUser.Taints
tt = append(tt, model.ProjectUserTaint{
ProjectId: projectID,
StaffId: t.StaffId,
Name: strings.Join(t.Taints, ","),
})
projectUser.Taints = append(projectUser.Taints, model.ProjectUserTaint{
ProjectId: projectID,
StaffId: t.StaffId,
Name: strings.Join(t.Taints, ","),
})
projectUser.Batch = dao.GenBatch()
err = dao.Project.DB.Save(&projectUser).Error
if err != nil {
return err, nil
}
slice = append(slice, projectUser)
}
} else {
err, p := dao.Project.TaintSaving(t, projectID)
if err != nil {
return err, nil
} else {
slice = append(slice, *p)
}
}
}
return nil, slice
}

具体可以看一下两张表之间的关系操作

还可以看一下判断是否重复的逻辑操作

存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// internal/app/project/dao/project.go
var (
Project = &project{}
)

func InitPG(db *gorm.DB) error {
err := Project.Init(db)
if err != nil {
return err
}

return err
}
func (u *project) TaintSaving(t dto.UploadTaint, projectId string) (error, *model.ProjectUser) {
projectUserTaint := model.ProjectUserTaint{
ProjectId: projectId,
StaffId: t.StaffId,
Name: strings.Join(t.Taints, ","),
}
tt := make([]model.ProjectUserTaint, 0)
projectUser := model.ProjectUser{
ProjectId: projectId,
StaffId: t.StaffId,
Batch: GenBatch(),
Taints: append(tt, projectUserTaint),
}

tx := u.DB.Create(&projectUser)
if tx.Error != nil {
return tx.Error, nil
}

return nil, &projectUser
}
// 生成Batch
func GenBatch() *string {
timeStamp := time.Now().Unix()
randomNum := rand.Intn(1000)
s := fmt.Sprintf("%d-%03d", timeStamp, randomNum)
return &s
}
func (u *project) BatchDeleteStudent(batch string) error {
return u.DB.Where("batch = ?", batch).Delete(&model.ProjectUser{}).Error
}

非常巧妙的使用了方法,多学习学习

删除

学会了上面的,下面的非常简单,可以略过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// internal/app/project/handler/project.go
func HandleBatchDeleteStudent(c flamego.Context, b dto.BatchDeleteStudent, r flamego.Render) {
//获取JSON请求的数据
batch := b.Batch
err := dao.Project.BatchDeleteStudent(batch)
if err != nil {
response.ServiceErr(r, err)
return
}
response.HTTPSuccess(r, "删除成功")
}
func (u *project) BatchDeleteStudent(batch string) error {
return u.DB.Where("batch = ?", batch).Delete(&model.ProjectUser{}).Error
}

结构


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
internal/app/

│ │ ├─project
│ │ │ │ init.go
│ │ │ │
│ │ │ ├─dao
│ │ │ │ init.go
│ │ │ │ project.go
│ │ │ │
│ │ │ ├─dto
│ │ │ │ project.go
│ │ │ │
│ │ │ ├─handler
│ │ │ │ project.go
│ │ │ │
│ │ │ ├─model
│ │ │ │ project.go
│ │ │ │ rule.go
│ │ │ │
│ │ │ ├─router
│ │ │ │ project.go
│ │ │ │
│ │ │ └─service
│ │ │ ├─gw
│ │ │ │ service.go
│ │ │ │
│ │ │ └─projectservice
│ │ │ service.go
│ │ │
│ │

这是具体一个包的结构

  • dao

    数据库处理,数据的CRUD操作写这里

  • dto

    JSON格式的结构体设定

  • handler

    路由函数的设置地点

  • model

    结构体模型,存入数据库的

  • router

    路由

  • sevice

    一些在路由函数到数据库之间的中间操作放这里,比如ParseExcel等

总结


  1. 学习了GORM一些复杂的关系,真的好难,要试过才知道,只看文档看不懂
  2. colipot和GPT的使用,而不是依赖和滥用
  3. 关键使用的debug–今天我终于学会debug了,每一步debug就像刮彩票,超级激动
  4. 这个东西从APIfox文档编写到功能的差不多实现,差不多花了一天时间–真的是整整一天的时间,昨天晚上学到四点钟,十一点钟爬起来吃了个饭学到现在,只能说老板牛逼