feat(verification): add user identity verification system
Some checks failed
Build Backend / build (push) Successful in 7m12s
Build Backend / build-docker (push) Has been cancelled

Implement a complete user identity verification system with support for multiple identity types (student, teacher, staff, alumni). The system includes user-facing verification submission and status checking endpoints, admin endpoints for reviewing verification requests, middleware for protecting verification-required routes, and integration with the user model to track verification status.
This commit is contained in:
lafay
2026-04-05 20:27:03 +08:00
parent b640c9a249
commit 6429322217
17 changed files with 977 additions and 75 deletions

View File

@@ -50,6 +50,8 @@ func ProvideRouter(
callHandler *handler.CallHandler, callHandler *handler.CallHandler,
reportHandler *handler.ReportHandler, reportHandler *handler.ReportHandler,
adminReportHandler *handler.AdminReportHandler, adminReportHandler *handler.AdminReportHandler,
verificationHandler *handler.VerificationHandler,
adminVerificationHandler *handler.AdminVerificationHandler,
logService *service.LogService, logService *service.LogService,
activityService service.UserActivityService, activityService service.UserActivityService,
casbinService service.CasbinService, casbinService service.CasbinService,
@@ -82,6 +84,8 @@ func ProvideRouter(
callHandler, callHandler,
reportHandler, reportHandler,
adminReportHandler, adminReportHandler,
verificationHandler,
adminVerificationHandler,
logService, logService,
activityService, activityService,
casbinService, casbinService,

View File

@@ -127,8 +127,13 @@ func InitializeApp() (*App, error) {
reportHandler := handler.NewReportHandler(reportService) reportHandler := handler.NewReportHandler(reportService)
adminReportService := wire.ProvideAdminReportService(reportRepository, postRepository, commentRepository, messageRepository, userRepository, systemNotificationRepository, pushService, transactionManager, operationLogService) adminReportService := wire.ProvideAdminReportService(reportRepository, postRepository, commentRepository, messageRepository, userRepository, systemNotificationRepository, pushService, transactionManager, operationLogService)
adminReportHandler := handler.NewAdminReportHandler(adminReportService) adminReportHandler := handler.NewAdminReportHandler(adminReportService)
verificationRepository := repository.NewVerificationRepository(db)
verificationService := wire.ProvideVerificationService(verificationRepository, userRepository)
verificationHandler := handler.NewVerificationHandler(verificationService)
adminVerificationService := wire.ProvideAdminVerificationService(verificationRepository, userRepository)
adminVerificationHandler := wire.ProvideAdminVerificationHandler(adminVerificationService, userRepository)
wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService) wsHandler := wire.ProvideWSHandler(hub, chatService, groupService, jwtService, callService)
router := ProvideRouter(userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, logService, userActivityService, casbinService, wsHandler) router := ProvideRouter(userHandler, postHandler, commentHandler, messageHandler, notificationHandler, uploadHandler, jwtService, pushHandler, systemMessageHandler, groupHandler, stickerHandler, voteHandler, channelHandler, scheduleHandler, roleHandler, adminUserHandler, adminPostHandler, adminCommentHandler, adminGroupHandler, adminDashboardHandler, adminLogHandler, qrCodeHandler, materialHandler, callHandler, reportHandler, adminReportHandler, verificationHandler, adminVerificationHandler, logService, userActivityService, casbinService, wsHandler)
hotRankWorker := wire.ProvideHotRankWorker(config, postRepository, cache) hotRankWorker := wire.ProvideHotRankWorker(config, postRepository, cache)
app := NewApp(config, db, router, pushService, hotRankWorker, server, logger) app := NewApp(config, db, router, pushService, hotRankWorker, server, logger)
return app, nil return app, nil
@@ -164,6 +169,8 @@ func ProvideRouter(
callHandler *handler.CallHandler, callHandler *handler.CallHandler,
reportHandler *handler.ReportHandler, reportHandler *handler.ReportHandler,
adminReportHandler *handler.AdminReportHandler, adminReportHandler *handler.AdminReportHandler,
verificationHandler *handler.VerificationHandler,
adminVerificationHandler *handler.AdminVerificationHandler,
logService *service.LogService, logService *service.LogService,
activityService service.UserActivityService, activityService service.UserActivityService,
casbinService service.CasbinService, casbinService service.CasbinService,
@@ -196,6 +203,8 @@ func ProvideRouter(
callHandler, callHandler,
reportHandler, reportHandler,
adminReportHandler, adminReportHandler,
verificationHandler,
adminVerificationHandler,
logService, logService,
activityService, activityService,
casbinService, casbinService,

2
go.sum
View File

@@ -115,6 +115,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

View File

@@ -1290,3 +1290,116 @@ type GroupAnnouncementCursorPageResponse struct {
PrevCursor string `json:"prev_cursor,omitempty"` PrevCursor string `json:"prev_cursor,omitempty"`
HasMore bool `json:"has_more"` HasMore bool `json:"has_more"`
} }
// ==================== Verification DTOs ====================
// VerificationStatus 审核状态
type VerificationStatusDTO string
const (
VerificationStatusNotSubmittedDTO VerificationStatusDTO = "not_submitted"
VerificationStatusPendingDTO VerificationStatusDTO = "pending"
VerificationStatusApprovedDTO VerificationStatusDTO = "approved"
VerificationStatusRejectedDTO VerificationStatusDTO = "rejected"
)
// UserIdentityDTO 用户身份类型
type UserIdentityDTO string
const (
UserIdentityGeneralDTO UserIdentityDTO = "general"
UserIdentityStudentDTO UserIdentityDTO = "student"
UserIdentityTeacherDTO UserIdentityDTO = "teacher"
UserIdentityStaffDTO UserIdentityDTO = "staff"
UserIdentityAlumniDTO UserIdentityDTO = "alumni"
UserIdentityExternalDTO UserIdentityDTO = "external"
)
// SubmitVerificationRequest 提交认证申请请求
type SubmitVerificationRequest struct {
Identity UserIdentityDTO `json:"identity" binding:"required,oneof=student teacher staff alumni"`
RealName string `json:"real_name" binding:"required,max=100"`
StudentID string `json:"student_id" binding:"omitempty,max=50"`
EmployeeID string `json:"employee_id" binding:"omitempty,max=50"`
Department string `json:"department" binding:"omitempty,max=200"`
ProofMaterials string `json:"proof_materials" binding:"required"`
}
// VerificationRecordResponse 认证记录响应
type VerificationRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Identity string `json:"identity"`
Status string `json:"status"`
RealName string `json:"real_name"`
StudentID string `json:"student_id,omitempty"`
EmployeeID string `json:"employee_id,omitempty"`
Department string `json:"department,omitempty"`
ProofMaterials string `json:"proof_materials"`
ReviewedAt string `json:"reviewed_at,omitempty"`
ReviewedBy string `json:"reviewed_by,omitempty"`
RejectReason string `json:"reject_reason,omitempty"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// VerificationStatusResponse 认证状态响应
type VerificationStatusResponse struct {
Identity string `json:"identity"`
VerificationStatus string `json:"verification_status"`
HasPendingRequest bool `json:"has_pending_request"`
LatestRecord *VerificationRecordResponse `json:"latest_record,omitempty"`
}
// AdminVerificationListRequest 管理端认证列表请求
type AdminVerificationListRequest struct {
Page int `form:"page" binding:"omitempty,min=1"`
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100"`
Status string `form:"status" binding:"omitempty,oneof=pending approved rejected"`
Keyword string `form:"keyword" binding:"omitempty,max=100"`
}
// AdminVerificationListResponse 管理端认证列表响应
type AdminVerificationListResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Identity string `json:"identity"`
Status string `json:"status"`
RealName string `json:"real_name"`
StudentID string `json:"student_id,omitempty"`
EmployeeID string `json:"employee_id,omitempty"`
Department string `json:"department,omitempty"`
CreatedAt string `json:"created_at"`
ReviewedAt string `json:"reviewed_at,omitempty"`
ReviewerName string `json:"reviewer_name,omitempty"`
}
// AdminVerificationDetailResponse 管理端认证详情响应
type AdminVerificationDetailResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Identity string `json:"identity"`
Status string `json:"status"`
RealName string `json:"real_name"`
StudentID string `json:"student_id,omitempty"`
EmployeeID string `json:"employee_id,omitempty"`
Department string `json:"department,omitempty"`
ProofMaterials string `json:"proof_materials"`
CreatedAt string `json:"created_at"`
ReviewedAt string `json:"reviewed_at,omitempty"`
ReviewedBy string `json:"reviewed_by,omitempty"`
ReviewerName string `json:"reviewer_name,omitempty"`
RejectReason string `json:"reject_reason,omitempty"`
}
// AdminReviewVerificationRequest 管理端审核认证请求
type AdminReviewVerificationRequest struct {
Approve bool `json:"approve" binding:"required"`
RejectReason string `json:"reject_reason" binding:"omitempty,max=500"`
}

View File

@@ -110,23 +110,31 @@ var (
ErrCallAlreadyAnswered = &AppError{Code: "CALL_ALREADY_ANSWERED", Message: "通话已在其他设备应答"} ErrCallAlreadyAnswered = &AppError{Code: "CALL_ALREADY_ANSWERED", Message: "通话已在其他设备应答"}
// 消息相关错误 // 消息相关错误
ErrConversationNotFound = &AppError{Code: "CONVERSATION_NOT_FOUND", Message: "会话不存在或无权限"} ErrConversationNotFound = &AppError{Code: "CONVERSATION_NOT_FOUND", Message: "会话不存在或无权限"}
ErrNotConversationMember = &AppError{Code: "NOT_CONVERSATION_MEMBER", Message: "不是会话参与者"} ErrNotConversationMember = &AppError{Code: "NOT_CONVERSATION_MEMBER", Message: "不是会话参与者"}
ErrMessageNotFound = &AppError{Code: "MESSAGE_NOT_FOUND", Message: "消息不存在"} ErrMessageNotFound = &AppError{Code: "MESSAGE_NOT_FOUND", Message: "消息不存在"}
ErrMessageAlreadyRecalled = &AppError{Code: "MESSAGE_ALREADY_RECALLED", Message: "消息已撤回"} ErrMessageAlreadyRecalled = &AppError{Code: "MESSAGE_ALREADY_RECALLED", Message: "消息已撤回"}
ErrMessageRecallTimeout = &AppError{Code: "MESSAGE_RECALL_TIMEOUT", Message: "消息撤回超时2分钟"} ErrMessageRecallTimeout = &AppError{Code: "MESSAGE_RECALL_TIMEOUT", Message: "消息撤回超时2分钟"}
ErrCannotSendMessage = &AppError{Code: "CANNOT_SEND_MESSAGE", Message: "无法发送消息"} ErrCannotSendMessage = &AppError{Code: "CANNOT_SEND_MESSAGE", Message: "无法发送消息"}
ErrCannotSendImage = &AppError{Code: "CANNOT_SEND_IMAGE", Message: "对方未关注你,暂不支持发送图片"} ErrCannotSendImage = &AppError{Code: "CANNOT_SEND_IMAGE", Message: "对方未关注你,暂不支持发送图片"}
ErrMessageLimitReached = &AppError{Code: "MESSAGE_LIMIT_REACHED", Message: "对方未关注你前,仅允许发送一条消息"} ErrMessageLimitReached = &AppError{Code: "MESSAGE_LIMIT_REACHED", Message: "对方未关注你前,仅允许发送一条消息"}
ErrQRCodeSessionExpired = &AppError{Code: "QRCODE_SESSION_EXPIRED", Message: "二维码已过期"} ErrQRCodeSessionExpired = &AppError{Code: "QRCODE_SESSION_EXPIRED", Message: "二维码已过期"}
ErrQRCodeAlreadyScanned = &AppError{Code: "QRCODE_ALREADY_SCANNED", Message: "二维码已被扫描"} ErrQRCodeAlreadyScanned = &AppError{Code: "QRCODE_ALREADY_SCANNED", Message: "二维码已被扫描"}
ErrQRCodeInvalidStatus = &AppError{Code: "QRCODE_INVALID_STATUS", Message: "二维码状态无效"} ErrQRCodeInvalidStatus = &AppError{Code: "QRCODE_INVALID_STATUS", Message: "二维码状态无效"}
ErrPushNotImplemented = &AppError{Code: "PUSH_NOT_IMPLEMENTED", Message: "推送服务未实现"} ErrPushNotImplemented = &AppError{Code: "PUSH_NOT_IMPLEMENTED", Message: "推送服务未实现"}
ErrPushQueueFull = &AppError{Code: "PUSH_QUEUE_FULL", Message: "推送队列已满"} ErrPushQueueFull = &AppError{Code: "PUSH_QUEUE_FULL", Message: "推送队列已满"}
ErrUserOffline = &AppError{Code: "USER_OFFLINE", Message: "用户不在线"} ErrUserOffline = &AppError{Code: "USER_OFFLINE", Message: "用户不在线"}
// 学习资料相关错误 // 学习资料相关错误
ErrSubjectHasMaterials = &AppError{Code: "SUBJECT_HAS_MATERIALS", Message: "学科下存在资料,无法删除"} ErrSubjectHasMaterials = &AppError{Code: "SUBJECT_HAS_MATERIALS", Message: "学科下存在资料,无法删除"}
// 身份认证相关错误
ErrVerificationPending = &AppError{Code: "VERIFICATION_PENDING", Message: "已有待审核的认证申请,请等待审核"}
ErrVerificationNotFound = &AppError{Code: "VERIFICATION_NOT_FOUND", Message: "认证记录不存在"}
ErrInvalidIdentity = &AppError{Code: "INVALID_IDENTITY", Message: "无效的身份类型"}
ErrAlreadyVerified = &AppError{Code: "ALREADY_VERIFIED", Message: "已完成身份认证,无需重复申请"}
ErrVerificationAlreadyHandled = &AppError{Code: "VERIFICATION_ALREADY_HANDLED", Message: "该认证申请已被处理"}
ErrVerificationRequired = &AppError{Code: "VERIFICATION_REQUIRED", Message: "需要完成身份认证"}
) )
// Wrap 包装错误 // Wrap 包装错误

View File

@@ -0,0 +1,187 @@
package handler
import (
"strconv"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service"
"github.com/gin-gonic/gin"
)
type AdminVerificationHandler struct {
adminVerificationService service.AdminVerificationService
userRepo repository.UserRepository
}
func NewAdminVerificationHandler(
adminVerificationService service.AdminVerificationService,
userRepo repository.UserRepository,
) *AdminVerificationHandler {
return &AdminVerificationHandler{
adminVerificationService: adminVerificationService,
userRepo: userRepo,
}
}
func (h *AdminVerificationHandler) ListVerifications(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
status := c.Query("status")
keyword := c.Query("keyword")
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 20
}
if status != "" && status != string(model.VerificationStatusPending) &&
status != string(model.VerificationStatusApproved) &&
status != string(model.VerificationStatusRejected) {
response.BadRequest(c, "无效的状态值")
return
}
records, total, err := h.adminVerificationService.ListVerifications(c.Request.Context(), page, pageSize, status, keyword)
if err != nil {
response.InternalServerError(c, "获取认证列表失败")
return
}
responses := make([]*dto.AdminVerificationListResponse, len(records))
for i, record := range records {
responses[i] = h.convertToAdminListResponse(record)
}
response.Paginated(c, responses, total, page, pageSize)
}
func (h *AdminVerificationHandler) GetVerificationDetail(c *gin.Context) {
id := c.Param("id")
if id == "" {
response.BadRequest(c, "认证ID不能为空")
return
}
record, err := h.adminVerificationService.GetVerificationDetail(c.Request.Context(), id)
if err != nil {
response.NotFound(c, "认证记录不存在")
return
}
response.Success(c, h.convertToAdminDetailResponse(record))
}
func (h *AdminVerificationHandler) ReviewVerification(c *gin.Context) {
reviewerID, exists := c.Get("user_id")
if !exists {
response.Unauthorized(c, "未授权")
return
}
id := c.Param("id")
if id == "" {
response.BadRequest(c, "认证ID不能为空")
return
}
var req dto.AdminReviewVerificationRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请求参数错误: "+err.Error())
return
}
if !req.Approve && req.RejectReason == "" {
response.BadRequest(c, "拒绝时必须填写拒绝原因")
return
}
record, err := h.adminVerificationService.ReviewVerification(
c.Request.Context(),
reviewerID.(string),
id,
req.Approve,
req.RejectReason,
)
if err != nil {
response.HandleError(c, err, "审核失败")
return
}
response.Success(c, h.convertToAdminDetailResponse(record))
}
func (h *AdminVerificationHandler) convertToAdminListResponse(record *model.VerificationRecord) *dto.AdminVerificationListResponse {
resp := &dto.AdminVerificationListResponse{
ID: record.ID,
UserID: record.UserID,
Identity: string(record.Identity),
Status: string(record.Status),
RealName: record.RealName,
StudentID: record.StudentID,
EmployeeID: record.EmployeeID,
Department: record.Department,
CreatedAt: dto.FormatTime(record.CreatedAt),
}
user, err := h.userRepo.GetByID(record.UserID)
if err == nil && user != nil {
resp.Username = user.Username
resp.Nickname = user.Nickname
resp.Avatar = user.Avatar
}
if record.ReviewedAt != nil {
resp.ReviewedAt = dto.FormatTime(*record.ReviewedAt)
if record.ReviewedBy != nil {
reviewer, err := h.userRepo.GetByID(*record.ReviewedBy)
if err == nil && reviewer != nil {
resp.ReviewerName = reviewer.Nickname
}
}
}
return resp
}
func (h *AdminVerificationHandler) convertToAdminDetailResponse(record *model.VerificationRecord) *dto.AdminVerificationDetailResponse {
resp := &dto.AdminVerificationDetailResponse{
ID: record.ID,
UserID: record.UserID,
Identity: string(record.Identity),
Status: string(record.Status),
RealName: record.RealName,
StudentID: record.StudentID,
EmployeeID: record.EmployeeID,
Department: record.Department,
ProofMaterials: record.ProofMaterials,
CreatedAt: dto.FormatTime(record.CreatedAt),
}
user, err := h.userRepo.GetByID(record.UserID)
if err == nil && user != nil {
resp.Username = user.Username
resp.Nickname = user.Nickname
resp.Avatar = user.Avatar
}
if record.ReviewedAt != nil {
resp.ReviewedAt = dto.FormatTime(*record.ReviewedAt)
}
if record.ReviewedBy != nil {
resp.ReviewedBy = *record.ReviewedBy
if reviewer, err := h.userRepo.GetByID(*record.ReviewedBy); err == nil && reviewer != nil {
resp.ReviewerName = reviewer.Nickname
}
}
if record.RejectReason != "" {
resp.RejectReason = record.RejectReason
}
return resp
}

View File

@@ -0,0 +1,137 @@
package handler
import (
"strconv"
"carrot_bbs/internal/dto"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/service"
"github.com/gin-gonic/gin"
)
type VerificationHandler struct {
verificationService service.VerificationService
}
func NewVerificationHandler(verificationService service.VerificationService) *VerificationHandler {
return &VerificationHandler{
verificationService: verificationService,
}
}
func (h *VerificationHandler) SubmitVerification(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
response.Unauthorized(c, "未授权")
return
}
var req dto.SubmitVerificationRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请求参数错误: "+err.Error())
return
}
record, err := h.verificationService.SubmitVerification(
c.Request.Context(),
userID.(string),
model.UserIdentity(req.Identity),
req.RealName,
req.StudentID,
req.EmployeeID,
req.Department,
req.ProofMaterials,
)
if err != nil {
response.HandleError(c, err, "提交认证申请失败")
return
}
response.Success(c, convertVerificationRecordToResponse(record))
}
func (h *VerificationHandler) GetVerificationStatus(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
response.Unauthorized(c, "未授权")
return
}
status, err := h.verificationService.GetVerificationStatus(c.Request.Context(), userID.(string))
if err != nil {
response.HandleError(c, err, "获取认证状态失败")
return
}
response.Success(c, &dto.VerificationStatusResponse{
Identity: status.Identity,
VerificationStatus: status.VerificationStatus,
HasPendingRequest: status.HasPendingRequest,
LatestRecord: convertVerificationRecordToResponse(status.LatestRecord),
})
}
func (h *VerificationHandler) GetVerificationRecords(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
response.Unauthorized(c, "未授权")
return
}
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 50 {
pageSize = 10
}
records, total, err := h.verificationService.GetVerificationRecords(c.Request.Context(), userID.(string), page, pageSize)
if err != nil {
response.HandleError(c, err, "获取认证记录失败")
return
}
responses := make([]*dto.VerificationRecordResponse, len(records))
for i, record := range records {
responses[i] = convertVerificationRecordToResponse(record)
}
response.Paginated(c, responses, total, page, pageSize)
}
func convertVerificationRecordToResponse(record *model.VerificationRecord) *dto.VerificationRecordResponse {
if record == nil {
return nil
}
resp := &dto.VerificationRecordResponse{
ID: record.ID,
UserID: record.UserID,
Identity: string(record.Identity),
Status: string(record.Status),
RealName: record.RealName,
StudentID: record.StudentID,
EmployeeID: record.EmployeeID,
Department: record.Department,
ProofMaterials: record.ProofMaterials,
CreatedAt: dto.FormatTime(record.CreatedAt),
UpdatedAt: dto.FormatTime(record.UpdatedAt),
}
if record.ReviewedAt != nil {
resp.ReviewedAt = dto.FormatTime(*record.ReviewedAt)
}
if record.ReviewedBy != nil {
resp.ReviewedBy = *record.ReviewedBy
}
if record.RejectReason != "" {
resp.RejectReason = record.RejectReason
}
return resp
}

View File

@@ -0,0 +1,35 @@
package middleware
import (
"github.com/gin-gonic/gin"
"carrot_bbs/internal/model"
"carrot_bbs/internal/pkg/response"
"carrot_bbs/internal/repository"
)
func RequireVerification(userRepo repository.UserRepository) gin.HandlerFunc {
return func(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
response.Unauthorized(c, "未授权")
c.Abort()
return
}
user, err := userRepo.GetByID(userID.(string))
if err != nil {
response.HandleError(c, err, "获取用户信息失败")
c.Abort()
return
}
if user.VerificationStatus != model.VerificationStatusApproved {
response.Forbidden(c, "需要完成身份认证")
c.Abort()
return
}
c.Next()
}
}

View File

@@ -170,6 +170,9 @@ func autoMigrate(db *gorm.DB) error {
// 通话相关 // 通话相关
&CallSession{}, &CallSession{},
&CallParticipant{}, &CallParticipant{},
// 身份认证相关
&VerificationRecord{},
) )
if err != nil { if err != nil {
return err return err

View File

@@ -16,6 +16,28 @@ const (
UserStatusInactive UserStatus = "inactive" UserStatusInactive UserStatus = "inactive"
) )
// UserIdentity 用户身份类型
type UserIdentity string
const (
UserIdentityGeneral UserIdentity = "general" // 普通用户
UserIdentityStudent UserIdentity = "student" // 学生
UserIdentityTeacher UserIdentity = "teacher" // 教师
UserIdentityStaff UserIdentity = "staff" // 职工
UserIdentityAlumni UserIdentity = "alumni" // 校友
UserIdentityExternal UserIdentity = "external" // 外部人员
)
// VerificationStatus 审核状态
type VerificationStatus string
const (
VerificationStatusNotSubmitted VerificationStatus = "not_submitted" // 未提交
VerificationStatusPending VerificationStatus = "pending" // 审核中
VerificationStatusApproved VerificationStatus = "approved" // 已通过
VerificationStatusRejected VerificationStatus = "rejected" // 已拒绝
)
// User 用户实体 // User 用户实体
type User struct { type User struct {
ID string `json:"id" gorm:"type:varchar(36);primaryKey"` ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
@@ -42,6 +64,10 @@ type User struct {
FollowersCount int `json:"followers_count" gorm:"default:0"` FollowersCount int `json:"followers_count" gorm:"default:0"`
FollowingCount int `json:"following_count" gorm:"default:0"` FollowingCount int `json:"following_count" gorm:"default:0"`
// 身份认证
Identity UserIdentity `json:"identity" gorm:"type:varchar(20);default:general"`
VerificationStatus VerificationStatus `json:"verification_status" gorm:"type:varchar(20);default:not_submitted"`
// 状态 // 状态
Status UserStatus `json:"status" gorm:"type:varchar(20);default:active"` Status UserStatus `json:"status" gorm:"type:varchar(20);default:active"`
LastLoginAt *time.Time `json:"last_login_at" gorm:"type:timestamp"` LastLoginAt *time.Time `json:"last_login_at" gorm:"type:timestamp"`

View File

@@ -0,0 +1,40 @@
package model
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type VerificationRecord struct {
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
UserID string `json:"user_id" gorm:"type:varchar(36);index;not null"`
Identity UserIdentity `json:"identity" gorm:"type:varchar(20);not null"`
Status VerificationStatus `json:"status" gorm:"type:varchar(20);default:pending"`
RealName string `json:"real_name" gorm:"type:varchar(100)"`
StudentID string `json:"student_id" gorm:"type:varchar(50)"`
EmployeeID string `json:"employee_id" gorm:"type:varchar(50)"`
Department string `json:"department" gorm:"type:varchar(200)"`
ProofMaterials string `json:"proof_materials" gorm:"type:text"`
ReviewedAt *time.Time `json:"reviewed_at" gorm:"type:timestamp"`
ReviewedBy *string `json:"reviewed_by" gorm:"type:varchar(36)"`
RejectReason string `json:"reject_reason" gorm:"type:text"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
func (v *VerificationRecord) BeforeCreate(tx *gorm.DB) error {
if v.ID == "" {
v.ID = uuid.New().String()
}
return nil
}
func (VerificationRecord) TableName() string {
return "verification_records"
}

View File

@@ -0,0 +1,106 @@
package repository
import (
"carrot_bbs/internal/model"
"gorm.io/gorm"
)
type VerificationRepository interface {
Create(record *model.VerificationRecord) error
GetByID(id string) (*model.VerificationRecord, error)
GetLatestByUserID(userID string) (*model.VerificationRecord, error)
GetPendingByUserID(userID string) (*model.VerificationRecord, error)
List(page, pageSize int, status, keyword string) ([]*model.VerificationRecord, int64, error)
Update(record *model.VerificationRecord) error
GetRecordsByUserID(userID string, page, pageSize int) ([]*model.VerificationRecord, int64, error)
}
type verificationRepository struct {
db *gorm.DB
}
func NewVerificationRepository(db *gorm.DB) VerificationRepository {
return &verificationRepository{db: db}
}
func (r *verificationRepository) Create(record *model.VerificationRecord) error {
return r.db.Create(record).Error
}
func (r *verificationRepository) GetByID(id string) (*model.VerificationRecord, error) {
var record model.VerificationRecord
err := r.db.First(&record, "id = ?", id).Error
if err != nil {
return nil, err
}
return &record, nil
}
func (r *verificationRepository) GetLatestByUserID(userID string) (*model.VerificationRecord, error) {
var record model.VerificationRecord
err := r.db.Where("user_id = ?", userID).
Order("created_at DESC").
First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
func (r *verificationRepository) GetPendingByUserID(userID string) (*model.VerificationRecord, error) {
var record model.VerificationRecord
err := r.db.Where("user_id = ? AND status = ?", userID, model.VerificationStatusPending).
First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
func (r *verificationRepository) List(page, pageSize int, status, keyword string) ([]*model.VerificationRecord, int64, error) {
var records []*model.VerificationRecord
var total int64
query := r.db.Model(&model.VerificationRecord{})
if status != "" {
query = query.Where("verification_records.status = ?", status)
}
if keyword != "" {
query = query.Joins("JOIN users ON users.id = verification_records.user_id").
Where("users.username LIKE ? OR users.nickname LIKE ? OR verification_records.real_name LIKE ?",
"%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%")
}
query.Count(&total)
offset := (page - 1) * pageSize
err := query.Order("created_at DESC").
Offset(offset).
Limit(pageSize).
Find(&records).Error
return records, total, err
}
func (r *verificationRepository) Update(record *model.VerificationRecord) error {
return r.db.Save(record).Error
}
func (r *verificationRepository) GetRecordsByUserID(userID string, page, pageSize int) ([]*model.VerificationRecord, int64, error) {
var records []*model.VerificationRecord
var total int64
query := r.db.Model(&model.VerificationRecord{}).Where("user_id = ?", userID)
query.Count(&total)
offset := (page - 1) * pageSize
err := query.Order("created_at DESC").
Offset(offset).
Limit(pageSize).
Find(&records).Error
return records, total, err
}

View File

@@ -11,36 +11,38 @@ import (
// Router 路由配置 // Router 路由配置
type Router struct { type Router struct {
engine *gin.Engine engine *gin.Engine
userHandler *handler.UserHandler userHandler *handler.UserHandler
postHandler *handler.PostHandler postHandler *handler.PostHandler
commentHandler *handler.CommentHandler commentHandler *handler.CommentHandler
messageHandler *handler.MessageHandler messageHandler *handler.MessageHandler
notificationHandler *handler.NotificationHandler notificationHandler *handler.NotificationHandler
uploadHandler *handler.UploadHandler uploadHandler *handler.UploadHandler
pushHandler *handler.PushHandler pushHandler *handler.PushHandler
systemMessageHandler *handler.SystemMessageHandler systemMessageHandler *handler.SystemMessageHandler
groupHandler *handler.GroupHandler groupHandler *handler.GroupHandler
stickerHandler *handler.StickerHandler stickerHandler *handler.StickerHandler
voteHandler *handler.VoteHandler voteHandler *handler.VoteHandler
channelHandler *handler.ChannelHandler channelHandler *handler.ChannelHandler
scheduleHandler *handler.ScheduleHandler scheduleHandler *handler.ScheduleHandler
roleHandler *handler.RoleHandler roleHandler *handler.RoleHandler
adminUserHandler *handler.AdminUserHandler adminUserHandler *handler.AdminUserHandler
adminPostHandler *handler.AdminPostHandler adminPostHandler *handler.AdminPostHandler
adminCommentHandler *handler.AdminCommentHandler adminCommentHandler *handler.AdminCommentHandler
adminGroupHandler *handler.AdminGroupHandler adminGroupHandler *handler.AdminGroupHandler
adminDashboardHandler *handler.AdminDashboardHandler adminDashboardHandler *handler.AdminDashboardHandler
adminLogHandler *handler.AdminLogHandler adminLogHandler *handler.AdminLogHandler
qrcodeHandler *handler.QRCodeHandler qrcodeHandler *handler.QRCodeHandler
materialHandler *handler.MaterialHandler materialHandler *handler.MaterialHandler
callHandler *handler.CallHandler callHandler *handler.CallHandler
reportHandler *handler.ReportHandler reportHandler *handler.ReportHandler
adminReportHandler *handler.AdminReportHandler adminReportHandler *handler.AdminReportHandler
wsHandler *handler.WSHandler verificationHandler *handler.VerificationHandler
logService *service.LogService adminVerificationHandler *handler.AdminVerificationHandler
jwtService *service.JWTService wsHandler *handler.WSHandler
casbinService service.CasbinService logService *service.LogService
jwtService *service.JWTService
casbinService service.CasbinService
} }
// New 创建路由 // New 创建路由
@@ -71,6 +73,8 @@ func New(
callHandler *handler.CallHandler, callHandler *handler.CallHandler,
reportHandler *handler.ReportHandler, reportHandler *handler.ReportHandler,
adminReportHandler *handler.AdminReportHandler, adminReportHandler *handler.AdminReportHandler,
verificationHandler *handler.VerificationHandler,
adminVerificationHandler *handler.AdminVerificationHandler,
logService *service.LogService, logService *service.LogService,
activityService service.UserActivityService, activityService service.UserActivityService,
casbinService service.CasbinService, casbinService service.CasbinService,
@@ -82,36 +86,38 @@ func New(
userHandler.SetActivityService(activityService) userHandler.SetActivityService(activityService)
r := &Router{ r := &Router{
engine: gin.Default(), engine: gin.Default(),
userHandler: userHandler, userHandler: userHandler,
postHandler: postHandler, postHandler: postHandler,
commentHandler: commentHandler, commentHandler: commentHandler,
messageHandler: messageHandler, messageHandler: messageHandler,
notificationHandler: notificationHandler, notificationHandler: notificationHandler,
uploadHandler: uploadHandler, uploadHandler: uploadHandler,
pushHandler: pushHandler, pushHandler: pushHandler,
systemMessageHandler: systemMessageHandler, systemMessageHandler: systemMessageHandler,
groupHandler: groupHandler, groupHandler: groupHandler,
stickerHandler: stickerHandler, stickerHandler: stickerHandler,
voteHandler: voteHandler, voteHandler: voteHandler,
channelHandler: channelHandler, channelHandler: channelHandler,
scheduleHandler: scheduleHandler, scheduleHandler: scheduleHandler,
roleHandler: roleHandler, roleHandler: roleHandler,
adminUserHandler: adminUserHandler, adminUserHandler: adminUserHandler,
adminPostHandler: adminPostHandler, adminPostHandler: adminPostHandler,
adminCommentHandler: adminCommentHandler, adminCommentHandler: adminCommentHandler,
adminGroupHandler: adminGroupHandler, adminGroupHandler: adminGroupHandler,
adminDashboardHandler: adminDashboardHandler, adminDashboardHandler: adminDashboardHandler,
adminLogHandler: adminLogHandler, adminLogHandler: adminLogHandler,
qrcodeHandler: qrcodeHandler, qrcodeHandler: qrcodeHandler,
materialHandler: materialHandler, materialHandler: materialHandler,
callHandler: callHandler, callHandler: callHandler,
reportHandler: reportHandler, reportHandler: reportHandler,
adminReportHandler: adminReportHandler, adminReportHandler: adminReportHandler,
logService: logService, verificationHandler: verificationHandler,
jwtService: jwtService, adminVerificationHandler: adminVerificationHandler,
casbinService: casbinService, logService: logService,
wsHandler: wsHandler, jwtService: jwtService,
casbinService: casbinService,
wsHandler: wsHandler,
} }
r.setupRoutes() r.setupRoutes()
@@ -201,6 +207,17 @@ func (r *Router) setupRoutes() {
users.GET("/:id/favorites", middleware.OptionalAuth(r.jwtService), r.postHandler.GetFavorites) users.GET("/:id/favorites", middleware.OptionalAuth(r.jwtService), r.postHandler.GetFavorites)
} }
// 身份认证路由
if r.verificationHandler != nil {
verification := v1.Group("/verification")
verification.Use(authMiddleware)
{
verification.GET("/status", r.verificationHandler.GetVerificationStatus)
verification.POST("/submit", r.verificationHandler.SubmitVerification)
verification.GET("/records", r.verificationHandler.GetVerificationRecords)
}
}
// 二维码登录(公开) // 二维码登录(公开)
if r.qrcodeHandler != nil { if r.qrcodeHandler != nil {
qrcode := v1.Group("/auth/qrcode") qrcode := v1.Group("/auth/qrcode")
@@ -580,6 +597,13 @@ func (r *Router) setupRoutes() {
admin.PUT("/reports/:id/handle", r.adminReportHandler.Handle) admin.PUT("/reports/:id/handle", r.adminReportHandler.Handle)
admin.POST("/reports/batch-handle", r.adminReportHandler.BatchHandle) admin.POST("/reports/batch-handle", r.adminReportHandler.BatchHandle)
} }
// 身份认证管理
if r.adminVerificationHandler != nil {
admin.GET("/verifications", r.adminVerificationHandler.ListVerifications)
admin.GET("/verifications/:id", r.adminVerificationHandler.GetVerificationDetail)
admin.PUT("/verifications/:id/review", r.adminVerificationHandler.ReviewVerification)
}
} }
} }
} }

View File

@@ -0,0 +1,176 @@
package service
import (
"context"
"fmt"
"time"
apperrors "carrot_bbs/internal/errors"
"carrot_bbs/internal/model"
"carrot_bbs/internal/repository"
)
type VerificationService interface {
SubmitVerification(ctx context.Context, userID string, identity model.UserIdentity, realName, studentID, employeeID, department, proofMaterials string) (*model.VerificationRecord, error)
GetVerificationStatus(ctx context.Context, userID string) (*VerificationStatusResponse, error)
GetVerificationRecords(ctx context.Context, userID string, page, pageSize int) ([]*model.VerificationRecord, int64, error)
}
type VerificationStatusResponse struct {
UserID string `json:"user_id"`
Identity string `json:"identity"`
VerificationStatus string `json:"verification_status"`
HasPendingRequest bool `json:"has_pending_request"`
LatestRecord *model.VerificationRecord `json:"latest_record,omitempty"`
}
type verificationServiceImpl struct {
verificationRepo repository.VerificationRepository
userRepo repository.UserRepository
}
func NewVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) VerificationService {
return &verificationServiceImpl{
verificationRepo: verificationRepo,
userRepo: userRepo,
}
}
func (s *verificationServiceImpl) SubmitVerification(ctx context.Context, userID string, identity model.UserIdentity, realName, studentID, employeeID, department, proofMaterials string) (*model.VerificationRecord, error) {
user, err := s.userRepo.GetByID(userID)
if err != nil {
return nil, apperrors.ErrUserNotFound
}
if user.VerificationStatus == model.VerificationStatusApproved {
return nil, apperrors.ErrAlreadyVerified
}
existing, err := s.verificationRepo.GetPendingByUserID(userID)
if err == nil && existing != nil {
return nil, apperrors.ErrVerificationPending
}
record := &model.VerificationRecord{
UserID: userID,
Identity: identity,
Status: model.VerificationStatusPending,
RealName: realName,
StudentID: studentID,
EmployeeID: employeeID,
Department: department,
ProofMaterials: proofMaterials,
}
if err := s.verificationRepo.Create(record); err != nil {
return nil, fmt.Errorf("创建认证记录失败: %w", err)
}
user.VerificationStatus = model.VerificationStatusPending
user.Identity = identity
if err := s.userRepo.Update(user); err != nil {
return nil, fmt.Errorf("更新用户状态失败: %w", err)
}
return record, nil
}
func (s *verificationServiceImpl) GetVerificationStatus(ctx context.Context, userID string) (*VerificationStatusResponse, error) {
user, err := s.userRepo.GetByID(userID)
if err != nil {
return nil, apperrors.ErrUserNotFound
}
response := &VerificationStatusResponse{
UserID: userID,
Identity: string(user.Identity),
VerificationStatus: string(user.VerificationStatus),
HasPendingRequest: false,
}
latestRecord, err := s.verificationRepo.GetLatestByUserID(userID)
if err == nil && latestRecord != nil {
response.LatestRecord = latestRecord
response.HasPendingRequest = latestRecord.Status == model.VerificationStatusPending
}
return response, nil
}
func (s *verificationServiceImpl) GetVerificationRecords(ctx context.Context, userID string, page, pageSize int) ([]*model.VerificationRecord, int64, error) {
return s.verificationRepo.GetRecordsByUserID(userID, page, pageSize)
}
type AdminVerificationService interface {
ListVerifications(ctx context.Context, page, pageSize int, status, keyword string) ([]*model.VerificationRecord, int64, error)
GetVerificationDetail(ctx context.Context, id string) (*model.VerificationRecord, error)
ReviewVerification(ctx context.Context, reviewerID string, recordID string, approve bool, rejectReason string) (*model.VerificationRecord, error)
}
type adminVerificationServiceImpl struct {
verificationRepo repository.VerificationRepository
userRepo repository.UserRepository
}
func NewAdminVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) AdminVerificationService {
return &adminVerificationServiceImpl{
verificationRepo: verificationRepo,
userRepo: userRepo,
}
}
func (s *adminVerificationServiceImpl) ListVerifications(ctx context.Context, page, pageSize int, status, keyword string) ([]*model.VerificationRecord, int64, error) {
return s.verificationRepo.List(page, pageSize, status, keyword)
}
func (s *adminVerificationServiceImpl) GetVerificationDetail(ctx context.Context, id string) (*model.VerificationRecord, error) {
return s.verificationRepo.GetByID(id)
}
func (s *adminVerificationServiceImpl) ReviewVerification(ctx context.Context, reviewerID string, recordID string, approve bool, rejectReason string) (*model.VerificationRecord, error) {
record, err := s.verificationRepo.GetByID(recordID)
if err != nil {
return nil, apperrors.ErrVerificationNotFound
}
if record.Status != model.VerificationStatusPending {
return nil, apperrors.ErrVerificationAlreadyHandled
}
if approve {
record.Status = model.VerificationStatusApproved
} else {
record.Status = model.VerificationStatusRejected
record.RejectReason = rejectReason
}
now := time.Now()
record.ReviewedBy = &reviewerID
record.ReviewedAt = &now
if err := s.verificationRepo.Update(record); err != nil {
return nil, fmt.Errorf("更新认证记录失败: %w", err)
}
user, err := s.userRepo.GetByID(record.UserID)
if err != nil {
return record, nil
}
if approve {
user.VerificationStatus = model.VerificationStatusApproved
user.Identity = record.Identity
} else {
user.VerificationStatus = model.VerificationStatusRejected
}
_ = s.userRepo.Update(user)
return record, nil
}

View File

@@ -3,6 +3,7 @@ package wire
import ( import (
"carrot_bbs/internal/handler" "carrot_bbs/internal/handler"
"carrot_bbs/internal/pkg/ws" "carrot_bbs/internal/pkg/ws"
"carrot_bbs/internal/repository"
"carrot_bbs/internal/service" "carrot_bbs/internal/service"
"github.com/google/wire" "github.com/google/wire"
@@ -30,6 +31,7 @@ var HandlerSet = wire.NewSet(
handler.NewCallHandler, handler.NewCallHandler,
handler.NewReportHandler, handler.NewReportHandler,
handler.NewAdminReportHandler, handler.NewAdminReportHandler,
handler.NewVerificationHandler,
// 需要特殊处理的 Handler // 需要特殊处理的 Handler
handler.NewUserHandler, handler.NewUserHandler,
@@ -39,6 +41,7 @@ var HandlerSet = wire.NewSet(
ProvideGroupHandler, ProvideGroupHandler,
ProvideScheduleHandler, ProvideScheduleHandler,
ProvideWSHandler, ProvideWSHandler,
ProvideAdminVerificationHandler,
) )
// ProvideMessageHandler 提供消息处理器 // ProvideMessageHandler 提供消息处理器
@@ -85,3 +88,11 @@ func ProvideWSHandler(
) *handler.WSHandler { ) *handler.WSHandler {
return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService) return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService)
} }
// ProvideAdminVerificationHandler 提供管理端身份认证处理器
func ProvideAdminVerificationHandler(
adminVerificationService service.AdminVerificationService,
userRepo repository.UserRepository,
) *handler.AdminVerificationHandler {
return handler.NewAdminVerificationHandler(adminVerificationService, userRepo)
}

View File

@@ -34,6 +34,9 @@ var RepositorySet = wire.NewSet(
repository.NewOperationLogRepository, repository.NewOperationLogRepository,
repository.NewLoginLogRepository, repository.NewLoginLogRepository,
repository.NewDataChangeLogRepository, repository.NewDataChangeLogRepository,
// 身份认证相关仓储
repository.NewVerificationRepository,
) )
// ProvideUserActivityRepository 提供用户活跃数据仓储 // ProvideUserActivityRepository 提供用户活跃数据仓储

View File

@@ -58,6 +58,8 @@ var ServiceSet = wire.NewSet(
ProvideCallService, ProvideCallService,
ProvideReportService, ProvideReportService,
ProvideAdminReportService, ProvideAdminReportService,
ProvideVerificationService,
ProvideAdminVerificationService,
// 日志服务 // 日志服务
ProvideAsyncLogManager, ProvideAsyncLogManager,
@@ -427,3 +429,19 @@ func ProvideAdminReportService(
) service.AdminReportService { ) service.AdminReportService {
return service.NewAdminReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, notifyRepo, pushService, txManager, logService) return service.NewAdminReportService(reportRepo, postRepo, commentRepo, messageRepo, userRepo, notifyRepo, pushService, txManager, logService)
} }
// ProvideVerificationService 提供身份认证服务
func ProvideVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) service.VerificationService {
return service.NewVerificationService(verificationRepo, userRepo)
}
// ProvideAdminVerificationService 提供管理端身份认证服务
func ProvideAdminVerificationService(
verificationRepo repository.VerificationRepository,
userRepo repository.UserRepository,
) service.AdminVerificationService {
return service.NewAdminVerificationService(verificationRepo, userRepo)
}