feat(verification): add user identity verification system
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:
@@ -50,6 +50,8 @@ func ProvideRouter(
|
||||
callHandler *handler.CallHandler,
|
||||
reportHandler *handler.ReportHandler,
|
||||
adminReportHandler *handler.AdminReportHandler,
|
||||
verificationHandler *handler.VerificationHandler,
|
||||
adminVerificationHandler *handler.AdminVerificationHandler,
|
||||
logService *service.LogService,
|
||||
activityService service.UserActivityService,
|
||||
casbinService service.CasbinService,
|
||||
@@ -82,6 +84,8 @@ func ProvideRouter(
|
||||
callHandler,
|
||||
reportHandler,
|
||||
adminReportHandler,
|
||||
verificationHandler,
|
||||
adminVerificationHandler,
|
||||
logService,
|
||||
activityService,
|
||||
casbinService,
|
||||
|
||||
@@ -127,8 +127,13 @@ func InitializeApp() (*App, error) {
|
||||
reportHandler := handler.NewReportHandler(reportService)
|
||||
adminReportService := wire.ProvideAdminReportService(reportRepository, postRepository, commentRepository, messageRepository, userRepository, systemNotificationRepository, pushService, transactionManager, operationLogService)
|
||||
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)
|
||||
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)
|
||||
app := NewApp(config, db, router, pushService, hotRankWorker, server, logger)
|
||||
return app, nil
|
||||
@@ -164,6 +169,8 @@ func ProvideRouter(
|
||||
callHandler *handler.CallHandler,
|
||||
reportHandler *handler.ReportHandler,
|
||||
adminReportHandler *handler.AdminReportHandler,
|
||||
verificationHandler *handler.VerificationHandler,
|
||||
adminVerificationHandler *handler.AdminVerificationHandler,
|
||||
logService *service.LogService,
|
||||
activityService service.UserActivityService,
|
||||
casbinService service.CasbinService,
|
||||
@@ -196,6 +203,8 @@ func ProvideRouter(
|
||||
callHandler,
|
||||
reportHandler,
|
||||
adminReportHandler,
|
||||
verificationHandler,
|
||||
adminVerificationHandler,
|
||||
logService,
|
||||
activityService,
|
||||
casbinService,
|
||||
|
||||
2
go.sum
2
go.sum
@@ -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/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/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.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
||||
@@ -1290,3 +1290,116 @@ type GroupAnnouncementCursorPageResponse struct {
|
||||
PrevCursor string `json:"prev_cursor,omitempty"`
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -110,23 +110,31 @@ var (
|
||||
ErrCallAlreadyAnswered = &AppError{Code: "CALL_ALREADY_ANSWERED", Message: "通话已在其他设备应答"}
|
||||
|
||||
// 消息相关错误
|
||||
ErrConversationNotFound = &AppError{Code: "CONVERSATION_NOT_FOUND", Message: "会话不存在或无权限"}
|
||||
ErrNotConversationMember = &AppError{Code: "NOT_CONVERSATION_MEMBER", Message: "不是会话参与者"}
|
||||
ErrMessageNotFound = &AppError{Code: "MESSAGE_NOT_FOUND", Message: "消息不存在"}
|
||||
ErrMessageAlreadyRecalled = &AppError{Code: "MESSAGE_ALREADY_RECALLED", Message: "消息已撤回"}
|
||||
ErrMessageRecallTimeout = &AppError{Code: "MESSAGE_RECALL_TIMEOUT", Message: "消息撤回超时(2分钟)"}
|
||||
ErrCannotSendMessage = &AppError{Code: "CANNOT_SEND_MESSAGE", Message: "无法发送消息"}
|
||||
ErrCannotSendImage = &AppError{Code: "CANNOT_SEND_IMAGE", Message: "对方未关注你,暂不支持发送图片"}
|
||||
ErrMessageLimitReached = &AppError{Code: "MESSAGE_LIMIT_REACHED", Message: "对方未关注你前,仅允许发送一条消息"}
|
||||
ErrQRCodeSessionExpired = &AppError{Code: "QRCODE_SESSION_EXPIRED", Message: "二维码已过期"}
|
||||
ErrQRCodeAlreadyScanned = &AppError{Code: "QRCODE_ALREADY_SCANNED", Message: "二维码已被扫描"}
|
||||
ErrQRCodeInvalidStatus = &AppError{Code: "QRCODE_INVALID_STATUS", Message: "二维码状态无效"}
|
||||
ErrPushNotImplemented = &AppError{Code: "PUSH_NOT_IMPLEMENTED", Message: "推送服务未实现"}
|
||||
ErrPushQueueFull = &AppError{Code: "PUSH_QUEUE_FULL", Message: "推送队列已满"}
|
||||
ErrUserOffline = &AppError{Code: "USER_OFFLINE", Message: "用户不在线"}
|
||||
ErrConversationNotFound = &AppError{Code: "CONVERSATION_NOT_FOUND", Message: "会话不存在或无权限"}
|
||||
ErrNotConversationMember = &AppError{Code: "NOT_CONVERSATION_MEMBER", Message: "不是会话参与者"}
|
||||
ErrMessageNotFound = &AppError{Code: "MESSAGE_NOT_FOUND", Message: "消息不存在"}
|
||||
ErrMessageAlreadyRecalled = &AppError{Code: "MESSAGE_ALREADY_RECALLED", Message: "消息已撤回"}
|
||||
ErrMessageRecallTimeout = &AppError{Code: "MESSAGE_RECALL_TIMEOUT", Message: "消息撤回超时(2分钟)"}
|
||||
ErrCannotSendMessage = &AppError{Code: "CANNOT_SEND_MESSAGE", Message: "无法发送消息"}
|
||||
ErrCannotSendImage = &AppError{Code: "CANNOT_SEND_IMAGE", Message: "对方未关注你,暂不支持发送图片"}
|
||||
ErrMessageLimitReached = &AppError{Code: "MESSAGE_LIMIT_REACHED", Message: "对方未关注你前,仅允许发送一条消息"}
|
||||
ErrQRCodeSessionExpired = &AppError{Code: "QRCODE_SESSION_EXPIRED", Message: "二维码已过期"}
|
||||
ErrQRCodeAlreadyScanned = &AppError{Code: "QRCODE_ALREADY_SCANNED", Message: "二维码已被扫描"}
|
||||
ErrQRCodeInvalidStatus = &AppError{Code: "QRCODE_INVALID_STATUS", Message: "二维码状态无效"}
|
||||
ErrPushNotImplemented = &AppError{Code: "PUSH_NOT_IMPLEMENTED", Message: "推送服务未实现"}
|
||||
ErrPushQueueFull = &AppError{Code: "PUSH_QUEUE_FULL", Message: "推送队列已满"}
|
||||
ErrUserOffline = &AppError{Code: "USER_OFFLINE", 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 包装错误
|
||||
|
||||
187
internal/handler/admin_verification_handler.go
Normal file
187
internal/handler/admin_verification_handler.go
Normal 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
|
||||
}
|
||||
137
internal/handler/verification_handler.go
Normal file
137
internal/handler/verification_handler.go
Normal 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
|
||||
}
|
||||
35
internal/middleware/verification.go
Normal file
35
internal/middleware/verification.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@@ -170,6 +170,9 @@ func autoMigrate(db *gorm.DB) error {
|
||||
// 通话相关
|
||||
&CallSession{},
|
||||
&CallParticipant{},
|
||||
|
||||
// 身份认证相关
|
||||
&VerificationRecord{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,6 +16,28 @@ const (
|
||||
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 用户实体
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey"`
|
||||
@@ -42,6 +64,10 @@ type User struct {
|
||||
FollowersCount int `json:"followers_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"`
|
||||
LastLoginAt *time.Time `json:"last_login_at" gorm:"type:timestamp"`
|
||||
|
||||
40
internal/model/verification.go
Normal file
40
internal/model/verification.go
Normal 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"
|
||||
}
|
||||
106
internal/repository/verification_repo.go
Normal file
106
internal/repository/verification_repo.go
Normal 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
|
||||
}
|
||||
@@ -11,36 +11,38 @@ import (
|
||||
|
||||
// Router 路由配置
|
||||
type Router struct {
|
||||
engine *gin.Engine
|
||||
userHandler *handler.UserHandler
|
||||
postHandler *handler.PostHandler
|
||||
commentHandler *handler.CommentHandler
|
||||
messageHandler *handler.MessageHandler
|
||||
notificationHandler *handler.NotificationHandler
|
||||
uploadHandler *handler.UploadHandler
|
||||
pushHandler *handler.PushHandler
|
||||
systemMessageHandler *handler.SystemMessageHandler
|
||||
groupHandler *handler.GroupHandler
|
||||
stickerHandler *handler.StickerHandler
|
||||
voteHandler *handler.VoteHandler
|
||||
channelHandler *handler.ChannelHandler
|
||||
scheduleHandler *handler.ScheduleHandler
|
||||
roleHandler *handler.RoleHandler
|
||||
adminUserHandler *handler.AdminUserHandler
|
||||
adminPostHandler *handler.AdminPostHandler
|
||||
adminCommentHandler *handler.AdminCommentHandler
|
||||
adminGroupHandler *handler.AdminGroupHandler
|
||||
adminDashboardHandler *handler.AdminDashboardHandler
|
||||
adminLogHandler *handler.AdminLogHandler
|
||||
qrcodeHandler *handler.QRCodeHandler
|
||||
materialHandler *handler.MaterialHandler
|
||||
callHandler *handler.CallHandler
|
||||
reportHandler *handler.ReportHandler
|
||||
adminReportHandler *handler.AdminReportHandler
|
||||
wsHandler *handler.WSHandler
|
||||
logService *service.LogService
|
||||
jwtService *service.JWTService
|
||||
casbinService service.CasbinService
|
||||
engine *gin.Engine
|
||||
userHandler *handler.UserHandler
|
||||
postHandler *handler.PostHandler
|
||||
commentHandler *handler.CommentHandler
|
||||
messageHandler *handler.MessageHandler
|
||||
notificationHandler *handler.NotificationHandler
|
||||
uploadHandler *handler.UploadHandler
|
||||
pushHandler *handler.PushHandler
|
||||
systemMessageHandler *handler.SystemMessageHandler
|
||||
groupHandler *handler.GroupHandler
|
||||
stickerHandler *handler.StickerHandler
|
||||
voteHandler *handler.VoteHandler
|
||||
channelHandler *handler.ChannelHandler
|
||||
scheduleHandler *handler.ScheduleHandler
|
||||
roleHandler *handler.RoleHandler
|
||||
adminUserHandler *handler.AdminUserHandler
|
||||
adminPostHandler *handler.AdminPostHandler
|
||||
adminCommentHandler *handler.AdminCommentHandler
|
||||
adminGroupHandler *handler.AdminGroupHandler
|
||||
adminDashboardHandler *handler.AdminDashboardHandler
|
||||
adminLogHandler *handler.AdminLogHandler
|
||||
qrcodeHandler *handler.QRCodeHandler
|
||||
materialHandler *handler.MaterialHandler
|
||||
callHandler *handler.CallHandler
|
||||
reportHandler *handler.ReportHandler
|
||||
adminReportHandler *handler.AdminReportHandler
|
||||
verificationHandler *handler.VerificationHandler
|
||||
adminVerificationHandler *handler.AdminVerificationHandler
|
||||
wsHandler *handler.WSHandler
|
||||
logService *service.LogService
|
||||
jwtService *service.JWTService
|
||||
casbinService service.CasbinService
|
||||
}
|
||||
|
||||
// New 创建路由
|
||||
@@ -71,6 +73,8 @@ func New(
|
||||
callHandler *handler.CallHandler,
|
||||
reportHandler *handler.ReportHandler,
|
||||
adminReportHandler *handler.AdminReportHandler,
|
||||
verificationHandler *handler.VerificationHandler,
|
||||
adminVerificationHandler *handler.AdminVerificationHandler,
|
||||
logService *service.LogService,
|
||||
activityService service.UserActivityService,
|
||||
casbinService service.CasbinService,
|
||||
@@ -82,36 +86,38 @@ func New(
|
||||
userHandler.SetActivityService(activityService)
|
||||
|
||||
r := &Router{
|
||||
engine: gin.Default(),
|
||||
userHandler: userHandler,
|
||||
postHandler: postHandler,
|
||||
commentHandler: commentHandler,
|
||||
messageHandler: messageHandler,
|
||||
notificationHandler: notificationHandler,
|
||||
uploadHandler: uploadHandler,
|
||||
pushHandler: pushHandler,
|
||||
systemMessageHandler: systemMessageHandler,
|
||||
groupHandler: groupHandler,
|
||||
stickerHandler: stickerHandler,
|
||||
voteHandler: voteHandler,
|
||||
channelHandler: channelHandler,
|
||||
scheduleHandler: scheduleHandler,
|
||||
roleHandler: roleHandler,
|
||||
adminUserHandler: adminUserHandler,
|
||||
adminPostHandler: adminPostHandler,
|
||||
adminCommentHandler: adminCommentHandler,
|
||||
adminGroupHandler: adminGroupHandler,
|
||||
adminDashboardHandler: adminDashboardHandler,
|
||||
adminLogHandler: adminLogHandler,
|
||||
qrcodeHandler: qrcodeHandler,
|
||||
materialHandler: materialHandler,
|
||||
callHandler: callHandler,
|
||||
reportHandler: reportHandler,
|
||||
adminReportHandler: adminReportHandler,
|
||||
logService: logService,
|
||||
jwtService: jwtService,
|
||||
casbinService: casbinService,
|
||||
wsHandler: wsHandler,
|
||||
engine: gin.Default(),
|
||||
userHandler: userHandler,
|
||||
postHandler: postHandler,
|
||||
commentHandler: commentHandler,
|
||||
messageHandler: messageHandler,
|
||||
notificationHandler: notificationHandler,
|
||||
uploadHandler: uploadHandler,
|
||||
pushHandler: pushHandler,
|
||||
systemMessageHandler: systemMessageHandler,
|
||||
groupHandler: groupHandler,
|
||||
stickerHandler: stickerHandler,
|
||||
voteHandler: voteHandler,
|
||||
channelHandler: channelHandler,
|
||||
scheduleHandler: scheduleHandler,
|
||||
roleHandler: roleHandler,
|
||||
adminUserHandler: adminUserHandler,
|
||||
adminPostHandler: adminPostHandler,
|
||||
adminCommentHandler: adminCommentHandler,
|
||||
adminGroupHandler: adminGroupHandler,
|
||||
adminDashboardHandler: adminDashboardHandler,
|
||||
adminLogHandler: adminLogHandler,
|
||||
qrcodeHandler: qrcodeHandler,
|
||||
materialHandler: materialHandler,
|
||||
callHandler: callHandler,
|
||||
reportHandler: reportHandler,
|
||||
adminReportHandler: adminReportHandler,
|
||||
verificationHandler: verificationHandler,
|
||||
adminVerificationHandler: adminVerificationHandler,
|
||||
logService: logService,
|
||||
jwtService: jwtService,
|
||||
casbinService: casbinService,
|
||||
wsHandler: wsHandler,
|
||||
}
|
||||
|
||||
r.setupRoutes()
|
||||
@@ -201,6 +207,17 @@ func (r *Router) setupRoutes() {
|
||||
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 {
|
||||
qrcode := v1.Group("/auth/qrcode")
|
||||
@@ -580,6 +597,13 @@ func (r *Router) setupRoutes() {
|
||||
admin.PUT("/reports/:id/handle", r.adminReportHandler.Handle)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
176
internal/service/verification_service.go
Normal file
176
internal/service/verification_service.go
Normal 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
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package wire
|
||||
import (
|
||||
"carrot_bbs/internal/handler"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
"carrot_bbs/internal/repository"
|
||||
"carrot_bbs/internal/service"
|
||||
|
||||
"github.com/google/wire"
|
||||
@@ -30,6 +31,7 @@ var HandlerSet = wire.NewSet(
|
||||
handler.NewCallHandler,
|
||||
handler.NewReportHandler,
|
||||
handler.NewAdminReportHandler,
|
||||
handler.NewVerificationHandler,
|
||||
|
||||
// 需要特殊处理的 Handler
|
||||
handler.NewUserHandler,
|
||||
@@ -39,6 +41,7 @@ var HandlerSet = wire.NewSet(
|
||||
ProvideGroupHandler,
|
||||
ProvideScheduleHandler,
|
||||
ProvideWSHandler,
|
||||
ProvideAdminVerificationHandler,
|
||||
)
|
||||
|
||||
// ProvideMessageHandler 提供消息处理器
|
||||
@@ -85,3 +88,11 @@ func ProvideWSHandler(
|
||||
) *handler.WSHandler {
|
||||
return handler.NewWSHandler(wsHub, chatService, groupService, jwtService, callService)
|
||||
}
|
||||
|
||||
// ProvideAdminVerificationHandler 提供管理端身份认证处理器
|
||||
func ProvideAdminVerificationHandler(
|
||||
adminVerificationService service.AdminVerificationService,
|
||||
userRepo repository.UserRepository,
|
||||
) *handler.AdminVerificationHandler {
|
||||
return handler.NewAdminVerificationHandler(adminVerificationService, userRepo)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ var RepositorySet = wire.NewSet(
|
||||
repository.NewOperationLogRepository,
|
||||
repository.NewLoginLogRepository,
|
||||
repository.NewDataChangeLogRepository,
|
||||
|
||||
// 身份认证相关仓储
|
||||
repository.NewVerificationRepository,
|
||||
)
|
||||
|
||||
// ProvideUserActivityRepository 提供用户活跃数据仓储
|
||||
|
||||
@@ -58,6 +58,8 @@ var ServiceSet = wire.NewSet(
|
||||
ProvideCallService,
|
||||
ProvideReportService,
|
||||
ProvideAdminReportService,
|
||||
ProvideVerificationService,
|
||||
ProvideAdminVerificationService,
|
||||
|
||||
// 日志服务
|
||||
ProvideAsyncLogManager,
|
||||
@@ -427,3 +429,19 @@ func ProvideAdminReportService(
|
||||
) service.AdminReportService {
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user