Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/search #6

Merged
merged 8 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func main() {
_ = godotenv.Load()
dal.InitDBClient()
db := dal.GetDBClient()
// note: po with search index require manual migration
err := db.AutoMigrate(&po.UserPO{},
&po.BaseCoursePO{}, &po.CoursePO{}, &po.TeacherPO{}, &po.CourseCategoryPO{},
&po.OfferedCoursePO{}, &po.OfferedCourseTeacherPO{},
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/bytedance/sonic v1.12.1
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0
github.com/gin-gonic/gin v1.10.0
github.com/go-ego/gse v0.80.3
github.com/go-redis/redismock/v9 v9.2.0
github.com/gorilla/csrf v1.7.2
github.com/gwatts/gin-adapter v1.0.0
Expand Down Expand Up @@ -63,6 +64,7 @@ require (
github.com/spf13/cast v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/vcaesar/cedar v0.20.2 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.25.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0 h1:EUFmvQ8ffefnS
github.com/gin-gonic/contrib v0.0.0-20240508051311-c1c6bf0061b0/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
Expand Down Expand Up @@ -164,6 +166,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
Expand Down
2 changes: 2 additions & 0 deletions model/domain/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type TrainingPlanFilter struct {
Department string
EntryYear string
ContainCourseIDs []int64
SearchQuery string
}

type BaseCourse struct {
Expand All @@ -80,4 +81,5 @@ type CourseListFilter struct {
Departments []string
Categories []string
Credits []float64
SearchQuery string
}
13 changes: 7 additions & 6 deletions model/domain/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package domain
import "time"

type ReviewFilter struct {
Page int64
PageSize int64
CourseID int64
Semester string
UserID int64
ReviewID int64
Page int64
PageSize int64
CourseID int64
Semester string
UserID int64
ReviewID int64
SearchQuery string
}

type Review struct {
Expand Down
1 change: 1 addition & 0 deletions model/domain/teacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ type TeacherListFilter struct {
Pinyin string
PinyinAbbr string
ContainCourseIDs []int64
SearchQuery string
}
5 changes: 3 additions & 2 deletions model/domain/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ type UserProfile struct {
}

type UserFilter struct {
Page int64
PageSize int64
Page int64
PageSize int64
SearchQuery string
// To be continued ... (add more fields)
}
1 change: 1 addition & 0 deletions model/dto/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type CourseListRequest struct {
Departments string `json:"departments" form:"departments"`
Categories string `json:"categories" form:"categories"`
Credits string `json:"credits" form:"credits"`
SearchQuery string `json:"search_query" form:"search_query"`
}

type CourseListResponse = BasePaginateResponse[CourseListItemDTO]
5 changes: 3 additions & 2 deletions model/dto/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ type CreateReviewResponse struct {
}

type ReviewListRequest struct {
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
SearchQuery string `json:"search_query" form:"search_query"`
}

type ReviewListResponse = BasePaginateResponse[ReviewDTO]
Expand Down
17 changes: 9 additions & 8 deletions model/dto/teacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ type TeacherDetailRequest struct {
}

type TeacherListRequest struct {
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"`
Department string `json:"departments" form:"departments"`
Title string `json:"title" form:"title"`
Pinyin string `json:"pinyin" form:"pinyin"`
PinyinAbbr string `json:"pinyin_abbr" form:"pinyin_abbr"`
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
Name string `json:"name" form:"name"`
Code string `json:"code" form:"code"`
Department string `json:"departments" form:"departments"`
Title string `json:"title" form:"title"`
Pinyin string `json:"pinyin" form:"pinyin"`
PinyinAbbr string `json:"pinyin_abbr" form:"pinyin_abbr"`
SearchQuery string `json:"search_query" form:"search_query"`
}

type TeacherListResponse = BasePaginateResponse[TeacherDTO]
1 change: 1 addition & 0 deletions model/dto/trainingplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type TrainingPlanListQueryRequest struct {
SortBy string `json:"sort_by" form:"sort_by"`
Page int `json:"page" binding:"required" form:"page"`
PageSize int `json:"page_size" binding:"required" form:"page_size"`
SearchQuery string `json:"search_query" form:"search_query"`
}
type TrainingPlanListRequest struct {
Page int `json:"page" binding:"required" form:"page"`
Expand Down
5 changes: 3 additions & 2 deletions model/dto/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ type UserRole = string
type UserType = string

type UserListRequest struct {
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
Page int64 `json:"page" form:"page"`
PageSize int64 `json:"page_size" form:"page_size"`
SearchQuery string `json:"search_query" form:"search_query"`
}

type UserListResponse = BasePaginateResponse[UserDetailDTO]
Expand Down
2 changes: 2 additions & 0 deletions model/po/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type CoursePO struct {
MainTeacherID int64 `gorm:"index;index:uniq_course,unique"`
MainTeacherName string `gorm:"index"`
Department string `gorm:"index;index:uniq_course,unique"`

SearchIndex SearchIndex // `gorm:"index:idx_search, type:gin"`
}

func (po *CoursePO) TableName() string {
Expand Down
1 change: 1 addition & 0 deletions model/po/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type ReviewPO struct {
Rating int64 `gorm:"index"`
Semester string `gorm:"index;index:uniq_course_review,unique"`
IsAnonymous bool
SearchIndex SearchIndex // `gorm:"index:idx_search, type:gin"`
}

func (po *ReviewPO) TableName() string {
Expand Down
84 changes: 84 additions & 0 deletions model/po/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package po

import (
"context"
"database/sql/driver"
"jcourse_go/util"
"strings"

"gorm.io/gorm"
"gorm.io/gorm/clause"
)

type SearchIndex string

// ref: https://gorm.io/zh_CN/docs/data_types.html

// warning: need manual migeration!
func (i *SearchIndex) Scan(value interface{}) error { return nil }
func (i SearchIndex) Value() (driver.Value, error) { return nil, nil }
func (i SearchIndex) GormDataType() string { return "tsvector" }
Comment on lines +17 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

为啥一个接收器是指针,另外几个不是

Copy link
Contributor Author

@wsm25 wsm25 Aug 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://gorm.io/docs/data_types.html 因为文档如此 抄的(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

行吧,IDE和lint如果没有警告的话就这样了


func (i SearchIndex) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return clause.Expr{
SQL: "to_tsvector('simple', ?)",
Vars: []interface{}{string(i)},
}
}

func toIndex(fields []string) SearchIndex {
var sb strings.Builder
for _, field := range fields {
for _, segment := range util.Fenci(field) {
sb.WriteString(segment)
sb.WriteByte(' ')
}
}
return SearchIndex(sb.String())
}

func (c *CoursePO) BeforeCreate(*gorm.DB) error {
c.SearchIndex = toIndex([]string{
c.Name,
c.Code, // 前缀模糊匹配更为适合
c.MainTeacherName,
c.Department, // 不分词更为适合
})
return nil
}
func (c *CoursePO) BeforeSave(tx *gorm.DB) error {
return c.BeforeCreate(tx)
}

func (t *TeacherPO) BeforeCreate(*gorm.DB) error {
t.SearchIndex = toIndex([]string{
t.Name,
t.Department,
t.Code,
})
return nil
}
func (t *TeacherPO) BeforeSave(tx *gorm.DB) error {
return t.BeforeCreate(tx)
}

func (t *TrainingPlanPO) BeforeCreate(*gorm.DB) error {
t.SearchIndex = toIndex([]string{
t.Major,
t.Department,
})
return nil
}
func (t *TrainingPlanPO) BeforeSave(tx *gorm.DB) error {
return t.BeforeCreate(tx)
}

func (r *ReviewPO) BeforeCreate(*gorm.DB) error {
r.SearchIndex = toIndex([]string{
r.Comment,
})
return nil
}
func (r *ReviewPO) BeforeSave(tx *gorm.DB) error {
return r.BeforeCreate(tx)
}
2 changes: 2 additions & 0 deletions model/po/teacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type TeacherPO struct {
Picture string // picture URL
ProfileURL string
Biography string // 个人简述

SearchIndex SearchIndex // `gorm:"index:idx_search, type:gin"`
}

func (po *TeacherPO) TableName() string {
Expand Down
2 changes: 2 additions & 0 deletions model/po/trainingplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type TrainingPlanPO struct {
TotalYear int `gorm:"index;index:uniq_training_plan,unique"`
MinCredits float64 `gorm:"index;index:uniq_training_plan,unique"`
MajorClass string `gorm:"index;index:uniq_training_plan,unique"` // the class of major

SearchIndex SearchIndex // `gorm:"index:idx_search, type:gin"`
}

func (po *TrainingPlanPO) TableName() string {
Expand Down
2 changes: 2 additions & 0 deletions model/po/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type UserPO struct {
Password string `gorm:"index:idx_auth"`
UserRole string `gorm:"index"` // 用户在选课社区的身份
LastSeenAt time.Time

SearchIndex SearchIndex // `gorm:"index:idx_search, type:gin"`
}

func (po *UserPO) TableName() string {
Expand Down
1 change: 1 addition & 0 deletions repository/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ type ICourseQuery interface {
WithMainTeacherID(id int64) DBOption
WithLimit(limit int64) DBOption
WithOffset(offset int64) DBOption
WithSearch(query string) DBOption
}

type CourseQuery struct {
Expand Down
1 change: 1 addition & 0 deletions repository/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type IReviewQuery interface {
WithOrderBy(orderBy string, ascending bool) DBOption
WithLimit(limit int64) DBOption
WithOffset(offset int64) DBOption
WithSearch(query string) DBOption
}

type ReviewQuery struct {
Expand Down
49 changes: 49 additions & 0 deletions repository/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package repository

import (
"jcourse_go/util"
"strings"

"gorm.io/gorm"
)

func (*CourseQuery) WithSearch(query string) DBOption { return withSearch(query) }
func (*ReviewQuery) WithSearch(query string) DBOption { return withSearch(query) }
func (*TeacherQuery) WithSearch(query string) DBOption { return withSearch(query) }
func (*TrainingPlanQuery) WithSearch(query string) DBOption { return withSearch(query) }

func withSearch(query string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("search_index @@ to_tsquery('simple', ?)",
userQueryToTsQuery(query),
)
}
}

// 目前只搜用户名
func (*UserQuery) WithSearch(query string) DBOption {
return func(db *gorm.DB) *gorm.DB {
return db.Where("name LIKE ?", query+"%")
}
}

// 空格分割的每个词都要匹配,词内分词做模糊匹配
func userQueryToTsQuery(query string) string {
var sb strings.Builder
words := strings.Fields(query)
for i, word := range words {
if i != 0 {
sb.WriteString(" & ")
}
sb.WriteByte('(')
segs := util.Fenci(word)
for j, seg := range segs {
if j != 0 {
sb.WriteString(" | ")
}
sb.WriteString(seg)
}
sb.WriteByte(')')
}
return sb.String()
}
1 change: 1 addition & 0 deletions repository/teacher.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type ITeacherQuery interface {
WithProfileURL(profileURL string) DBOption
WithIDs(ids []int64) DBOption
WithPaginate(page int64, pageSize int64) DBOption
WithSearch(query string) DBOption
}

type TeacherQuery struct {
Expand Down
1 change: 1 addition & 0 deletions repository/trainingplan.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type ITrainingPlanQuery interface {
WithDegree(degree string) DBOption
WithIDs(courseIDs []int64) DBOption
WithPaginate(page int64, pageSize int64) DBOption
WithSearch(query string) DBOption
}

func NewTrainingPlanQuery() ITrainingPlanQuery {
Expand Down
1 change: 1 addition & 0 deletions repository/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type IUserQuery interface {
ResetUserPassword(ctx context.Context, userID int64, password string) error
WithLimit(limit int64) DBOption
WithOffset(offset int64) DBOption
WithSearch(query string) DBOption
}

type IUserProfileQuery interface {
Expand Down
3 changes: 3 additions & 0 deletions service/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func buildCourseDBOptionFromFilter(query repository.ICourseQuery, filter domain.
if len(filter.Credits) > 0 {
opts = append(opts, query.WithCredits(filter.Credits))
}
if filter.SearchQuery != "" {
opts = append(opts, query.WithSearch(filter.SearchQuery))
}
return opts
}

Expand Down
3 changes: 3 additions & 0 deletions service/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ func buildReviewDBOptionFromFilter(query repository.IReviewQuery, filter domain.
if filter.ReviewID != 0 {
opts = append(opts, query.WithID(filter.ReviewID))
}
if filter.SearchQuery != "" {
opts = append(opts, query.WithSearch(filter.SearchQuery))
}
return opts
}

Expand Down
Loading
Loading