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,
|
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,
|
||||||
|
|||||||
@@ -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
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/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=
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,6 +127,14 @@ var (
|
|||||||
|
|
||||||
// 学习资料相关错误
|
// 学习资料相关错误
|
||||||
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 包装错误
|
||||||
|
|||||||
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{},
|
&CallSession{},
|
||||||
&CallParticipant{},
|
&CallParticipant{},
|
||||||
|
|
||||||
|
// 身份认证相关
|
||||||
|
&VerificationRecord{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
@@ -37,6 +37,8 @@ type Router struct {
|
|||||||
callHandler *handler.CallHandler
|
callHandler *handler.CallHandler
|
||||||
reportHandler *handler.ReportHandler
|
reportHandler *handler.ReportHandler
|
||||||
adminReportHandler *handler.AdminReportHandler
|
adminReportHandler *handler.AdminReportHandler
|
||||||
|
verificationHandler *handler.VerificationHandler
|
||||||
|
adminVerificationHandler *handler.AdminVerificationHandler
|
||||||
wsHandler *handler.WSHandler
|
wsHandler *handler.WSHandler
|
||||||
logService *service.LogService
|
logService *service.LogService
|
||||||
jwtService *service.JWTService
|
jwtService *service.JWTService
|
||||||
@@ -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,
|
||||||
@@ -108,6 +112,8 @@ func New(
|
|||||||
callHandler: callHandler,
|
callHandler: callHandler,
|
||||||
reportHandler: reportHandler,
|
reportHandler: reportHandler,
|
||||||
adminReportHandler: adminReportHandler,
|
adminReportHandler: adminReportHandler,
|
||||||
|
verificationHandler: verificationHandler,
|
||||||
|
adminVerificationHandler: adminVerificationHandler,
|
||||||
logService: logService,
|
logService: logService,
|
||||||
jwtService: jwtService,
|
jwtService: jwtService,
|
||||||
casbinService: casbinService,
|
casbinService: casbinService,
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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 (
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ var RepositorySet = wire.NewSet(
|
|||||||
repository.NewOperationLogRepository,
|
repository.NewOperationLogRepository,
|
||||||
repository.NewLoginLogRepository,
|
repository.NewLoginLogRepository,
|
||||||
repository.NewDataChangeLogRepository,
|
repository.NewDataChangeLogRepository,
|
||||||
|
|
||||||
|
// 身份认证相关仓储
|
||||||
|
repository.NewVerificationRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProvideUserActivityRepository 提供用户活跃数据仓储
|
// ProvideUserActivityRepository 提供用户活跃数据仓储
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user