gorm之项目实战
ER图
关系整理
-
一对一关系:
- User 和 UserLog: 一个用户对应一个用户日志,通过
User
模型的主键与UserLog
模型的外键建立一对一关系。
- User 和 UserLog: 一个用户对应一个用户日志,通过
-
一对多关系:
- User 和 Teacher: 一个用户可以对应多个老师,通过
Teacher
模型的外键(UserID
)与User
模型的主键建立一对多关系。 - User 和 Student: 一个用户可以对应多个学生,通过
Student
模型的外键(UserID
)与User
模型的主键建立一对多关系。 - Teacher 和 Class: 一个老师可以有多个班级,通过
Class
模型的外键(TeacherID
)与Teacher
模型的主键建立一对多关系。 - Student 和 StudentClass: 一个学生可以有多个班级,通过
StudentClass
模型的外键(StudentID
)与Student
模型的主键建立一对多关系。 - Student 和 Attendance: 一个学生可以有多条考勤记录,通过
Attendance
模型的外键(StudentID
)与Student
模型的主键建立一对多关系。
- User 和 Teacher: 一个用户可以对应多个老师,通过
-
多对多关系:
- Student 和 Class: 一个学生可以属于多个班级,一个班级可以有多个学生,通过
StudentClass
模型作为中间表,建立多对多关系。 - Teacher 和 Class: 一个老师可以教授多个班级,一个班级可以有多个老师,通过
Class
模型的外键(TeacherID
)与Teacher
模型的主键,建立多对多关系。
- Student 和 Class: 一个学生可以属于多个班级,一个班级可以有多个学生,通过
-
一对多逆向关系:
- Parent 和 Student: 一个家长可以有多个子女,通过
Student
模型的外键(StudentID
)与Parent
模型的主键建立一对多逆向关系。
- Parent 和 Student: 一个家长可以有多个子女,通过
使用gen自动化生成代码
首先使用gen工具生成代码
package main
// gorm gen configure
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gen"
)
const MySQLDSN = "root:root@tcp(127.0.0.1:3306)/school?charset=utf8mb4&parseTime=True"
func connectDB(dsn string) *gorm.DB {
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic(fmt.Errorf("connect db fail: %w", err))
}
return db
}
func main() {
// 指定生成代码的具体相对目录(相对当前文件),默认为:./query
// 默认生成需要使用WithContext之后才可以查询的代码,但可以通过设置gen.WithoutContext禁用该模式
g := gen.NewGenerator(gen.Config{
// 默认会在 OutPath 目录生成CRUD代码,并且同目录下生成 model 包
// 所以OutPath最终package不能设置为model,在有数据库表同步的情况下会产生冲突
// 若一定要使用可以通过ModelPkgPath单独指定model package的名称
OutPath: "dao/query",
/* ModelPkgPath: "dal/model"*/
// gen.WithoutContext:禁用WithContext模式
// gen.WithDefaultQuery:生成一个全局Query对象Q
// gen.WithQueryInterface:生成Query接口
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
})
// 通常复用项目中已有的SQL连接配置db(*gorm.DB)
// 非必需,但如果需要复用连接时的gorm.Config或需要连接数据库同步表信息则必须设置
g.UseDB(connectDB(MySQLDSN))
// 从连接的数据库为所有表生成Model结构体和CRUD代码
// 也可以手动指定需要生成代码的数据表
g.ApplyBasic(g.GenerateAllTable()...)
// 执行并生成代码
g.Execute()
}
在gen生成的model中定义外键联系
生成的model代码在dao/model
我们需要在这些Model中定义外键关系,先把User表and Teacher表与Student表关系定义
一对一关系
首先先确定主表和附表,主表
-
主表:User,逐渐:UserID
-
附表:Student,外键:UserID
-
附表:Teacher,外键:UserID
-
在model.User中添加两个
-
把generate改下,我们要用改过的模型生成query
-
g.ApplyBasic( model.Student{}, model.Teacher{}, model.User{}, model.UserLog{}, model.Class{}, model.Course{}, model.Attendance{}, model.StudentClass{}, model.Parent{}, ) // 执行并生成代码 g.Execute()
增加用户业务逻辑
这边按UserType新建对应的学生和教师
func CreateUser(c *gin.Context) {
var req request.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "创建用户请求失败,无效的请求参数"})
return
}
var user model.User
switch req.UserType {
case "Student":
user = model.User{
Username: req.Username,
Password: req.Password,
OpenID: req.OpenID,
Avatar: req.Avatar,
LastLogin: time.Now(),
UserType: req.UserType,
IsValid: req.IsValid,
Student: model.Student{
StudentName: req.Username,
},
}
case "Teacher":
user = model.User{
Username: req.Username,
Password: req.Password,
OpenID: req.OpenID,
Avatar: req.Avatar,
LastLogin: time.Now(),
UserType: req.UserType,
IsValid: req.IsValid,
Teacher: model.Teacher{
TeacherName: req.Username,
},
}
default:
c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "无效的用户类型"})
return
}
err := query.User.WithContext(context.Background()).Create(&user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": fmt.Sprintf("创建用户请求失败,无法创建用户: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "用户创建成功"})
}
删除用户业务逻辑
// DeleteUser 处理删除用户请求的函数
func DeleteUser(c *gin.Context) {
userIDStr := c.Param("id") // Assuming the route has "id" parameter
var User model.User
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Failed to delete user, invalid user ID"})
return
}
//var user model.User
config.GVA_DB.Take(&User, userID)
ret := config.GVA_DB.Select("Student").Delete(&User)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": fmt.Sprintf("Failed to delete user, unable to delete user: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": fmt.Sprintf("User deleted successfully, RowsAffected: %v", ret.RowsAffected)})
}
更新
这里因为是主表,逻辑上之更新注表就好,后面改相应表的在编写逻辑
主要是改改头像什么的
查找
// GetUser 处理获取单个用户请求的函数
func GetUser(c *gin.Context) {
userIDStr := c.Param("id") // Assuming the route has "id" parameter
u := query.User
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"code": http.StatusBadRequest, "error": "Failed to get user, invalid user ID"})
return
}
user, err := query.User.WithContext(context.Background()).Where(query.User.UserID.Eq(int32(userID))).Preload(u.Student, u.Teacher).First()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": fmt.Sprintf("Failed to get user, unable to get user: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "user": user})
}
// GetAllUsers 处理获取所有用户请求的函数
func GetAllUsers(c *gin.Context) {
u := query.User
users, err := query.User.WithContext(context.Background()).Preload(u.Student, u.Teacher).Find()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "error": fmt.Sprintf("Failed to get all users, unable to get user list: %v", err)})
return
}
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "users": users})
}