diff --git a/danke/api/course_group.go b/danke/api/course_group.go index 25c5c0b..35ef294 100644 --- a/danke/api/course_group.go +++ b/danke/api/course_group.go @@ -2,6 +2,7 @@ package api import ( "errors" + "github.com/gofiber/fiber/v2" . "github.com/opentreehole/backend/common" . "github.com/opentreehole/backend/danke/model" @@ -148,7 +149,12 @@ func SearchCourseGroupV3(c *fiber.Ctx) (err error) { if CourseCodeRegexp.MatchString(query) { querySet = querySet.Where("code LIKE ?", query+"%") } else { - querySet = querySet.Where("name LIKE ?", "%"+query+"%") + queryWord :="%"+query+"%" + querySet = querySet. + Joins("JOIN teacher_course_groups tcg ON course_group.id = tcg.course_group_id"). + Joins("JOIN teacher t ON tc.teacher_id = t.id"). + Where("t.name like ? OR course_group.name LIKE ?", queryWord, queryWord). + Group("id") } if page > 0 { if pageSize == 0 { diff --git a/danke/model/course_group.go b/danke/model/course_group.go index 2a40b24..96a0fca 100644 --- a/danke/model/course_group.go +++ b/danke/model/course_group.go @@ -3,12 +3,13 @@ package model import ( "context" "encoding/base64" + "regexp" + "time" + "github.com/eko/gocache/lib/v4/store" "github.com/opentreehole/backend/common" "github.com/vmihailenco/msgpack/v5" "golang.org/x/crypto/sha3" - "regexp" - "time" ) // CourseGroup 课程组 @@ -24,6 +25,7 @@ type CourseGroup struct { CourseCount int `json:"course_count" gorm:"not null;default:0"` // 课程数量 ReviewCount int `json:"review_count" gorm:"not null;default:0"` // 评价数量 Courses CourseList `json:"courses"` + Teachers []*Teacher `gorm:"many2many:teacher_course_groups;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` } var CourseCodeRegexp = regexp.MustCompile(`^([A-Z]{3,})([0-9]{2,})`) diff --git a/danke/model/init.go b/danke/model/init.go index 47092f0..580a0d0 100644 --- a/danke/model/init.go +++ b/danke/model/init.go @@ -40,6 +40,7 @@ func Init() { err = DB.AutoMigrate( &CourseGroup{}, &Course{}, + &Teacher{}, &Review{}, &ReviewHistory{}, &Achievement{}, diff --git a/danke/model/teacher.go b/danke/model/teacher.go new file mode 100644 index 0000000..f775338 --- /dev/null +++ b/danke/model/teacher.go @@ -0,0 +1,7 @@ +package model + +type Teacher struct { + ID int `json:"id"` + Name string `json:"name" gorm:"not null"` // 教师姓名 + CourseGroups []*CourseGroup `gorm:"many2many:teacher_course_groups;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` +} \ No newline at end of file diff --git a/danke_utils/main.go b/danke_utils/main.go new file mode 100644 index 0000000..4df7dbe --- /dev/null +++ b/danke_utils/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "log" + "os" + "time" + + "github.com/opentreehole/backend/common" + "github.com/spf13/viper" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" +) + +var GormConfig = &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, // 表名使用单数, `User` -> `user` + }, + DisableForeignKeyConstraintWhenMigrating: true, // 禁用自动创建外键约束,必须手动创建或者在业务逻辑层维护 + Logger: logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, // 慢 SQL 阈值 + LogLevel: logger.Error, // 日志级别 + IgnoreRecordNotFoundError: true, // 忽略ErrRecordNotFound(记录未找到)错误 + Colorful: false, // 禁用彩色打印 + }, + ), +} + +var DB *gorm.DB + +func Init() { + viper.AutomaticEnv() + dbType := viper.GetString(common.EnvDBType) + dbUrl := viper.GetString(common.EnvDBUrl) + + var err error + + switch dbType { + case "mysql": + DB, err = gorm.Open(mysql.Open(dbUrl), GormConfig) + case "postgres": + DB, err = gorm.Open(postgres.Open(dbUrl), GormConfig) + default: + panic("db type not supported") + } + + if err != nil { + panic(err) + } +} + +func main() { + Init() + // Call any script as needed + // GenerateTeacherTable(DB) +} \ No newline at end of file diff --git a/danke_utils/teacher_table.go b/danke_utils/teacher_table.go new file mode 100644 index 0000000..a0c827b --- /dev/null +++ b/danke_utils/teacher_table.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "slices" + "strings" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +const ( + BatchSize = 1000 +) + +type Course struct { + ID int `json:"id"` + Name string `json:"name" gorm:"not null"` // 课程名称 + Code string `json:"code" gorm:"not null"` // 课程编号 + CodeID string `json:"code_id" gorm:"not null"` // 选课序号。用于区分同一课程编号的不同平行班 + Teachers string `json:"teachers" gorm:"not null"` + CourseGroupID int `json:"course_group_id" gorm:"not null;index"` // 课程组编号 +} + +type Teacher struct { + ID int + Name string `gorm:"not null"` // 课程组 ID +} + +type TeacherCourseLink struct { + TeacherID int `gorm:"primaryKey;autoIncrement:false"` + CourseGroupID int `gorm:"primaryKey;autoIncrement:false"` // 课程组编号 +} + +func AppendUnique[T comparable](slice []T, elems ...T) []T { + for _, elem := range elems { + if !slices.Contains(slice, elem) { + slice = append(slice, elem) + } + } + + return slice +} + +func GenerateTeacherTable(DB *gorm.DB) { + Init() + + // reader := bufio.NewReader(os.Stdin) + + dataMap := map[string][]int{} + + var queryResult []Course + query := DB.Table("course") + query.FindInBatches(&queryResult, BatchSize, func(tx *gorm.DB, batch int) error { + for _, course := range queryResult { + teacherList := strings.Split(course.Teachers, ",") + for _, name := range teacherList { + courseList, found := dataMap[name] + if found { + dataMap[name] = AppendUnique(courseList, course.CourseGroupID) + } else { + dataMap[name] = []int{course.CourseGroupID} + } + } + } + + fmt.Printf("Handled batchg %d\n", batch) + return nil + }) + + var teachers []*Teacher + for k := range dataMap { + teachers = append(teachers, &Teacher{Name: k}) + } + + // Avoid insertion failure due to duplication + DB.Clauses(clause.OnConflict{DoNothing: true}).Table("teacher").Create(teachers) + + var links []*TeacherCourseLink + for index, teacher := range teachers { + for _, cid := range dataMap[teacher.Name] { + links = append(links, &TeacherCourseLink{TeacherID: teacher.ID, CourseGroupID: cid}) + } + + // Submit every 100 teachers to avoid SQL being too long + if index%100 == 0 { + fmt.Printf("Inserted %d teachers\n", index) + + // Avoid insertion failure due to duplication + DB.Clauses(clause.OnConflict{DoNothing: true}).Table("teacher_course_groups").Create(links) + links = nil + } + } +}