From 12d2a6a26eccfc17fde4c2e931734a8c45cce7c4 Mon Sep 17 00:00:00 2001 From: Du Jiajun Date: Sat, 10 Aug 2024 17:16:50 +0800 Subject: [PATCH 01/19] fix: build --- model/converter/review.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model/converter/review.go b/model/converter/review.go index 0013bc4..d7772fa 100644 --- a/model/converter/review.go +++ b/model/converter/review.go @@ -12,7 +12,7 @@ func ConvertReviewPOToDomain(review po.ReviewPO) domain.Review { Course: domain.Course{ID: review.CourseID}, User: domain.User{ID: review.UserID}, Semester: review.Semester, - Rate: review.Rating, + Rating: review.Rating, IsAnonymous: review.IsAnonymous, Comment: review.Comment, CreatedAt: review.CreatedAt, @@ -39,7 +39,7 @@ func ConvertReviewDomainToDTO(review domain.Review, hideUser bool) dto.ReviewDTO ID: review.ID, Course: ConvertCourseDomainToListDTO(review.Course), Semester: review.Semester, - Rating: review.Rate, + Rating: review.Rating, IsAnonymous: review.IsAnonymous, Comment: review.Comment, UpdatedAt: review.UpdatedAt, From e4b5fdfadac5fc3555dc589af04fb3bcdd89680d Mon Sep 17 00:00:00 2001 From: Du Jiajun Date: Tue, 13 Aug 2024 23:20:49 +0800 Subject: [PATCH 02/19] feat: add lint --- .github/workflows/golangci-lint.yml | 26 ++++++++++++++++++++++++++ .golangci.yml | 12 ++++++++++++ cmd/import_from_jwc/import_from_jwc.go | 10 ++++------ cmd/migrate/migrate.go | 1 + dal/sql.go | 3 ++- handler/auth.go | 1 + handler/course.go | 5 +++++ handler/review.go | 10 ++++++++-- main.go | 5 +++-- middleware/auth.go | 1 + middleware/csrf.go | 1 + middleware/session.go | 1 + repository/auth_test.go | 1 + repository/course.go | 1 + repository/review.go | 1 + repository/teacher.go | 1 + repository/user.go | 1 + router.go | 3 ++- rpc/smtp.go | 1 + service/auth.go | 6 ++++-- service/auth_test.go | 1 + 21 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..59e3c5f --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,26 @@ +name: golangci-lint +on: + push: + branches: + - main + - master + pull_request: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.59 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d79c6ee --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,12 @@ +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - gocyclo + - gofmt + - goimports + fast: true diff --git a/cmd/import_from_jwc/import_from_jwc.go b/cmd/import_from_jwc/import_from_jwc.go index a62843f..3374424 100644 --- a/cmd/import_from_jwc/import_from_jwc.go +++ b/cmd/import_from_jwc/import_from_jwc.go @@ -11,6 +11,7 @@ import ( pinyin2 "github.com/mozillazg/go-pinyin" "gorm.io/gorm" "gorm.io/gorm/clause" + "jcourse_go/dal" "jcourse_go/model/po" ) @@ -39,7 +40,9 @@ func initDB() { func readRawCSV(filename string) [][]string { fs, err := os.Open(filename) - defer fs.Close() + defer func(fs *os.File) { + _ = fs.Close() + }(fs) if err != nil { panic(err) } @@ -229,7 +232,6 @@ func queryAllTeacher() { teacherKeyMap[makeTeacherKey(teacher.Code)] = teacher teacherIDMap[teacher.ID] = teacher } - return } func parseCourseFromLine(line []string) po.CoursePO { @@ -263,7 +265,6 @@ func queryAllCourse() { courseKeyMap[makeCourseKey(course.Code, course.MainTeacherName)] = course courseIDMap[course.ID] = course } - return } func parseOfferedCourseFromLine(line []string) po.OfferedCoursePO { @@ -297,7 +298,6 @@ func queryAllOfferedCourse() { offeredCourseIDMap[offeredCourse.ID] = offeredCourse offeredCourseKeyMap[makeOfferedCourseKey(offeredCourse.CourseID, offeredCourse.Semester)] = offeredCourse } - return } func parseOfferedCourseTeacherGroup(line []string) []po.OfferedCourseTeacherPO { @@ -329,7 +329,6 @@ func queryAllOfferedCourseTeacherGroup() { for _, offeredCourseTeacher := range offeredCourseTeachers { offeredCourseTeacherKeyMap[makeOfferedCourseTeacherKey(offeredCourseTeacher.OfferedCourseID, offeredCourseTeacher.TeacherID)] = offeredCourseTeacher } - return } func parseCourseCategories(line []string) []po.CourseCategoryPO { @@ -364,7 +363,6 @@ func queryAllCourseCategory() { } courseCategoryMap[makeCourseCategoryKey(int64(course.ID), courseCategory.Category)] = courseCategory } - return } func generatePinyin(name string) string { diff --git a/cmd/migrate/migrate.go b/cmd/migrate/migrate.go index 91f71d0..8203e16 100644 --- a/cmd/migrate/migrate.go +++ b/cmd/migrate/migrate.go @@ -2,6 +2,7 @@ package main import ( "github.com/joho/godotenv" + "jcourse_go/dal" "jcourse_go/model/po" ) diff --git a/dal/sql.go b/dal/sql.go index e93a464..22fe2ce 100644 --- a/dal/sql.go +++ b/dal/sql.go @@ -5,12 +5,13 @@ import ( "log" "os" + "jcourse_go/util" + "github.com/DATA-DOG/go-sqlmock" _ "github.com/lib/pq" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" - "jcourse_go/util" ) var dbClient *gorm.DB diff --git a/handler/auth.go b/handler/auth.go index 8ed853a..8bc4702 100644 --- a/handler/auth.go +++ b/handler/auth.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" + "jcourse_go/constant" "jcourse_go/model/domain" "jcourse_go/model/dto" diff --git a/handler/course.go b/handler/course.go index 53f8d17..96bc33b 100644 --- a/handler/course.go +++ b/handler/course.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "jcourse_go/constant" "jcourse_go/model/converter" "jcourse_go/model/domain" @@ -85,6 +86,10 @@ func GetCourseListHandler(c *gin.Context) { return } total, err := service.GetCourseCount(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + return + } resp := dto.CourseListResponse{ Total: total, diff --git a/handler/review.go b/handler/review.go index e559a5f..fe2c7d8 100644 --- a/handler/review.go +++ b/handler/review.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + "jcourse_go/middleware" "jcourse_go/model/converter" "jcourse_go/model/domain" @@ -48,6 +49,10 @@ func GetReviewListHandler(c *gin.Context) { return } total, err := service.GetReviewCount(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + return + } response := dto.ReviewListResponse{ Page: request.Page, @@ -96,7 +101,7 @@ func UpdateReviewHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, dto.UpdateReviewResponse{ReviewID: request.ReviewID}) + c.JSON(http.StatusOK, dto.UpdateReviewResponse{ReviewID: request.ReviewID}) // nolint: gosimple } func DeleteReviewHandler(c *gin.Context) { @@ -110,7 +115,8 @@ func DeleteReviewHandler(c *gin.Context) { c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) return } - c.JSON(http.StatusOK, dto.DeleteReviewResponse{ReviewID: request.ReviewID}) + + c.JSON(http.StatusOK, dto.DeleteReviewResponse{ReviewID: request.ReviewID}) // nolint: gosimple } func GetReviewListForCourseHandler(c *gin.Context) {} diff --git a/main.go b/main.go index c6a1f97..1610bcd 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,11 @@ package main import ( - "github.com/gin-gonic/gin" - "github.com/joho/godotenv" "jcourse_go/dal" "jcourse_go/rpc" + + "github.com/gin-gonic/gin" + "github.com/joho/godotenv" ) func Init() { diff --git a/middleware/auth.go b/middleware/auth.go index 57382c3..f832853 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" + "jcourse_go/constant" "jcourse_go/model/domain" "jcourse_go/model/dto" diff --git a/middleware/csrf.go b/middleware/csrf.go index 9715256..eaa99b1 100644 --- a/middleware/csrf.go +++ b/middleware/csrf.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/csrf" adapter "github.com/gwatts/gin-adapter" + "jcourse_go/constant" "jcourse_go/model/dto" "jcourse_go/util" diff --git a/middleware/session.go b/middleware/session.go index 535b29e..0e26597 100644 --- a/middleware/session.go +++ b/middleware/session.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" + "jcourse_go/constant" "jcourse_go/dal" "jcourse_go/model/domain" diff --git a/repository/auth_test.go b/repository/auth_test.go index b42306f..7a94756 100644 --- a/repository/auth_test.go +++ b/repository/auth_test.go @@ -7,6 +7,7 @@ import ( "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" + "jcourse_go/dal" ) diff --git a/repository/course.go b/repository/course.go index ffb76d4..c7953e3 100644 --- a/repository/course.go +++ b/repository/course.go @@ -6,6 +6,7 @@ import ( "fmt" "gorm.io/gorm" + "jcourse_go/dal" "jcourse_go/model/po" ) diff --git a/repository/review.go b/repository/review.go index 338d59e..fa5034f 100644 --- a/repository/review.go +++ b/repository/review.go @@ -4,6 +4,7 @@ import ( "context" "gorm.io/gorm" + "jcourse_go/dal" "jcourse_go/model/po" ) diff --git a/repository/teacher.go b/repository/teacher.go index 87effdb..8a86a4c 100644 --- a/repository/teacher.go +++ b/repository/teacher.go @@ -5,6 +5,7 @@ import ( "errors" "gorm.io/gorm" + "jcourse_go/dal" "jcourse_go/model/po" ) diff --git a/repository/user.go b/repository/user.go index b4a30d3..f3d4d9c 100644 --- a/repository/user.go +++ b/repository/user.go @@ -6,6 +6,7 @@ import ( "time" "gorm.io/gorm" + "jcourse_go/constant" "jcourse_go/dal" "jcourse_go/model/po" diff --git a/router.go b/router.go index 189da0a..eb820f8 100644 --- a/router.go +++ b/router.go @@ -1,9 +1,10 @@ package main import ( - "github.com/gin-gonic/gin" "jcourse_go/handler" "jcourse_go/middleware" + + "github.com/gin-gonic/gin" ) func registerRouter(r *gin.Engine) { diff --git a/rpc/smtp.go b/rpc/smtp.go index fbb4742..83f51ab 100644 --- a/rpc/smtp.go +++ b/rpc/smtp.go @@ -7,6 +7,7 @@ import ( "strconv" "gopkg.in/gomail.v2" + "jcourse_go/util" ) diff --git a/service/auth.go b/service/auth.go index a0293ca..b7dc924 100644 --- a/service/auth.go +++ b/service/auth.go @@ -9,6 +9,7 @@ import ( "regexp" "github.com/SJTU-jCourse/password_hasher" + "jcourse_go/constant" "jcourse_go/model/converter" "jcourse_go/model/domain" @@ -109,7 +110,7 @@ func SendRegisterCodeEmail(ctx context.Context, email string) error { if err != nil { return err } - body := fmt.Sprintf(constant.EmailBodyVerifyCode, code) + body := fmt.Sprintf(constant.EmailBodyVerifyCode, code) // nolint: gosimple err = repository.StoreVerifyCode(ctx, email, code) if err != nil { return err @@ -125,7 +126,8 @@ func SendRegisterCodeEmail(ctx context.Context, email string) error { func ValidateEmail(email string) bool { // 1. validate basic email format regex := regexp.MustCompile(`\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*`) - if !regex.MatchString(email) { + + if !regex.MatchString(email) { // nolint: gosimple return false } diff --git a/service/auth_test.go b/service/auth_test.go index 19b556e..0d5665a 100644 --- a/service/auth_test.go +++ b/service/auth_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "jcourse_go/constant" ) From 4e0ea22f4a8309fc31b0c57d41cc120548749a60 Mon Sep 17 00:00:00 2001 From: Du Jiajun Date: Tue, 13 Aug 2024 23:25:58 +0800 Subject: [PATCH 03/19] ci: timeout --- .github/workflows/golangci-lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 59e3c5f..c2c2f54 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -23,4 +23,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.59 + version: latest + args: --timeout 3m --verbose From e17f9f2d1c1f80819dcd39675692b8a985e46bd3 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 21 Jul 2024 15:32:26 +0800 Subject: [PATCH 04/19] feat: add PersonalSignature field, UserActivities table and several converter functions --- .idea/.gitignore | 8 ++++++ .idea/jcourse_go.iml | 9 ++++++ .idea/modules.xml | 8 ++++++ .idea/vcs.xml | 6 ++++ model/converter/user.go | 61 ++++++++++++++++++++++++++++++++++++----- model/domain/user.go | 21 +++++++++----- model/dto/user.go | 50 +++++++++++++++++++++++++++++++++ model/po/user.go | 25 ++++++++++++----- 8 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/jcourse_go.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 model/dto/user.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/jcourse_go.iml b/.idea/jcourse_go.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/jcourse_go.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..89f009c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/model/converter/user.go b/model/converter/user.go index f16f5dc..9d8f2c5 100644 --- a/model/converter/user.go +++ b/model/converter/user.go @@ -19,13 +19,14 @@ func ConvertUserPOToDomain(userPO po.UserPO) domain.User { func ConvertUserProfilePOToDomain(userProfile po.UserProfilePO) domain.UserProfile { return domain.UserProfile{ - UserID: userProfile.UserID, - Avatar: userProfile.Avatar, - Department: userProfile.Department, - Type: userProfile.Type, - Major: userProfile.Major, - Degree: userProfile.Degree, - Grade: userProfile.Grade, + UserID: userProfile.UserID, + Avatar: userProfile.Avatar, + Department: userProfile.Department, + Type: userProfile.Type, + Major: userProfile.Major, + Degree: userProfile.Degree, + Grade: userProfile.Grade, + PersonalSignature: userProfile.PersonalSignature, } } @@ -44,3 +45,49 @@ func ConvertUserDomainToReviewDTO(user domain.User) dto.UserInReviewDTO { Avatar: user.Profile.Avatar, } } + +func ConvertToUserSummaryDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserSummaryDTO { + if userPO == nil { + return nil + } + return &dto.UserSummaryDTO{ + ID: int64(userPO.ID), + Username: userPO.Username, + Avatar: userProfilePO.Avatar, + Role: userPO.UserRole, + } +} + +func ConvertToUserDetailsDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserDetailsDTO { + if userPO == nil { + return nil + } + return &dto.UserDetailsDTO{ + ID: int64(userPO.ID), + Username: userPO.Username, + Role: userPO.UserRole, + LastSeenAt: userPO.LastSeenAt, + Type: userPO.UserRole, + Avatar: userProfilePO.Avatar, + PersonalSignature: userProfilePO.PersonalSignature, + } +} + +func ConvertToUserProfileDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserProfileDTO { + if userPO == nil { + return nil + } + return &dto.UserProfileDTO{ + ID: int64(userPO.ID), + UserID: userProfilePO.UserID, + Avatar: userProfilePO.Avatar, + Department: userProfilePO.Department, + Type: userProfilePO.Type, + Major: userProfilePO.Major, + Degree: userProfilePO.Degree, + Grade: userProfilePO.Grade, + PersonalSignature: userProfilePO.PersonalSignature, + Username: userPO.Username, + Role: userPO.UserRole, + } +} diff --git a/model/domain/user.go b/model/domain/user.go index 1cc3c1f..f0f97f6 100644 --- a/model/domain/user.go +++ b/model/domain/user.go @@ -28,11 +28,18 @@ type User struct { } type UserProfile struct { - UserID int64 - Avatar string - Department string - Type UserType // 用户在学校的身份 - Major string - Degree string - Grade string + UserID int64 + Avatar string + Department string + Type UserType // 用户在学校的身份 + Major string + Degree string + Grade string + PersonalSignature string +} + +type UserFilter struct { + Page int64 + PageSize int64 + // To be continued ... (add more fields) } diff --git a/model/dto/user.go b/model/dto/user.go new file mode 100644 index 0000000..b0e8ca4 --- /dev/null +++ b/model/dto/user.go @@ -0,0 +1,50 @@ +package dto + +import ( + "time" +) + +type UserRole = string + +type UserType = string + +type UserListRequest struct { + Page int64 `json:"page" form:"page"` + PageSize int64 `json:"page_size" form:"page_size"` +} + +type UserListResponse = BasePaginateResponse[UserSummaryDTO] + +// 用户概要信息(用于用户列表) +type UserSummaryDTO struct { + ID int64 `json:"id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + Role UserRole `json:"user_role"` +} + +// 用户详细信息(用于用户详情页面) +type UserDetailsDTO struct { + ID int64 `json:"id"` + Username string `json:"username"` + Role string `json:"user_role"` + LastSeenAt time.Time `json:"lastSeenAt"` + Type string `json:"user_type"` + Avatar string `json:"avatar"` + PersonalSignature string `json:"personal_signature"` +} + +// 用户个人资料信息(用于个人资料页面) +type UserProfileDTO struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Avatar string `json:"avatar"` + Department string `json:"department"` + Type string `json:"type"` + Major string `json:"major"` + Degree string `json:"degree"` + Grade string `json:"grade"` + PersonalSignature string `json:"personal_signature"` + Username string `json:"username"` + Role string `json:"user_role"` +} diff --git a/model/po/user.go b/model/po/user.go index 02b8610..2ed27db 100644 --- a/model/po/user.go +++ b/model/po/user.go @@ -21,15 +21,26 @@ func (po *UserPO) TableName() string { type UserProfilePO struct { gorm.Model - UserID int64 - Avatar string - Department string - Type string // 用户在学校的身份 - Major string - Degree string - Grade string + UserID int64 + Avatar string + Department string + Type string // 用户在学校的身份 + Major string + Degree string + Grade string + PersonalSignature string } func (profile *UserProfilePO) TableName() string { return "user_profiles" } + +type UserActivityPO struct { + gorm.Model + UserID int64 // 用户ID + ActivityType string // 活动类型,如发布课程点评、点赞、回复、关注/屏蔽用户/课程等。 + TargetID string // 活动对象ID + CreatedAt time.Time // 活动发生时间 +} + +func (userActivity *UserActivityPO) TableName() string { return "user_activities" } From db26179f94c8c993aa19abee1a8a5740921bc361 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 21 Jul 2024 15:43:44 +0800 Subject: [PATCH 05/19] feat(user): add pagination and retrieval functions for User and UserProfile --- repository/user.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/repository/user.go b/repository/user.go index f3d4d9c..dffc0c8 100644 --- a/repository/user.go +++ b/repository/user.go @@ -17,16 +17,27 @@ type DBOption func(*gorm.DB) *gorm.DB type IUserQuery interface { GetUserDetail(ctx context.Context, opts ...DBOption) (*po.UserPO, error) GetUserList(ctx context.Context, opts ...DBOption) ([]po.UserPO, error) + GetUserCount(ctx context.Context, opts ...DBOption) (int64, error) + GetUserByID(ctx context.Context, userID int64) (*po.UserPO, error) GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]po.UserPO, error) WithID(id int64) DBOption WithEmail(email string) DBOption WithPassword(password string) DBOption CreateUser(ctx context.Context, email string, password string) (*po.UserPO, error) ResetUserPassword(ctx context.Context, userID int64, password string) error + WithLimit(limit int64) DBOption + WithOffset(offset int64) DBOption } type IUserProfileQuery interface { GetUserProfileByIDs(ctx context.Context, userIDs []int64) (map[int64]po.UserProfilePO, error) + GetUserProfileByID(ctx context.Context, userID int64) (*po.UserProfilePO, error) + GetUserProfileList(ctx context.Context, opts ...DBOption) ([]po.UserProfilePO, error) + GetUserProfileCount(ctx context.Context, opts ...DBOption) (int64, error) + WithUserID(id int64) DBOption + WithLimit(limit int64) DBOption + WithOffset(offset int64) DBOption + //CreateUserProfile(ctx context.Context, userID int64) (*po.UserProfilePO, error) } type UserProfileQuery struct { @@ -47,6 +58,16 @@ func (u *UserProfileQuery) GetUserProfileByIDs(ctx context.Context, userIDs []in return userProfileMap, nil } +func (u *UserProfileQuery) GetUserProfileByID(ctx context.Context, userID int64) (*po.UserProfilePO, error) { + db := u.optionDB(ctx, u.WithUserID(userID)) + userProfilePO := po.UserProfilePO{} + result := db.Find(&userProfilePO) + if result.Error != nil { + return &userProfilePO, result.Error + } + return &userProfilePO, nil +} + func (u *UserProfileQuery) optionDB(ctx context.Context, opts ...DBOption) *gorm.DB { db := u.db.WithContext(ctx).Model(&po.UserProfilePO{}) for _, opt := range opts { @@ -61,6 +82,22 @@ func (u *UserProfileQuery) WithUserIDs(userIDs []int64) DBOption { } } +func (q *UserProfileQuery) WithUserID(id int64) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("user_id = ?", id) + } +} + +func (q *UserProfileQuery) WithLimit(limit int64) DBOption { + return func(db *gorm.DB) *gorm.DB { return db.Limit(int(limit)) } +} + +func (q *UserProfileQuery) WithOffset(offset int64) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Offset(int(offset)) + } +} + func NewUserProfileQuery() IUserProfileQuery { return &UserProfileQuery{db: dal.GetDBClient()} } @@ -71,6 +108,26 @@ func NewUserQuery() IUserQuery { } } +func (q *UserProfileQuery) GetUserProfileList(ctx context.Context, opts ...DBOption) ([]po.UserProfilePO, error) { + db := q.optionDB(ctx, opts...) + userProfilePOs := make([]po.UserProfilePO, 0) + result := db.Find(&userProfilePOs) + if result.Error != nil { + return userProfilePOs, result.Error + } + return userProfilePOs, nil +} + +func (q *UserProfileQuery) GetUserProfileCount(ctx context.Context, opts ...DBOption) (int64, error) { + db := q.optionDB(ctx, opts...) + count := int64(0) + result := db.Count(&count) + if result.Error != nil { + return 0, result.Error + } + return count, nil +} + type UserQuery struct { db *gorm.DB } @@ -89,6 +146,16 @@ func (q *UserQuery) GetUserByIDs(ctx context.Context, userIDs []int64) (map[int6 return userMap, nil } +func (q *UserQuery) GetUserByID(ctx context.Context, userID int64) (*po.UserPO, error) { + db := q.optionDB(ctx, q.WithID(userID)) + userPO := po.UserPO{} + result := db.Find(&userPO) + if result.Error != nil { + return &userPO, result.Error + } + return &userPO, nil +} + func (q *UserQuery) WithEmail(email string) DBOption { return func(db *gorm.DB) *gorm.DB { return db.Where("email = ?", email) @@ -138,6 +205,16 @@ func (q *UserQuery) GetUserList(ctx context.Context, opts ...DBOption) ([]po.Use return userPOs, nil } +func (q *UserQuery) GetUserCount(ctx context.Context, opts ...DBOption) (int64, error) { + db := q.optionDB(ctx, opts...) + count := int64(0) + result := db.Count(&count) + if result.Error != nil { + return 0, result.Error + } + return count, nil +} + func (q *UserQuery) CreateUser(ctx context.Context, email string, passwordStore string) (*po.UserPO, error) { user := po.UserPO{ Username: email, @@ -157,3 +234,13 @@ func (q *UserQuery) ResetUserPassword(ctx context.Context, userID int64, passwor result := q.optionDB(ctx, q.WithID(userID)).Debug().Update("password", passwordStore) return result.Error } + +func (q *UserQuery) WithLimit(limit int64) DBOption { + return func(db *gorm.DB) *gorm.DB { return db.Limit(int(limit)) } +} + +func (q *UserQuery) WithOffset(offset int64) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Offset(int(offset)) + } +} From ac5a5d8f7f545ba58bb62d31b4210177bf4c295d Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 21 Jul 2024 15:51:53 +0800 Subject: [PATCH 06/19] feat: add /:userID/reviews, /:userID/watch and /me/info routes in userGroup in route.go --- handler/user.go | 4 ++++ router.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/handler/user.go b/handler/user.go index 5360778..e7fe019 100644 --- a/handler/user.go +++ b/handler/user.go @@ -13,3 +13,7 @@ func GetUserDetailHandler(c *gin.Context) {} func WatchUserHandler(c *gin.Context) {} func UnWatchUserHandler(c *gin.Context) {} + +func UpdateUserInfoHandler(c *gin.Context) {} + +func GetUserReviewsHandler(c *gin.Context) {} diff --git a/router.go b/router.go index eb820f8..de89460 100644 --- a/router.go +++ b/router.go @@ -53,7 +53,10 @@ func registerRouter(r *gin.Engine) { userGroup.GET("/suggest", handler.GetSuggestedUserHandler) userGroup.GET("/me", handler.GetCurrentUserHandler) userGroup.GET("/:userID", handler.GetUserDetailHandler) + userGroup.GET("/:userID/reviews", handler.GetUserReviewsHandler) + userGroup.POST("/:userID/watch", handler.WatchUserHandler) userGroup.POST("/:userID/unwatch", handler.UnWatchUserHandler) + userGroup.PATCH("/me/info", handler.UpdateUserInfoHandler) adminGroup := needAuthGroup.Group("/admin") adminGroup.Use(middleware.RequireAdmin()) From b7bedcd71be95385e36812f8ef0c49ad5296c11f Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 21 Jul 2024 15:57:34 +0800 Subject: [PATCH 07/19] feat: achieve GetUserList and GetUserCount functions, build UserDBoption for filter in \service\user.go --- service/user.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/service/user.go b/service/user.go index fb21f77..6049701 100644 --- a/service/user.go +++ b/service/user.go @@ -2,6 +2,8 @@ package service import ( "context" + "jcourse_go/model/dto" + "jcourse_go/util" "jcourse_go/model/converter" "jcourse_go/model/domain" @@ -36,3 +38,110 @@ func GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]domain.User, } return result, nil } + +func GetUserSummaryByID(ctx context.Context, userID int64) (*dto.UserSummaryDTO, error) { + userQuery := repository.NewUserQuery() + userPO, err := userQuery.GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + + userProfileQuery := repository.NewUserProfileQuery() + userProfilePO, err := userProfileQuery.GetUserProfileByID(ctx, userID) + if err != nil { + return nil, err + } + userSummary := converter.ConvertToUserSummaryDTO(userPO, userProfilePO) + return userSummary, nil +} + +func GetUserDetailsByID(ctx context.Context, userID int64) (*dto.UserDetailsDTO, error) { + userQuery := repository.NewUserQuery() + userPO, err := userQuery.GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + + userProfileQuery := repository.NewUserProfileQuery() + userProfilePO, err := userProfileQuery.GetUserProfileByID(ctx, userID) + if err != nil { + return nil, err + } + userDetails := converter.ConvertToUserDetailsDTO(userPO, userProfilePO) + return userDetails, nil +} + +func GetUserProfileByID(ctx context.Context, userID int64) (*dto.UserProfileDTO, error) { + userQuery := repository.NewUserQuery() + userPO, err := userQuery.GetUserByID(ctx, userID) + if err != nil { + return nil, err + } + + userProfileQuery := repository.NewUserProfileQuery() + userProfilePO, err := userProfileQuery.GetUserProfileByID(ctx, userID) + if err != nil { + return nil, err + } + userProfile := converter.ConvertToUserProfileDTO(userPO, userProfilePO) + return userProfile, nil +} + +func buildUserDBOptionFromFilter(query repository.IUserQuery, filter domain.UserFilter) []repository.DBOption { + opts := make([]repository.DBOption, 0) + if filter.PageSize > 0 { + opts = append(opts, query.WithLimit(filter.PageSize)) + } + if filter.Page > 0 { + opts = append(opts, query.WithOffset(util.CalcOffset(filter.Page, filter.PageSize))) + } + return opts +} + +func GetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserSummaryDTO, error) { + userQuery := repository.NewUserQuery() + userProfileQuery := repository.NewUserProfileQuery() + opts := buildUserDBOptionFromFilter(userQuery, filter) + userPOs, err := userQuery.GetUserList(ctx, opts...) + if err != nil { + return nil, err + } + userProfilePOs, err := userProfileQuery.GetUserProfileList(ctx, opts...) + if err != nil { + return nil, err + } + result := make([]dto.UserSummaryDTO, 0) + + userProfileMap := make(map[int]string) + for _, profile := range userProfilePOs { + userProfileMap[int(profile.UserID)] = profile.Avatar + } + + for _, userPO := range userPOs { + if avatar, exists := userProfileMap[int(userPO.ID)]; exists { + userSummaryDTO := dto.UserSummaryDTO{ + ID: int64(userPO.ID), + Username: userPO.Username, + Role: userPO.UserRole, + Avatar: avatar, + } + result = append(result, userSummaryDTO) + } else { + userSummaryDTO := dto.UserSummaryDTO{ + ID: int64(userPO.ID), + Username: userPO.Username, + Role: userPO.UserRole, + Avatar: "", + } + result = append(result, userSummaryDTO) + } + } + return result, nil +} + +func GetUserCount(ctx context.Context, filter domain.UserFilter) (int64, error) { + userQuery := repository.NewUserQuery() + filter.Page, filter.PageSize = 0, 0 + opts := buildUserDBOptionFromFilter(userQuery, filter) + return userQuery.GetUserCount(ctx, opts...) +} From cc287d8927c6aa24c102eb2dc72656aeaa8a26ab Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 21 Jul 2024 16:08:07 +0800 Subject: [PATCH 08/19] feat: implement GetUserListHandler, GetCurrentUserHandler, GetUserDetailHandler and GetUserViewsHandler in \handler\user.go which are partially declared in commit bcba52e --- handler/user.go | 109 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/handler/user.go b/handler/user.go index e7fe019..002c1f8 100644 --- a/handler/user.go +++ b/handler/user.go @@ -1,14 +1,77 @@ package handler -import "github.com/gin-gonic/gin" +import ( + "github.com/gin-gonic/gin" + "jcourse_go/constant" + "jcourse_go/model/converter" + "jcourse_go/model/domain" + "jcourse_go/model/dto" + "jcourse_go/service" + "net/http" + "strconv" +) func GetSuggestedUserHandler(c *gin.Context) {} -func GetUserListHandler(c *gin.Context) {} +func GetUserListHandler(c *gin.Context) { + /* + 管理员权限验证 + */ + var request dto.UserListRequest + if err := c.ShouldBind(&request); err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) + return + } -func GetCurrentUserHandler(c *gin.Context) {} + filter := domain.UserFilter{ + Page: request.Page, + PageSize: request.PageSize, + } + users, err := service.GetUserList(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + } + total, _ := service.GetUserCount(c, filter) + response := dto.UserListResponse{ + Page: request.Page, + PageSize: request.PageSize, + Total: total, + Data: users, + } + c.JSON(http.StatusOK, response) +} -func GetUserDetailHandler(c *gin.Context) {} +func GetCurrentUserHandler(c *gin.Context) { + userInterface, exists := c.Get(constant.CtxKeyUser) + if !exists { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) + return + } + user, _ := userInterface.(*domain.User) + + me, err := service.GetUserDetailsByID(c, user.ID) + if err != nil { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) + return + } + c.JSON(http.StatusOK, me) + return +} + +func GetUserDetailHandler(c *gin.Context) { + userIDStr := c.Param("userID") + userID, err := strconv.Atoi(userIDStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "非法用户ID"}) + return + } + + userDetail, errDetail := service.GetUserDetailsByID(c, int64(userID)) + if errDetail != nil { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) + } + c.JSON(http.StatusOK, userDetail) +} func WatchUserHandler(c *gin.Context) {} @@ -16,4 +79,40 @@ func UnWatchUserHandler(c *gin.Context) {} func UpdateUserInfoHandler(c *gin.Context) {} -func GetUserReviewsHandler(c *gin.Context) {} +func GetUserReviewsHandler(c *gin.Context) { + userIDStr := c.Param("userID") + + userID, err := strconv.Atoi(userIDStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "非法用户ID"}) + return + } + + var request dto.ReviewListRequest + if err := c.ShouldBind(&request); err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) + return + } + + filter := domain.ReviewFilter{ + Page: request.Page, + PageSize: request.PageSize, + UserID: int64(userID), + } + + reviews, err := service.GetReviewList(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + return + } + + total, err := service.GetReviewCount(c, filter) + + response := dto.ReviewListResponse{ + Page: request.Page, + PageSize: request.PageSize, + Total: total, + Data: converter.ConvertReviewDomainToListDTO(reviews, true), + } + c.JSON(http.StatusOK, response) +} From 65774d8c8f18dcb650502bc57e3c26bceea61251 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Tue, 6 Aug 2024 22:37:43 +0800 Subject: [PATCH 09/19] Reorganize routes in router.go and update model fields in dto/user.go according to jcourse_v2_frontend model.ts --- router.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/router.go b/router.go index de89460..2bec19d 100644 --- a/router.go +++ b/router.go @@ -51,12 +51,13 @@ func registerRouter(r *gin.Engine) { userGroup := needAuthGroup.Group("/user") userGroup.GET("", handler.GetUserListHandler) userGroup.GET("/suggest", handler.GetSuggestedUserHandler) - userGroup.GET("/me", handler.GetCurrentUserHandler) - userGroup.GET("/:userID", handler.GetUserDetailHandler) + userGroup.GET("/me", handler.GetCurrentUserSummaryHandler) + userGroup.GET("/:userID/detail", handler.GetUserDetailHandler) userGroup.GET("/:userID/reviews", handler.GetUserReviewsHandler) userGroup.POST("/:userID/watch", handler.WatchUserHandler) userGroup.POST("/:userID/unwatch", handler.UnWatchUserHandler) - userGroup.PATCH("/me/info", handler.UpdateUserInfoHandler) + userGroup.GET("/me/profile", handler.GetCurrentUserProfileHandler) + userGroup.PUT("/me/profile", handler.UpdateUserProfileHandler) adminGroup := needAuthGroup.Group("/admin") adminGroup.Use(middleware.RequireAdmin()) From 1db812015d49507ccdcb2b9b5cec72af81d70b63 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Tue, 6 Aug 2024 22:40:51 +0800 Subject: [PATCH 10/19] Update model fields in dto/user.go according to jcourse_v2_frontend model.ts --- model/dto/user.go | 52 +++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/model/dto/user.go b/model/dto/user.go index b0e8ca4..30f7979 100644 --- a/model/dto/user.go +++ b/model/dto/user.go @@ -1,9 +1,5 @@ package dto -import ( - "time" -) - type UserRole = string type UserType = string @@ -13,38 +9,32 @@ type UserListRequest struct { PageSize int64 `json:"page_size" form:"page_size"` } -type UserListResponse = BasePaginateResponse[UserSummaryDTO] +type UserListResponse = BasePaginateResponse[UserDetailDTO] -// 用户概要信息(用于用户列表) type UserSummaryDTO struct { - ID int64 `json:"id"` - Username string `json:"username"` - Avatar string `json:"avatar"` - Role UserRole `json:"user_role"` + ID int64 `json:"id"` + ReviewCount int64 `json:"review_count"` + LikeReceive int64 `json:"like_receive"` + TipReceive int64 `json:"tip_receive"` + FollowingCourseCount int64 `json:"following_course_count"` } -// 用户详细信息(用于用户详情页面) -type UserDetailsDTO struct { - ID int64 `json:"id"` - Username string `json:"username"` - Role string `json:"user_role"` - LastSeenAt time.Time `json:"lastSeenAt"` - Type string `json:"user_type"` - Avatar string `json:"avatar"` - PersonalSignature string `json:"personal_signature"` +type UserDetailDTO struct { + ID int64 `json:"id"` + Username string `json:"username"` + Avatar string `json:"avatar"` + Bio string `json:"bio"` } -// 用户个人资料信息(用于个人资料页面) type UserProfileDTO struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - Avatar string `json:"avatar"` - Department string `json:"department"` - Type string `json:"type"` - Major string `json:"major"` - Degree string `json:"degree"` - Grade string `json:"grade"` - PersonalSignature string `json:"personal_signature"` - Username string `json:"username"` - Role string `json:"user_role"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Username string `json:"username"` + Bio string `json:"bio"` + Email string `json:"email"` + Avatar string `json:"avatar"` + Role string `json:"user_role"` + Department string `json:"department"` + Major string `json:"major"` + Grade string `json:"grade"` } From d6fce215a1b14768b89d872f5bf1645b3018ac00 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Tue, 6 Aug 2024 22:43:05 +0800 Subject: [PATCH 11/19] Related changes in model for commit d6d1d1d --- model/converter/user.go | 58 +++++++++++++++-------------------------- model/domain/user.go | 16 ++++++------ model/po/user.go | 16 ++++++------ 3 files changed, 37 insertions(+), 53 deletions(-) diff --git a/model/converter/user.go b/model/converter/user.go index 9d8f2c5..2f46936 100644 --- a/model/converter/user.go +++ b/model/converter/user.go @@ -19,14 +19,14 @@ func ConvertUserPOToDomain(userPO po.UserPO) domain.User { func ConvertUserProfilePOToDomain(userProfile po.UserProfilePO) domain.UserProfile { return domain.UserProfile{ - UserID: userProfile.UserID, - Avatar: userProfile.Avatar, - Department: userProfile.Department, - Type: userProfile.Type, - Major: userProfile.Major, - Degree: userProfile.Degree, - Grade: userProfile.Grade, - PersonalSignature: userProfile.PersonalSignature, + UserID: userProfile.UserID, + Avatar: userProfile.Avatar, + Department: userProfile.Department, + Type: userProfile.Type, + Major: userProfile.Major, + Degree: userProfile.Degree, + Grade: userProfile.Grade, + Bio: userProfile.Bio, } } @@ -46,30 +46,15 @@ func ConvertUserDomainToReviewDTO(user domain.User) dto.UserInReviewDTO { } } -func ConvertToUserSummaryDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserSummaryDTO { +func ConvertToUserDetailDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserDetailDTO { if userPO == nil { return nil } - return &dto.UserSummaryDTO{ + return &dto.UserDetailDTO{ ID: int64(userPO.ID), Username: userPO.Username, Avatar: userProfilePO.Avatar, - Role: userPO.UserRole, - } -} - -func ConvertToUserDetailsDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserDetailsDTO { - if userPO == nil { - return nil - } - return &dto.UserDetailsDTO{ - ID: int64(userPO.ID), - Username: userPO.Username, - Role: userPO.UserRole, - LastSeenAt: userPO.LastSeenAt, - Type: userPO.UserRole, - Avatar: userProfilePO.Avatar, - PersonalSignature: userProfilePO.PersonalSignature, + Bio: userProfilePO.Bio, } } @@ -78,16 +63,15 @@ func ConvertToUserProfileDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) return nil } return &dto.UserProfileDTO{ - ID: int64(userPO.ID), - UserID: userProfilePO.UserID, - Avatar: userProfilePO.Avatar, - Department: userProfilePO.Department, - Type: userProfilePO.Type, - Major: userProfilePO.Major, - Degree: userProfilePO.Degree, - Grade: userProfilePO.Grade, - PersonalSignature: userProfilePO.PersonalSignature, - Username: userPO.Username, - Role: userPO.UserRole, + ID: int64(userPO.ID), + UserID: userProfilePO.UserID, + Username: userPO.Username, + Bio: userProfilePO.Bio, + Email: userPO.Email, + Avatar: userProfilePO.Avatar, + Role: userPO.UserRole, + Department: userProfilePO.Department, + Major: userProfilePO.Major, + Grade: userProfilePO.Grade, } } diff --git a/model/domain/user.go b/model/domain/user.go index f0f97f6..102c2ed 100644 --- a/model/domain/user.go +++ b/model/domain/user.go @@ -28,14 +28,14 @@ type User struct { } type UserProfile struct { - UserID int64 - Avatar string - Department string - Type UserType // 用户在学校的身份 - Major string - Degree string - Grade string - PersonalSignature string + UserID int64 + Avatar string + Department string + Type UserType // 用户在学校的身份 + Major string + Degree string + Grade string + Bio string } type UserFilter struct { diff --git a/model/po/user.go b/model/po/user.go index 2ed27db..8301320 100644 --- a/model/po/user.go +++ b/model/po/user.go @@ -21,14 +21,14 @@ func (po *UserPO) TableName() string { type UserProfilePO struct { gorm.Model - UserID int64 - Avatar string - Department string - Type string // 用户在学校的身份 - Major string - Degree string - Grade string - PersonalSignature string + UserID int64 + Avatar string + Department string + Type string // 用户在学校的身份 + Major string + Degree string + Grade string + Bio string } func (profile *UserProfilePO) TableName() string { From e14ec8f835ff352f1671c26cdda723b4ea8eeadd Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Tue, 6 Aug 2024 22:45:31 +0800 Subject: [PATCH 12/19] Refined/Implemented several handlers in d6d1d1d --- handler/user.go | 33 ++++++++++++++++++------- service/user.go | 64 ++++++++++++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/handler/user.go b/handler/user.go index 002c1f8..f12148c 100644 --- a/handler/user.go +++ b/handler/user.go @@ -14,11 +14,11 @@ import ( func GetSuggestedUserHandler(c *gin.Context) {} func GetUserListHandler(c *gin.Context) { - /* - 管理员权限验证 - */ + + // 管理员权限验证 + var request dto.UserListRequest - if err := c.ShouldBind(&request); err != nil { + if err := c.ShouldBindQuery(&request); err != nil { c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) return } @@ -41,7 +41,7 @@ func GetUserListHandler(c *gin.Context) { c.JSON(http.StatusOK, response) } -func GetCurrentUserHandler(c *gin.Context) { +func GetCurrentUserSummaryHandler(c *gin.Context) { userInterface, exists := c.Get(constant.CtxKeyUser) if !exists { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) @@ -49,13 +49,12 @@ func GetCurrentUserHandler(c *gin.Context) { } user, _ := userInterface.(*domain.User) - me, err := service.GetUserDetailsByID(c, user.ID) + me, err := service.GetUserSummaryByID(c, user.ID) if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) return } c.JSON(http.StatusOK, me) - return } func GetUserDetailHandler(c *gin.Context) { @@ -66,18 +65,34 @@ func GetUserDetailHandler(c *gin.Context) { return } - userDetail, errDetail := service.GetUserDetailsByID(c, int64(userID)) + userDetail, errDetail := service.GetUserDetailByID(c, int64(userID)) if errDetail != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) } c.JSON(http.StatusOK, userDetail) } +func GetCurrentUserProfileHandler(c *gin.Context) { + userInterface, exists := c.Get(constant.CtxKeyUser) + if !exists { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) + return + } + + user, _ := userInterface.(*domain.User) + me, err := service.GetUserProfileByID(c, user.ID) + if err != nil { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) + return + } + c.JSON(http.StatusOK, me) +} + func WatchUserHandler(c *gin.Context) {} func UnWatchUserHandler(c *gin.Context) {} -func UpdateUserInfoHandler(c *gin.Context) {} +func UpdateUserProfileHandler(c *gin.Context) {} func GetUserReviewsHandler(c *gin.Context) { userIDStr := c.Param("userID") diff --git a/service/user.go b/service/user.go index 6049701..de47127 100644 --- a/service/user.go +++ b/service/user.go @@ -3,6 +3,7 @@ package service import ( "context" "jcourse_go/model/dto" + "jcourse_go/model/po" "jcourse_go/util" "jcourse_go/model/converter" @@ -10,6 +11,27 @@ import ( "jcourse_go/repository" ) +// 该函数如果放在converter包中会报错import cycle is not allowed +// UserSummaryDTO的组装需要借助service/review.go中的GetReviewCount函数 +func ConvertToUserSummaryDTO(ctx context.Context, userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserSummaryDTO { + if userPO == nil { + return nil + } + + filter := domain.ReviewFilter{ + UserID: int64(userPO.ID), + } + + total, _ := GetReviewCount(ctx, filter) + + return &dto.UserSummaryDTO{ + ID: int64(userPO.ID), + ReviewCount: total, + TipReceive: 0, + FollowingCourseCount: 0, + } +} + func GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]domain.User, error) { result := make(map[int64]domain.User) if len(userIDs) == 0 { @@ -51,11 +73,11 @@ func GetUserSummaryByID(ctx context.Context, userID int64) (*dto.UserSummaryDTO, if err != nil { return nil, err } - userSummary := converter.ConvertToUserSummaryDTO(userPO, userProfilePO) + userSummary := ConvertToUserSummaryDTO(ctx, userPO, userProfilePO) return userSummary, nil } -func GetUserDetailsByID(ctx context.Context, userID int64) (*dto.UserDetailsDTO, error) { +func GetUserDetailByID(ctx context.Context, userID int64) (*dto.UserDetailDTO, error) { userQuery := repository.NewUserQuery() userPO, err := userQuery.GetUserByID(ctx, userID) if err != nil { @@ -67,7 +89,7 @@ func GetUserDetailsByID(ctx context.Context, userID int64) (*dto.UserDetailsDTO, if err != nil { return nil, err } - userDetails := converter.ConvertToUserDetailsDTO(userPO, userProfilePO) + userDetails := converter.ConvertToUserDetailDTO(userPO, userProfilePO) return userDetails, nil } @@ -98,7 +120,7 @@ func buildUserDBOptionFromFilter(query repository.IUserQuery, filter domain.User return opts } -func GetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserSummaryDTO, error) { +func GetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserDetailDTO, error) { userQuery := repository.NewUserQuery() userProfileQuery := repository.NewUserProfileQuery() opts := buildUserDBOptionFromFilter(userQuery, filter) @@ -110,31 +132,25 @@ func GetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserSumma if err != nil { return nil, err } - result := make([]dto.UserSummaryDTO, 0) + result := make([]dto.UserDetailDTO, 0) - userProfileMap := make(map[int]string) - for _, profile := range userProfilePOs { - userProfileMap[int(profile.UserID)] = profile.Avatar + userProfileMap := make(map[int]*po.UserProfilePO) + for _, userProfilePO := range userProfilePOs { + userProfileMap[int(userProfilePO.UserID)] = &userProfilePO } for _, userPO := range userPOs { - if avatar, exists := userProfileMap[int(userPO.ID)]; exists { - userSummaryDTO := dto.UserSummaryDTO{ - ID: int64(userPO.ID), - Username: userPO.Username, - Role: userPO.UserRole, - Avatar: avatar, - } - result = append(result, userSummaryDTO) - } else { - userSummaryDTO := dto.UserSummaryDTO{ - ID: int64(userPO.ID), - Username: userPO.Username, - Role: userPO.UserRole, - Avatar: "", - } - result = append(result, userSummaryDTO) + userDetailDTO := dto.UserDetailDTO{ + ID: int64(userPO.ID), + Username: userPO.Username, + Avatar: "", + Bio: "", + } + if userProfilePO, exists := userProfileMap[int(userPO.ID)]; exists { + userDetailDTO.Avatar = userProfilePO.Avatar + userDetailDTO.Bio = userProfilePO.Bio } + result = append(result, userDetailDTO) } return result, nil } From c036343ec6400028490604f9bc3a01a503c9ca52 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Wed, 7 Aug 2024 14:03:37 +0800 Subject: [PATCH 13/19] Implement UpdateUserProfileHandler in handler/user.go and add Update feature to repository/user.go --- handler/user.go | 14 ++++++++++++- model/converter/user.go | 45 +++++++++++++++++++++++++++++++++++++++++ repository/user.go | 12 +++++++++++ service/user.go | 26 ++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/handler/user.go b/handler/user.go index f12148c..9eae4a8 100644 --- a/handler/user.go +++ b/handler/user.go @@ -92,7 +92,19 @@ func WatchUserHandler(c *gin.Context) {} func UnWatchUserHandler(c *gin.Context) {} -func UpdateUserProfileHandler(c *gin.Context) {} +func UpdateUserProfileHandler(c *gin.Context) { + var request dto.UserProfileDTO + if err := c.ShouldBindJSON(&request); err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) + return + } + err := service.UpdateUserProfileByID(c, &request) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "用户信息更新失败。"}) + return + } + c.JSON(http.StatusOK, dto.BaseResponse{Message: "用户信息更新成功。"}) +} func GetUserReviewsHandler(c *gin.Context) { userIDStr := c.Param("userID") diff --git a/model/converter/user.go b/model/converter/user.go index 2f46936..44a87f4 100644 --- a/model/converter/user.go +++ b/model/converter/user.go @@ -1,9 +1,11 @@ package converter import ( + "gorm.io/gorm" "jcourse_go/model/domain" "jcourse_go/model/dto" "jcourse_go/model/po" + "time" ) func ConvertUserPOToDomain(userPO po.UserPO) domain.User { @@ -75,3 +77,46 @@ func ConvertToUserProfileDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) Grade: userProfilePO.Grade, } } + +func ConvertUpdateUserProfileDTOToUserPO(userProfileDTO *dto.UserProfileDTO, userPO *po.UserPO) *po.UserPO { + if userProfileDTO == nil { + return nil + } + return &po.UserPO{ + Model: gorm.Model{ + ID: userPO.ID, + CreatedAt: userPO.CreatedAt, + UpdatedAt: time.Now(), + DeletedAt: userPO.DeletedAt, + }, + Username: userProfileDTO.Username, + Email: userPO.Email, + Password: userPO.Password, + UserRole: userPO.UserRole, + LastSeenAt: time.Now(), + } +} + +func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDTO, userProfilePO *po.UserProfilePO) *po.UserProfilePO { + if userProfileDTO == nil { + return nil + } + + // 保留一些immutable的属性( + return &po.UserProfilePO{ + Model: gorm.Model{ + ID: userProfilePO.ID, + CreatedAt: userProfilePO.CreatedAt, + UpdatedAt: time.Now(), + DeletedAt: userProfilePO.DeletedAt, + }, + UserID: userProfilePO.UserID, + Avatar: userProfileDTO.Avatar, + Department: userProfileDTO.Department, + Type: userProfilePO.Type, + Major: userProfileDTO.Major, + Degree: userProfilePO.Degree, + Grade: userProfileDTO.Grade, + Bio: userProfileDTO.Bio, + } +} diff --git a/repository/user.go b/repository/user.go index dffc0c8..708d642 100644 --- a/repository/user.go +++ b/repository/user.go @@ -20,6 +20,7 @@ type IUserQuery interface { GetUserCount(ctx context.Context, opts ...DBOption) (int64, error) GetUserByID(ctx context.Context, userID int64) (*po.UserPO, error) GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]po.UserPO, error) + UpdateUserByID(ctx context.Context, user *po.UserPO) error WithID(id int64) DBOption WithEmail(email string) DBOption WithPassword(password string) DBOption @@ -34,6 +35,7 @@ type IUserProfileQuery interface { GetUserProfileByID(ctx context.Context, userID int64) (*po.UserProfilePO, error) GetUserProfileList(ctx context.Context, opts ...DBOption) ([]po.UserProfilePO, error) GetUserProfileCount(ctx context.Context, opts ...DBOption) (int64, error) + UpdateUserProfileByID(ctx context.Context, userProfile *po.UserProfilePO) error WithUserID(id int64) DBOption WithLimit(limit int64) DBOption WithOffset(offset int64) DBOption @@ -128,6 +130,11 @@ func (q *UserProfileQuery) GetUserProfileCount(ctx context.Context, opts ...DBOp return count, nil } +func (q *UserProfileQuery) UpdateUserProfileByID(ctx context.Context, userProfile *po.UserProfilePO) error { + result := q.optionDB(ctx, q.WithUserID(userProfile.UserID)).Save(userProfile).Error + return result +} + type UserQuery struct { db *gorm.DB } @@ -215,6 +222,11 @@ func (q *UserQuery) GetUserCount(ctx context.Context, opts ...DBOption) (int64, return count, nil } +func (q *UserQuery) UpdateUserByID(ctx context.Context, user *po.UserPO) error { + result := q.optionDB(ctx, q.WithID(int64(user.ID))).Save(user).Error + return result +} + func (q *UserQuery) CreateUser(ctx context.Context, email string, passwordStore string) (*po.UserPO, error) { user := po.UserPO{ Username: email, diff --git a/service/user.go b/service/user.go index de47127..451e941 100644 --- a/service/user.go +++ b/service/user.go @@ -161,3 +161,29 @@ func GetUserCount(ctx context.Context, filter domain.UserFilter) (int64, error) opts := buildUserDBOptionFromFilter(userQuery, filter) return userQuery.GetUserCount(ctx, opts...) } + +func UpdateUserProfileByID(ctx context.Context, userProfileDTO *dto.UserProfileDTO) error { + userQuery := repository.NewUserQuery() + oldUserPO, errQuery := userQuery.GetUserByID(ctx, userProfileDTO.UserID) + if errQuery != nil { + return errQuery + } + newUserPO := converter.ConvertUpdateUserProfileDTOToUserPO(userProfileDTO, oldUserPO) + + errUpdate := userQuery.UpdateUserByID(ctx, newUserPO) + if errUpdate != nil { + return errUpdate + } + + userProfileQuery := repository.NewUserProfileQuery() + oldUserProfilePO, errQuery2 := userProfileQuery.GetUserProfileByID(ctx, userProfileDTO.UserID) + if errQuery2 != nil { + return errQuery2 + } + newUserProfilePO := converter.ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO, oldUserProfilePO) + errUpdate2 := userProfileQuery.UpdateUserProfileByID(ctx, newUserProfilePO) + if errUpdate2 != nil { + return errUpdate2 + } + return nil +} From 5d4a053621c5bc9411d1c3cf5ffcec02e075fedc Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 11 Aug 2024 08:15:46 +0800 Subject: [PATCH 14/19] Remove .idea directory from version control and update .gitignore --- .gitignore | 2 ++ .idea/.gitignore | 8 -------- .idea/jcourse_go.iml | 9 --------- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 5 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/jcourse_go.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.gitignore b/.gitignore index f140648..5e381ef 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ go.work.sum .env *.sqlite + +.idea \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 35410ca..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 默认忽略的文件 -/shelf/ -/workspace.xml -# 基于编辑器的 HTTP 客户端请求 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/jcourse_go.iml b/.idea/jcourse_go.iml deleted file mode 100644 index 5e764c4..0000000 --- a/.idea/jcourse_go.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 89f009c..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From d4b53bb738fdb8393a3bd3dbd5b520b80c8cae77 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 11 Aug 2024 09:09:37 +0800 Subject: [PATCH 15/19] Refactor: remove direct gorm.Model dependency in business logic --- model/converter/user.go | 38 +++++++++++++------------------------- service/user.go | 4 ++-- 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/model/converter/user.go b/model/converter/user.go index 44a87f4..99cb9ef 100644 --- a/model/converter/user.go +++ b/model/converter/user.go @@ -1,7 +1,6 @@ package converter import ( - "gorm.io/gorm" "jcourse_go/model/domain" "jcourse_go/model/dto" "jcourse_go/model/po" @@ -78,38 +77,23 @@ func ConvertToUserProfileDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) } } -func ConvertUpdateUserProfileDTOToUserPO(userProfileDTO *dto.UserProfileDTO, userPO *po.UserPO) *po.UserPO { - if userProfileDTO == nil { - return nil - } - return &po.UserPO{ - Model: gorm.Model{ - ID: userPO.ID, - CreatedAt: userPO.CreatedAt, - UpdatedAt: time.Now(), - DeletedAt: userPO.DeletedAt, - }, +func ConvertUpdateUserProfileDTOToUserPO(userProfileDTO *dto.UserProfileDTO, userPO *po.UserPO) po.UserPO { + updatedUserPo := po.UserPO{ Username: userProfileDTO.Username, Email: userPO.Email, Password: userPO.Password, UserRole: userPO.UserRole, LastSeenAt: time.Now(), } -} - -func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDTO, userProfilePO *po.UserProfilePO) *po.UserProfilePO { - if userProfileDTO == nil { - return nil + if userProfileDTO.ID != 0 { + updatedUserPo.ID = userPO.ID } + return updatedUserPo +} - // 保留一些immutable的属性( - return &po.UserProfilePO{ - Model: gorm.Model{ - ID: userProfilePO.ID, - CreatedAt: userProfilePO.CreatedAt, - UpdatedAt: time.Now(), - DeletedAt: userProfilePO.DeletedAt, - }, +func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDTO, userProfilePO *po.UserProfilePO) po.UserProfilePO { + // 保留一些immutable的属性 + updatedUserProfilePO := po.UserProfilePO{ UserID: userProfilePO.UserID, Avatar: userProfileDTO.Avatar, Department: userProfileDTO.Department, @@ -119,4 +103,8 @@ func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDT Grade: userProfileDTO.Grade, Bio: userProfileDTO.Bio, } + if updatedUserProfilePO.ID != 0 { + updatedUserProfilePO.ID = userProfilePO.ID + } + return updatedUserProfilePO } diff --git a/service/user.go b/service/user.go index 451e941..105b7ad 100644 --- a/service/user.go +++ b/service/user.go @@ -170,7 +170,7 @@ func UpdateUserProfileByID(ctx context.Context, userProfileDTO *dto.UserProfileD } newUserPO := converter.ConvertUpdateUserProfileDTOToUserPO(userProfileDTO, oldUserPO) - errUpdate := userQuery.UpdateUserByID(ctx, newUserPO) + errUpdate := userQuery.UpdateUserByID(ctx, &newUserPO) if errUpdate != nil { return errUpdate } @@ -181,7 +181,7 @@ func UpdateUserProfileByID(ctx context.Context, userProfileDTO *dto.UserProfileD return errQuery2 } newUserProfilePO := converter.ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO, oldUserProfilePO) - errUpdate2 := userProfileQuery.UpdateUserProfileByID(ctx, newUserProfilePO) + errUpdate2 := userProfileQuery.UpdateUserProfileByID(ctx, &newUserProfilePO) if errUpdate2 != nil { return errUpdate2 } From 51411a919a02ae4cef8db7f5559df7c0f9a94869 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Sun, 11 Aug 2024 11:23:43 +0800 Subject: [PATCH 16/19] Refactor: Update user-related API routes and improve handlers - Renamed routes in userGroup - Added a new '/user' route in adminGroup for displaying the user list - Refined user-related handlers to improve readability - Implemented authentication checks across user-related endpoints --- handler/admin.go | 34 ++++++++++++++++++++++++++ handler/user.go | 62 +++++++++++++++++++++++++++++++++++------------ model/dto/user.go | 2 ++ router.go | 7 +++--- service/user.go | 6 +++++ 5 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 handler/admin.go diff --git a/handler/admin.go b/handler/admin.go new file mode 100644 index 0000000..f21ddd4 --- /dev/null +++ b/handler/admin.go @@ -0,0 +1,34 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "jcourse_go/model/domain" + "jcourse_go/model/dto" + "jcourse_go/service" + "net/http" +) + +func AdminGetUserList(c *gin.Context) { + var request dto.UserListRequest + if err := c.ShouldBindQuery(&request); err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) + return + } + + filter := domain.UserFilter{ + Page: request.Page, + PageSize: request.PageSize, + } + users, err := service.AdminGetUserList(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + } + total, _ := service.GetUserCount(c, filter) + response := dto.UserListResponseForAdmin{ + Page: request.Page, + PageSize: request.PageSize, + Total: total, + Data: users, + } + c.JSON(http.StatusOK, response) +} diff --git a/handler/user.go b/handler/user.go index 9eae4a8..9632a4e 100644 --- a/handler/user.go +++ b/handler/user.go @@ -1,6 +1,7 @@ package handler import ( + "errors" "github.com/gin-gonic/gin" "jcourse_go/constant" "jcourse_go/model/converter" @@ -14,9 +15,6 @@ import ( func GetSuggestedUserHandler(c *gin.Context) {} func GetUserListHandler(c *gin.Context) { - - // 管理员权限验证 - var request dto.UserListRequest if err := c.ShouldBindQuery(&request); err != nil { c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) @@ -41,7 +39,24 @@ func GetUserListHandler(c *gin.Context) { c.JSON(http.StatusOK, response) } -func GetCurrentUserSummaryHandler(c *gin.Context) { +func getUserIDFromRequest(c *gin.Context) (int64, error) { + userIDStr := c.Param("userID") + userID, err := strconv.Atoi(userIDStr) + if err != nil { + return -1, errors.New("非法用户ID") + } + return int64(userID), nil +} + +// 非公开信息? +func GetUserSummaryHandler(c *gin.Context) { + userID, err := getUserIDFromRequest(c) + if err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "非法用户ID"}) + return + } + + // UserSummary鉴权 userInterface, exists := c.Get(constant.CtxKeyUser) if !exists { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) @@ -49,6 +64,11 @@ func GetCurrentUserSummaryHandler(c *gin.Context) { } user, _ := userInterface.(*domain.User) + if user.ID != userID { + c.JSON(http.StatusForbidden, dto.BaseResponse{Message: "无权查看他人信息!"}) + return + } + me, err := service.GetUserSummaryByID(c, user.ID) if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) @@ -57,29 +77,43 @@ func GetCurrentUserSummaryHandler(c *gin.Context) { c.JSON(http.StatusOK, me) } +// 公开信息 func GetUserDetailHandler(c *gin.Context) { - userIDStr := c.Param("userID") - userID, err := strconv.Atoi(userIDStr) + userID, err := getUserIDFromRequest(c) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "非法用户ID"}) + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "非法用户ID"}) return } - userDetail, errDetail := service.GetUserDetailByID(c, int64(userID)) + userDetail, errDetail := service.GetUserDetailByID(c, userID) if errDetail != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) + return } c.JSON(http.StatusOK, userDetail) } -func GetCurrentUserProfileHandler(c *gin.Context) { +// 非公开信息 +func GetUserProfileHandler(c *gin.Context) { + userID, err := getUserIDFromRequest(c) + if err != nil { + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "非法用户ID"}) + return + } + + // UserPorfile鉴权 userInterface, exists := c.Get(constant.CtxKeyUser) if !exists { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) return } - user, _ := userInterface.(*domain.User) + + if user.ID != userID { + c.JSON(http.StatusForbidden, dto.BaseResponse{Message: "无权查看他人信息!"}) + return + } + me, err := service.GetUserProfileByID(c, user.ID) if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) @@ -107,11 +141,9 @@ func UpdateUserProfileHandler(c *gin.Context) { } func GetUserReviewsHandler(c *gin.Context) { - userIDStr := c.Param("userID") - - userID, err := strconv.Atoi(userIDStr) + userID, err := getUserIDFromRequest(c) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "非法用户ID"}) + c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "非法用户ID"}) return } @@ -124,7 +156,7 @@ func GetUserReviewsHandler(c *gin.Context) { filter := domain.ReviewFilter{ Page: request.Page, PageSize: request.PageSize, - UserID: int64(userID), + UserID: userID, } reviews, err := service.GetReviewList(c, filter) diff --git a/model/dto/user.go b/model/dto/user.go index 30f7979..28955c5 100644 --- a/model/dto/user.go +++ b/model/dto/user.go @@ -11,6 +11,8 @@ type UserListRequest struct { type UserListResponse = BasePaginateResponse[UserDetailDTO] +type UserListResponseForAdmin = BasePaginateResponse[UserProfileDTO] + type UserSummaryDTO struct { ID int64 `json:"id"` ReviewCount int64 `json:"review_count"` diff --git a/router.go b/router.go index 2bec19d..33b920c 100644 --- a/router.go +++ b/router.go @@ -51,16 +51,17 @@ func registerRouter(r *gin.Engine) { userGroup := needAuthGroup.Group("/user") userGroup.GET("", handler.GetUserListHandler) userGroup.GET("/suggest", handler.GetSuggestedUserHandler) - userGroup.GET("/me", handler.GetCurrentUserSummaryHandler) + userGroup.GET("/:userID/summary", handler.GetUserSummaryHandler) userGroup.GET("/:userID/detail", handler.GetUserDetailHandler) userGroup.GET("/:userID/reviews", handler.GetUserReviewsHandler) userGroup.POST("/:userID/watch", handler.WatchUserHandler) userGroup.POST("/:userID/unwatch", handler.UnWatchUserHandler) - userGroup.GET("/me/profile", handler.GetCurrentUserProfileHandler) - userGroup.PUT("/me/profile", handler.UpdateUserProfileHandler) + userGroup.GET("/:userID/profile", handler.GetUserProfileHandler) + userGroup.PUT("/:userID/profile", handler.UpdateUserProfileHandler) adminGroup := needAuthGroup.Group("/admin") adminGroup.Use(middleware.RequireAdmin()) + adminGroup.GET("/user", handler.AdminGetUserList) adminGroup.GET("") } diff --git a/service/user.go b/service/user.go index 105b7ad..8b0204a 100644 --- a/service/user.go +++ b/service/user.go @@ -155,6 +155,12 @@ func GetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserDetai return result, nil } +func AdminGetUserList(ctx context.Context, filter domain.UserFilter) ([]dto.UserProfileDTO, error) { + // 视前端而定获取用户的哪些信息 + // E.g. UserProfileDTO + return nil, nil +} + func GetUserCount(ctx context.Context, filter domain.UserFilter) (int64, error) { userQuery := repository.NewUserQuery() filter.Page, filter.PageSize = 0, 0 From 16cae31b11df2911f44752bdf297bb09474ba2a7 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Mon, 12 Aug 2024 11:05:23 +0800 Subject: [PATCH 17/19] Refactor: service-domain, handler-dto; fix bug in ConvertUserDomainToUserSummaryDTO func which caused cycle import --- handler/user.go | 18 ++++++++----- model/converter/user.go | 57 +++++++++++++++++++++++----------------- model/dto/user.go | 1 - service/user.go | 58 ++++++++--------------------------------- 4 files changed, 55 insertions(+), 79 deletions(-) diff --git a/handler/user.go b/handler/user.go index 9632a4e..3a40bda 100644 --- a/handler/user.go +++ b/handler/user.go @@ -69,12 +69,12 @@ func GetUserSummaryHandler(c *gin.Context) { return } - me, err := service.GetUserSummaryByID(c, user.ID) + userSummary, err := service.GetUserSummaryByID(c, user.ID) if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) return } - c.JSON(http.StatusOK, me) + c.JSON(http.StatusOK, userSummary) } // 公开信息 @@ -85,11 +85,13 @@ func GetUserDetailHandler(c *gin.Context) { return } - userDetail, errDetail := service.GetUserDetailByID(c, userID) - if errDetail != nil { + userDomain, err := service.GetUserDomainByID(c, userID) + if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) return } + + userDetail := converter.ConvertUserDomainToUserDetailDTO(userDomain) c.JSON(http.StatusOK, userDetail) } @@ -101,7 +103,7 @@ func GetUserProfileHandler(c *gin.Context) { return } - // UserPorfile鉴权 + // UserProfile鉴权 userInterface, exists := c.Get(constant.CtxKeyUser) if !exists { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) @@ -114,12 +116,14 @@ func GetUserProfileHandler(c *gin.Context) { return } - me, err := service.GetUserProfileByID(c, user.ID) + userDomain, err := service.GetUserDomainByID(c, user.ID) if err != nil { c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "此用户不存在!"}) return } - c.JSON(http.StatusOK, me) + + userProfile := converter.ConvertUserDomainToUserProfileDTO(userDomain) + c.JSON(http.StatusOK, userProfile) } func WatchUserHandler(c *gin.Context) {} diff --git a/model/converter/user.go b/model/converter/user.go index 99cb9ef..eea9dab 100644 --- a/model/converter/user.go +++ b/model/converter/user.go @@ -47,48 +47,57 @@ func ConvertUserDomainToReviewDTO(user domain.User) dto.UserInReviewDTO { } } -func ConvertToUserDetailDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserDetailDTO { - if userPO == nil { +func ConvertUserDomainToUserSummaryDTO(id int64, reviewCount int64, likeReceive int64, tipReceive int64, followingCourseCount int64) *dto.UserSummaryDTO { + return &dto.UserSummaryDTO{ + ID: id, + ReviewCount: reviewCount, + LikeReceive: likeReceive, + TipReceive: tipReceive, + FollowingCourseCount: followingCourseCount, + } +} + +func ConvertUserDomainToUserDetailDTO(userDomain *domain.User) *dto.UserDetailDTO { + if userDomain == nil { return nil } return &dto.UserDetailDTO{ - ID: int64(userPO.ID), - Username: userPO.Username, - Avatar: userProfilePO.Avatar, - Bio: userProfilePO.Bio, + ID: userDomain.ID, + Username: userDomain.Username, + Avatar: userDomain.Profile.Avatar, + Bio: userDomain.Profile.Bio, } } -func ConvertToUserProfileDTO(userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserProfileDTO { - if userPO == nil { +func ConvertUserDomainToUserProfileDTO(userDomain *domain.User) *dto.UserProfileDTO { + if userDomain == nil { return nil } return &dto.UserProfileDTO{ - ID: int64(userPO.ID), - UserID: userProfilePO.UserID, - Username: userPO.Username, - Bio: userProfilePO.Bio, - Email: userPO.Email, - Avatar: userProfilePO.Avatar, - Role: userPO.UserRole, - Department: userProfilePO.Department, - Major: userProfilePO.Major, - Grade: userProfilePO.Grade, + UserID: userDomain.ID, + Username: userDomain.Username, + Bio: userDomain.Profile.Bio, + Email: userDomain.Email, + Avatar: userDomain.Profile.Avatar, + Role: userDomain.Role, + Department: userDomain.Profile.Department, + Major: userDomain.Profile.Major, + Grade: userDomain.Profile.Grade, } } func ConvertUpdateUserProfileDTOToUserPO(userProfileDTO *dto.UserProfileDTO, userPO *po.UserPO) po.UserPO { - updatedUserPo := po.UserPO{ + updatedUserPO := po.UserPO{ Username: userProfileDTO.Username, Email: userPO.Email, Password: userPO.Password, UserRole: userPO.UserRole, LastSeenAt: time.Now(), } - if userProfileDTO.ID != 0 { - updatedUserPo.ID = userPO.ID + if userProfileDTO.UserID != 0 { + updatedUserPO.ID = uint(userProfileDTO.UserID) } - return updatedUserPo + return updatedUserPO } func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDTO, userProfilePO *po.UserProfilePO) po.UserProfilePO { @@ -103,8 +112,8 @@ func ConvertUpdateUserProfileDTOToUsrProfilePO(userProfileDTO *dto.UserProfileDT Grade: userProfileDTO.Grade, Bio: userProfileDTO.Bio, } - if updatedUserProfilePO.ID != 0 { - updatedUserProfilePO.ID = userProfilePO.ID + if userProfileDTO.UserID != 0 { + updatedUserProfilePO.ID = uint(userProfileDTO.UserID) } return updatedUserProfilePO } diff --git a/model/dto/user.go b/model/dto/user.go index 28955c5..257b704 100644 --- a/model/dto/user.go +++ b/model/dto/user.go @@ -29,7 +29,6 @@ type UserDetailDTO struct { } type UserProfileDTO struct { - ID int64 `json:"id"` UserID int64 `json:"user_id"` Username string `json:"username"` Bio string `json:"bio"` diff --git a/service/user.go b/service/user.go index 8b0204a..1e2272b 100644 --- a/service/user.go +++ b/service/user.go @@ -11,25 +11,17 @@ import ( "jcourse_go/repository" ) -// 该函数如果放在converter包中会报错import cycle is not allowed -// UserSummaryDTO的组装需要借助service/review.go中的GetReviewCount函数 -func ConvertToUserSummaryDTO(ctx context.Context, userPO *po.UserPO, userProfilePO *po.UserProfilePO) *dto.UserSummaryDTO { - if userPO == nil { - return nil - } - +func GetUserSummaryByID(ctx context.Context, userID int64) (*dto.UserSummaryDTO, error) { filter := domain.ReviewFilter{ - UserID: int64(userPO.ID), + UserID: userID, } total, _ := GetReviewCount(ctx, filter) + // 过滤非匿名点评 - return &dto.UserSummaryDTO{ - ID: int64(userPO.ID), - ReviewCount: total, - TipReceive: 0, - FollowingCourseCount: 0, - } + // 获取用户收到的赞数、被打赏积分数、关注的课程数 + + return converter.ConvertUserDomainToUserSummaryDTO(userID, total, 0, 0, 0), nil } func GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]domain.User, error) { @@ -61,23 +53,8 @@ func GetUserByIDs(ctx context.Context, userIDs []int64) (map[int64]domain.User, return result, nil } -func GetUserSummaryByID(ctx context.Context, userID int64) (*dto.UserSummaryDTO, error) { - userQuery := repository.NewUserQuery() - userPO, err := userQuery.GetUserByID(ctx, userID) - if err != nil { - return nil, err - } - - userProfileQuery := repository.NewUserProfileQuery() - userProfilePO, err := userProfileQuery.GetUserProfileByID(ctx, userID) - if err != nil { - return nil, err - } - userSummary := ConvertToUserSummaryDTO(ctx, userPO, userProfilePO) - return userSummary, nil -} - -func GetUserDetailByID(ctx context.Context, userID int64) (*dto.UserDetailDTO, error) { +// 共用函数,用于获取用户基本信息和详细资料并组装成domain.User +func GetUserDomainByID(ctx context.Context, userID int64) (*domain.User, error) { userQuery := repository.NewUserQuery() userPO, err := userQuery.GetUserByID(ctx, userID) if err != nil { @@ -89,24 +66,11 @@ func GetUserDetailByID(ctx context.Context, userID int64) (*dto.UserDetailDTO, e if err != nil { return nil, err } - userDetails := converter.ConvertToUserDetailDTO(userPO, userProfilePO) - return userDetails, nil -} -func GetUserProfileByID(ctx context.Context, userID int64) (*dto.UserProfileDTO, error) { - userQuery := repository.NewUserQuery() - userPO, err := userQuery.GetUserByID(ctx, userID) - if err != nil { - return nil, err - } + user := converter.ConvertUserPOToDomain(*userPO) + converter.PackUserWithProfile(&user, *userProfilePO) - userProfileQuery := repository.NewUserProfileQuery() - userProfilePO, err := userProfileQuery.GetUserProfileByID(ctx, userID) - if err != nil { - return nil, err - } - userProfile := converter.ConvertToUserProfileDTO(userPO, userProfilePO) - return userProfile, nil + return &user, nil } func buildUserDBOptionFromFilter(query repository.IUserQuery, filter domain.UserFilter) []repository.DBOption { From 3ae2d298b8d6cfbfa6463c652ece9c7eb8bbe993 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Wed, 14 Aug 2024 10:51:51 +0800 Subject: [PATCH 18/19] feat: Add authentication to UpdateUserProfileHandler and change save function parameter in repository/user.go from value to reference --- handler/user.go | 13 +++++++++++++ repository/user.go | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/handler/user.go b/handler/user.go index 3a40bda..407a536 100644 --- a/handler/user.go +++ b/handler/user.go @@ -131,11 +131,24 @@ func WatchUserHandler(c *gin.Context) {} func UnWatchUserHandler(c *gin.Context) {} func UpdateUserProfileHandler(c *gin.Context) { + userInterface, exists := c.Get(constant.CtxKeyUser) + if !exists { + c.JSON(http.StatusNotFound, dto.BaseResponse{Message: "用户未登录!"}) + return + } + user, _ := userInterface.(*domain.User) + var request dto.UserProfileDTO if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, dto.BaseResponse{Message: "参数错误"}) return } + + if user.ID != request.UserID { + c.JSON(http.StatusForbidden, dto.BaseResponse{Message: "无权更新其他用户信息!"}) + return + } + err := service.UpdateUserProfileByID(c, &request) if err != nil { c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "用户信息更新失败。"}) diff --git a/repository/user.go b/repository/user.go index 708d642..aa9caee 100644 --- a/repository/user.go +++ b/repository/user.go @@ -131,7 +131,7 @@ func (q *UserProfileQuery) GetUserProfileCount(ctx context.Context, opts ...DBOp } func (q *UserProfileQuery) UpdateUserProfileByID(ctx context.Context, userProfile *po.UserProfilePO) error { - result := q.optionDB(ctx, q.WithUserID(userProfile.UserID)).Save(userProfile).Error + result := q.optionDB(ctx, q.WithUserID(userProfile.UserID)).Save(&userProfile).Error return result } @@ -223,7 +223,7 @@ func (q *UserQuery) GetUserCount(ctx context.Context, opts ...DBOption) (int64, } func (q *UserQuery) UpdateUserByID(ctx context.Context, user *po.UserPO) error { - result := q.optionDB(ctx, q.WithID(int64(user.ID))).Save(user).Error + result := q.optionDB(ctx, q.WithID(int64(user.ID))).Save(&user).Error return result } From 3d901c4fc52be3825fc2eab2e556b9fb05d18a45 Mon Sep 17 00:00:00 2001 From: Victor Zhu <2364305645@qq.com> Date: Wed, 14 Aug 2024 11:30:32 +0800 Subject: [PATCH 19/19] fix: issues reported by golangci-lint --- handler/admin.go | 3 ++- handler/user.go | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/handler/admin.go b/handler/admin.go index f21ddd4..e5b4970 100644 --- a/handler/admin.go +++ b/handler/admin.go @@ -1,11 +1,12 @@ package handler import ( - "github.com/gin-gonic/gin" "jcourse_go/model/domain" "jcourse_go/model/dto" "jcourse_go/service" "net/http" + + "github.com/gin-gonic/gin" ) func AdminGetUserList(c *gin.Context) { diff --git a/handler/user.go b/handler/user.go index 407a536..30a82a2 100644 --- a/handler/user.go +++ b/handler/user.go @@ -2,7 +2,6 @@ package handler import ( "errors" - "github.com/gin-gonic/gin" "jcourse_go/constant" "jcourse_go/model/converter" "jcourse_go/model/domain" @@ -10,6 +9,8 @@ import ( "jcourse_go/service" "net/http" "strconv" + + "github.com/gin-gonic/gin" ) func GetSuggestedUserHandler(c *gin.Context) {} @@ -183,6 +184,10 @@ func GetUserReviewsHandler(c *gin.Context) { } total, err := service.GetReviewCount(c, filter) + if err != nil { + c.JSON(http.StatusInternalServerError, dto.BaseResponse{Message: "内部错误。"}) + return + } response := dto.ReviewListResponse{ Page: request.Page,