refactor(server): decouple services and improve architecture
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s

- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
This commit is contained in:
2026-06-15 03:41:59 +08:00
parent 9951043034
commit d9aa4b46c3
78 changed files with 2156 additions and 1640 deletions

View File

@@ -4,8 +4,8 @@ import (
"strconv"
"time"
"with_you/internal/dto"
"with_you/internal/pkg/response"
"with_you/internal/query"
"with_you/internal/service"
"github.com/gin-gonic/gin"
@@ -37,7 +37,7 @@ func (h *AdminLogHandler) GetOperationLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := dto.LogFilter{
filters := query.LogFilter{
UserID: c.Query("user_id"),
Operation: c.Query("operation"),
TargetType: c.Query("target_type"),
@@ -70,7 +70,7 @@ func (h *AdminLogHandler) GetLoginLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := dto.LoginFilter{
filters := query.LoginFilter{
UserID: c.Query("user_id"),
Event: c.Query("event"),
Result: c.Query("result"),
@@ -113,7 +113,7 @@ func (h *AdminLogHandler) GetDataChangeLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
filters := dto.DataChangeFilter{
filters := query.DataChangeFilter{
UserID: c.Query("user_id"),
OperatorID: c.Query("operator_id"),
ChangeType: c.Query("change_type"),
@@ -190,7 +190,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) {
"logs": logs,
})
case "login":
logs, _, err := h.loginLogService.GetLoginLogs(c.Request.Context(), dto.LoginFilter{}, 1, 10000)
logs, _, err := h.loginLogService.GetLoginLogs(c.Request.Context(), query.LoginFilter{}, 1, 10000)
if err != nil {
response.HandleError(c, err, "failed to export login logs")
return
@@ -200,7 +200,7 @@ func (h *AdminLogHandler) ExportLogs(c *gin.Context) {
"log": logs,
})
case "data_change":
logs, _, err := h.dataChangeLogService.GetDataChangeLogs(c.Request.Context(), dto.DataChangeFilter{}, 1, 10000)
logs, _, err := h.dataChangeLogService.GetDataChangeLogs(c.Request.Context(), query.DataChangeFilter{}, 1, 10000)
if err != nil {
response.HandleError(c, err, "failed to export data change logs")
return

View File

@@ -3,6 +3,7 @@ package handler
import (
"with_you/internal/dto"
"with_you/internal/pkg/response"
"with_you/internal/query"
"with_you/internal/service"
"github.com/gin-gonic/gin"
@@ -24,7 +25,7 @@ func NewAdminReportHandler(adminReportService service.AdminReportService) *Admin
// List 获取举报列表
func (h *AdminReportHandler) List(c *gin.Context) {
// 解析查询参数
var query dto.AdminReportListQuery
var query query.AdminReportListQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.BadRequest(c, "参数错误: "+err.Error())
return
@@ -136,4 +137,3 @@ func (h *AdminReportHandler) BatchHandle(c *gin.Context) {
response.Success(c, result)
}

View File

@@ -1,12 +1,12 @@
package handler
import (
"context"
"strconv"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/response"
"with_you/internal/repository"
"with_you/internal/service"
"github.com/gin-gonic/gin"
@@ -14,16 +14,16 @@ import (
type AdminVerificationHandler struct {
adminVerificationService service.AdminVerificationService
userRepo repository.UserRepository
userService service.UserService
}
func NewAdminVerificationHandler(
adminVerificationService service.AdminVerificationService,
userRepo repository.UserRepository,
userService service.UserService,
) *AdminVerificationHandler {
return &AdminVerificationHandler{
adminVerificationService: adminVerificationService,
userRepo: userRepo,
userService: userService,
}
}
@@ -55,7 +55,7 @@ func (h *AdminVerificationHandler) ListVerifications(c *gin.Context) {
responses := make([]*dto.AdminVerificationListResponse, len(records))
for i, record := range records {
responses[i] = h.convertToAdminListResponse(record)
responses[i] = h.convertToAdminListResponse(c.Request.Context(), record)
}
response.Paginated(c, responses, total, page, pageSize)
@@ -74,7 +74,7 @@ func (h *AdminVerificationHandler) GetVerificationDetail(c *gin.Context) {
return
}
response.Success(c, h.convertToAdminDetailResponse(record))
response.Success(c, h.convertToAdminDetailResponse(c.Request.Context(), record))
}
func (h *AdminVerificationHandler) ReviewVerification(c *gin.Context) {
@@ -113,10 +113,10 @@ func (h *AdminVerificationHandler) ReviewVerification(c *gin.Context) {
return
}
response.Success(c, h.convertToAdminDetailResponse(record))
response.Success(c, h.convertToAdminDetailResponse(c.Request.Context(), record))
}
func (h *AdminVerificationHandler) convertToAdminListResponse(record *model.VerificationRecord) *dto.AdminVerificationListResponse {
func (h *AdminVerificationHandler) convertToAdminListResponse(ctx context.Context, record *model.VerificationRecord) *dto.AdminVerificationListResponse {
resp := &dto.AdminVerificationListResponse{
ID: record.ID,
UserID: record.UserID,
@@ -129,7 +129,7 @@ func (h *AdminVerificationHandler) convertToAdminListResponse(record *model.Veri
CreatedAt: dto.FormatTime(record.CreatedAt),
}
user, err := h.userRepo.GetByID(record.UserID)
user, err := h.userService.GetUserByID(ctx, record.UserID)
if err == nil && user != nil {
resp.Username = user.Username
resp.Nickname = user.Nickname
@@ -139,7 +139,7 @@ func (h *AdminVerificationHandler) convertToAdminListResponse(record *model.Veri
if record.ReviewedAt != nil {
resp.ReviewedAt = dto.FormatTime(*record.ReviewedAt)
if record.ReviewedBy != nil {
reviewer, err := h.userRepo.GetByID(*record.ReviewedBy)
reviewer, err := h.userService.GetUserByID(ctx, *record.ReviewedBy)
if err == nil && reviewer != nil {
resp.ReviewerName = reviewer.Nickname
}
@@ -149,7 +149,7 @@ func (h *AdminVerificationHandler) convertToAdminListResponse(record *model.Veri
return resp
}
func (h *AdminVerificationHandler) convertToAdminDetailResponse(record *model.VerificationRecord) *dto.AdminVerificationDetailResponse {
func (h *AdminVerificationHandler) convertToAdminDetailResponse(ctx context.Context, record *model.VerificationRecord) *dto.AdminVerificationDetailResponse {
resp := &dto.AdminVerificationDetailResponse{
ID: record.ID,
UserID: record.UserID,
@@ -163,7 +163,7 @@ func (h *AdminVerificationHandler) convertToAdminDetailResponse(record *model.Ve
CreatedAt: dto.FormatTime(record.CreatedAt),
}
user, err := h.userRepo.GetByID(record.UserID)
user, err := h.userService.GetUserByID(ctx, record.UserID)
if err == nil && user != nil {
resp.Username = user.Username
resp.Nickname = user.Nickname
@@ -175,7 +175,7 @@ func (h *AdminVerificationHandler) convertToAdminDetailResponse(record *model.Ve
}
if record.ReviewedBy != nil {
resp.ReviewedBy = *record.ReviewedBy
if reviewer, err := h.userRepo.GetByID(*record.ReviewedBy); err == nil && reviewer != nil {
if reviewer, err := h.userService.GetUserByID(ctx, *record.ReviewedBy); err == nil && reviewer != nil {
resp.ReviewerName = reviewer.Nickname
}
}

View File

@@ -15,11 +15,11 @@ import (
// CommentHandler 评论处理器
type CommentHandler struct {
commentService *service.CommentService
commentService service.CommentService
}
// NewCommentHandler 创建评论处理器
func NewCommentHandler(commentService *service.CommentService) *CommentHandler {
func NewCommentHandler(commentService service.CommentService) *CommentHandler {
return &CommentHandler{
commentService: commentService,
}

View File

@@ -4,22 +4,18 @@ import (
"github.com/gin-gonic/gin"
"with_you/internal/pkg/response"
"with_you/internal/repository"
"with_you/internal/service"
)
type EmptyClassroomHandler struct {
classroomSyncService service.EmptyClassroomSyncService
classroomRepo repository.EmptyClassroomRepository
}
func NewEmptyClassroomHandler(
classroomSyncService service.EmptyClassroomSyncService,
classroomRepo repository.EmptyClassroomRepository,
) *EmptyClassroomHandler {
return &EmptyClassroomHandler{
classroomSyncService: classroomSyncService,
classroomRepo: classroomRepo,
}
}
@@ -77,7 +73,7 @@ func (h *EmptyClassroomHandler) ListEmptyClassrooms(c *gin.Context) {
return
}
classrooms, err := h.classroomRepo.ListByUserAndSemester(userID, semester)
classrooms, err := h.classroomSyncService.ListClassrooms(userID, semester)
if err != nil {
response.HandleError(c, err, "failed to list empty classrooms")
return

View File

@@ -4,19 +4,16 @@ import (
"github.com/gin-gonic/gin"
"with_you/internal/pkg/response"
"with_you/internal/repository"
"with_you/internal/service"
)
type ExamHandler struct {
examSyncService service.ExamSyncService
examRepo repository.ExamRepository
}
func NewExamHandler(examSyncService service.ExamSyncService, examRepo repository.ExamRepository) *ExamHandler {
func NewExamHandler(examSyncService service.ExamSyncService) *ExamHandler {
return &ExamHandler{
examSyncService: examSyncService,
examRepo: examRepo,
}
}
@@ -68,7 +65,7 @@ func (h *ExamHandler) ListExams(c *gin.Context) {
return
}
exams, err := h.examRepo.ListByUserAndSemester(userID, semester)
exams, err := h.examSyncService.ListExams(userID, semester)
if err != nil {
response.HandleError(c, err, "failed to list exams")
return

View File

@@ -4,19 +4,16 @@ import (
"github.com/gin-gonic/gin"
"with_you/internal/pkg/response"
"with_you/internal/repository"
"with_you/internal/service"
)
type GradeHandler struct {
gradeSyncService service.GradeSyncService
gradeRepo repository.GradeRepository
}
func NewGradeHandler(gradeSyncService service.GradeSyncService, gradeRepo repository.GradeRepository) *GradeHandler {
func NewGradeHandler(gradeSyncService service.GradeSyncService) *GradeHandler {
return &GradeHandler{
gradeSyncService: gradeSyncService,
gradeRepo: gradeRepo,
}
}
@@ -63,16 +60,14 @@ func (h *GradeHandler) ListGrades(c *gin.Context) {
return
}
grades, err := h.gradeRepo.ListByUser(userID)
summary, err := h.gradeSyncService.ListGradesWithSummary(userID)
if err != nil {
response.HandleError(c, err, "failed to list grades")
return
}
gpaSummary, _ := h.gradeRepo.GetLatestGpaSummary(userID)
response.Success(c, gin.H{
"grades": grades,
"gpa_summary": gpaSummary,
"grades": summary.Grades,
"gpa_summary": summary.GpaSummary,
})
}

View File

@@ -34,7 +34,7 @@ func NewLiveKitHandler(
liveKitService: liveKitService,
callService: callService,
config: &cfg.LiveKit,
logger: logger,
logger: logger,
}
}
@@ -163,4 +163,4 @@ func (h *LiveKitHandler) handleRoomFinished(ctx context.Context, event *livekit.
zap.Error(err),
)
}
}
}

View File

@@ -4,9 +4,9 @@ import (
"errors"
"strconv"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/response"
"with_you/internal/query"
"with_you/internal/service"
"github.com/gin-gonic/gin"
@@ -148,7 +148,7 @@ func (h *MaterialHandler) ListMaterials(c *gin.Context) {
fileType := c.Query("file_type")
keyword := c.Query("keyword")
params := dto.MaterialFileQueryParams{
params := query.MaterialFileQueryParams{
SubjectID: subjectID,
FileType: fileType,
Keyword: keyword,
@@ -329,7 +329,7 @@ func (h *MaterialHandler) AdminListMaterials(c *gin.Context) {
status := c.Query("status")
keyword := c.Query("keyword")
params := dto.MaterialFileQueryParams{
params := query.MaterialFileQueryParams{
SubjectID: subjectID,
FileType: fileType,
Status: status,

View File

@@ -87,14 +87,14 @@ func (h *MessageHandler) enrichConversations(ctx context.Context, convs []*model
// MessageHandler 消息处理器
type MessageHandler struct {
chatService service.ChatService
messageService *service.MessageService
messageService service.MessageService
userService service.UserService
groupService service.GroupService
wsPublisher ws.MessagePublisher
}
// NewMessageHandler 创建消息处理器
func NewMessageHandler(chatService service.ChatService, messageService *service.MessageService, userService service.UserService, groupService service.GroupService, wsPublisher ws.MessagePublisher) *MessageHandler {
func NewMessageHandler(chatService service.ChatService, messageService service.MessageService, userService service.UserService, groupService service.GroupService, wsPublisher ws.MessagePublisher) *MessageHandler {
return &MessageHandler{
chatService: chatService,
messageService: messageService,

View File

@@ -13,11 +13,11 @@ import (
// NotificationHandler 通知处理器
type NotificationHandler struct {
notificationService *service.NotificationService
notificationService service.NotificationService
}
// NewNotificationHandler 创建通知处理器
func NewNotificationHandler(notificationService *service.NotificationService) *NotificationHandler {
func NewNotificationHandler(notificationService service.NotificationService) *NotificationHandler {
return &NotificationHandler{
notificationService: notificationService,
}

View File

@@ -14,11 +14,11 @@ import (
// QRCodeHandler 二维码登录处理器
type QRCodeHandler struct {
qrcodeService *service.QRCodeLoginService
qrcodeService service.QRCodeLoginService
}
// NewQRCodeHandler 创建二维码登录处理器
func NewQRCodeHandler(qrcodeService *service.QRCodeLoginService) *QRCodeHandler {
func NewQRCodeHandler(qrcodeService service.QRCodeLoginService) *QRCodeHandler {
return &QRCodeHandler{
qrcodeService: qrcodeService,
}

View File

@@ -9,11 +9,11 @@ import (
// UploadHandler 上传处理器
type UploadHandler struct {
uploadService *service.UploadService
uploadService service.UploadService
}
// NewUploadHandler 创建上传处理器
func NewUploadHandler(uploadService *service.UploadService) *UploadHandler {
func NewUploadHandler(uploadService service.UploadService) *UploadHandler {
return &UploadHandler{
uploadService: uploadService,
}

View File

@@ -36,7 +36,7 @@ type UserHandler struct {
userService service.UserService
activityService service.UserActivityService
profileAuditService service.UserProfileAuditService
jwtService *service.JWTService
jwtService service.JWTService
logService *service.LogService
}
@@ -53,7 +53,7 @@ func (h *UserHandler) SetProfileAuditService(profileAuditService service.UserPro
}
// SetJWTService 设置JWT服务
func (h *UserHandler) SetJWTService(jwtService *service.JWTService) {
func (h *UserHandler) SetJWTService(jwtService service.JWTService) {
h.jwtService = jwtService
}

View File

@@ -13,12 +13,12 @@ import (
// VoteHandler 投票处理器
type VoteHandler struct {
voteService *service.VoteService
voteService service.VoteService
postService service.PostService
}
// NewVoteHandler 创建投票处理器
func NewVoteHandler(voteService *service.VoteService, postService service.PostService) *VoteHandler {
func NewVoteHandler(voteService service.VoteService, postService service.PostService) *VoteHandler {
return &VoteHandler{
voteService: voteService,
postService: postService,

View File

@@ -19,7 +19,6 @@ import (
"with_you/internal/model"
"with_you/internal/pkg/response"
"with_you/internal/pkg/ws"
"with_you/internal/repository"
"with_you/internal/service"
)
@@ -69,9 +68,9 @@ type WSHandler struct {
publisher ws.MessagePublisher
chatService service.ChatService
groupService service.GroupService
jwtService *service.JWTService
jwtService service.JWTService
callService service.CallService
userRepo repository.UserRepository
userService service.UserService
clientSeq uint64
}
@@ -92,9 +91,9 @@ func NewWSHandler(
publisher ws.MessagePublisher,
chatService service.ChatService,
groupService service.GroupService,
jwtService *service.JWTService,
jwtService service.JWTService,
callService service.CallService,
userRepo repository.UserRepository,
userService service.UserService,
) *WSHandler {
return &WSHandler{
publisher: publisher,
@@ -102,7 +101,7 @@ func NewWSHandler(
groupService: groupService,
jwtService: jwtService,
callService: callService,
userRepo: userRepo,
userService: userService,
}
}
@@ -578,7 +577,7 @@ func (h *WSHandler) handleAck(client *ws.Client, payload json.RawMessage) {
// isVerified 检查用户是否已通过身份认证
func (h *WSHandler) isVerified(ctx context.Context, client *ws.Client) bool {
user, err := h.userRepo.GetByID(client.UserID)
user, err := h.userService.GetUserByID(ctx, client.UserID)
if err != nil {
h.publisher.SendError(client, "internal_error", "获取用户信息失败")
return false