537 lines
15 KiB
Go
537 lines
15 KiB
Go
package handlers
|
||
|
||
import (
|
||
"io"
|
||
"path/filepath"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"go.uber.org/zap"
|
||
|
||
"tyapi-server/internal/domains/certification/dto"
|
||
"tyapi-server/internal/domains/certification/services"
|
||
"tyapi-server/internal/shared/interfaces"
|
||
)
|
||
|
||
// CertificationHandler 认证处理器
|
||
type CertificationHandler struct {
|
||
certificationService *services.CertificationService
|
||
response interfaces.ResponseBuilder
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewCertificationHandler 创建认证处理器
|
||
func NewCertificationHandler(
|
||
certificationService *services.CertificationService,
|
||
response interfaces.ResponseBuilder,
|
||
logger *zap.Logger,
|
||
) *CertificationHandler {
|
||
return &CertificationHandler{
|
||
certificationService: certificationService,
|
||
response: response,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// CreateCertification 创建认证申请
|
||
// @Summary 创建认证申请
|
||
// @Description 用户创建企业认证申请
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Success 200 {object} dto.CertificationCreateResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/create [post]
|
||
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
result, err := h.certificationService.CreateCertification(c.Request.Context(), userID)
|
||
if err != nil {
|
||
h.logger.Error("创建认证申请失败",
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
h.response.InternalError(c, "创建认证申请失败")
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "认证申请创建成功")
|
||
}
|
||
|
||
// UploadLicense 上传营业执照
|
||
// @Summary 上传营业执照
|
||
// @Description 上传营业执照文件并进行OCR识别
|
||
// @Tags 认证
|
||
// @Accept multipart/form-data
|
||
// @Produce json
|
||
// @Param file formData file true "营业执照文件"
|
||
// @Success 200 {object} dto.UploadLicenseResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/upload-license [post]
|
||
func (h *CertificationHandler) UploadLicense(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
// 获取上传的文件
|
||
file, header, err := c.Request.FormFile("file")
|
||
if err != nil {
|
||
h.logger.Error("获取上传文件失败", zap.Error(err))
|
||
h.response.BadRequest(c, "请选择要上传的文件")
|
||
return
|
||
}
|
||
defer file.Close()
|
||
|
||
// 检查文件类型
|
||
fileName := header.Filename
|
||
ext := strings.ToLower(filepath.Ext(fileName))
|
||
allowedExts := []string{".jpg", ".jpeg", ".png", ".pdf"}
|
||
|
||
isAllowed := false
|
||
for _, allowedExt := range allowedExts {
|
||
if ext == allowedExt {
|
||
isAllowed = true
|
||
break
|
||
}
|
||
}
|
||
|
||
if !isAllowed {
|
||
h.response.BadRequest(c, "文件格式不支持,仅支持 JPG、PNG、PDF 格式")
|
||
return
|
||
}
|
||
|
||
// 检查文件大小(限制为10MB)
|
||
const maxFileSize = 10 * 1024 * 1024 // 10MB
|
||
if header.Size > maxFileSize {
|
||
h.response.BadRequest(c, "文件大小不能超过10MB")
|
||
return
|
||
}
|
||
|
||
// 读取文件内容
|
||
fileBytes, err := io.ReadAll(file)
|
||
if err != nil {
|
||
h.logger.Error("读取文件内容失败", zap.Error(err))
|
||
h.response.InternalError(c, "文件读取失败")
|
||
return
|
||
}
|
||
|
||
// 调用服务上传文件
|
||
result, err := h.certificationService.UploadLicense(c.Request.Context(), userID, fileBytes, fileName)
|
||
if err != nil {
|
||
h.logger.Error("上传营业执照失败",
|
||
zap.String("user_id", userID),
|
||
zap.String("file_name", fileName),
|
||
zap.Error(err),
|
||
)
|
||
h.response.InternalError(c, "上传失败,请稍后重试")
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "营业执照上传成功")
|
||
}
|
||
|
||
// SubmitEnterpriseInfo 提交企业信息
|
||
// @Summary 提交企业信息
|
||
// @Description 确认并提交企业四要素信息
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path string true "认证申请ID"
|
||
// @Param request body dto.SubmitEnterpriseInfoRequest true "企业信息"
|
||
// @Success 200 {object} dto.SubmitEnterpriseInfoResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/{id}/submit-info [put]
|
||
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
certificationID := c.Param("id")
|
||
if certificationID == "" {
|
||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||
return
|
||
}
|
||
|
||
var req dto.SubmitEnterpriseInfoRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||
h.response.BadRequest(c, "请求参数格式错误")
|
||
return
|
||
}
|
||
|
||
// 验证企业信息
|
||
if req.CompanyName == "" {
|
||
h.response.BadRequest(c, "企业名称不能为空")
|
||
return
|
||
}
|
||
if req.UnifiedSocialCode == "" {
|
||
h.response.BadRequest(c, "统一社会信用代码不能为空")
|
||
return
|
||
}
|
||
if req.LegalPersonName == "" {
|
||
h.response.BadRequest(c, "法定代表人姓名不能为空")
|
||
return
|
||
}
|
||
if req.LegalPersonID == "" {
|
||
h.response.BadRequest(c, "法定代表人身份证号不能为空")
|
||
return
|
||
}
|
||
if req.LicenseUploadRecordID == "" {
|
||
h.response.BadRequest(c, "营业执照上传记录ID不能为空")
|
||
return
|
||
}
|
||
|
||
result, err := h.certificationService.SubmitEnterpriseInfo(c.Request.Context(), certificationID, &req)
|
||
if err != nil {
|
||
h.logger.Error("提交企业信息失败",
|
||
zap.String("certification_id", certificationID),
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
if strings.Contains(err.Error(), "已被使用") || strings.Contains(err.Error(), "不允许") {
|
||
h.response.BadRequest(c, err.Error())
|
||
} else {
|
||
h.response.InternalError(c, "提交失败,请稍后重试")
|
||
}
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "企业信息提交成功")
|
||
}
|
||
|
||
// InitiateFaceVerify 初始化人脸识别
|
||
// @Summary 初始化人脸识别
|
||
// @Description 开始人脸识别认证流程
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path string true "认证申请ID"
|
||
// @Param request body dto.FaceVerifyRequest true "人脸识别请求"
|
||
// @Success 200 {object} dto.FaceVerifyResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/{id}/face-verify [post]
|
||
func (h *CertificationHandler) InitiateFaceVerify(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
certificationID := c.Param("id")
|
||
if certificationID == "" {
|
||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||
return
|
||
}
|
||
|
||
var req dto.FaceVerifyRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||
h.response.BadRequest(c, "请求参数格式错误")
|
||
return
|
||
}
|
||
|
||
// 验证请求参数
|
||
if req.RealName == "" {
|
||
h.response.BadRequest(c, "真实姓名不能为空")
|
||
return
|
||
}
|
||
if req.IDCardNumber == "" {
|
||
h.response.BadRequest(c, "身份证号不能为空")
|
||
return
|
||
}
|
||
|
||
result, err := h.certificationService.InitiateFaceVerify(c.Request.Context(), certificationID, &req)
|
||
if err != nil {
|
||
h.logger.Error("初始化人脸识别失败",
|
||
zap.String("certification_id", certificationID),
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
if strings.Contains(err.Error(), "不允许") {
|
||
h.response.BadRequest(c, err.Error())
|
||
} else {
|
||
h.response.InternalError(c, "初始化失败,请稍后重试")
|
||
}
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "人脸识别初始化成功")
|
||
}
|
||
|
||
// ApplyContract 申请电子合同
|
||
// @Summary 申请电子合同
|
||
// @Description 申请生成企业认证电子合同
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path string true "认证申请ID"
|
||
// @Success 200 {object} dto.ApplyContractResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/{id}/apply-contract [post]
|
||
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
certificationID := c.Param("id")
|
||
if certificationID == "" {
|
||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||
return
|
||
}
|
||
|
||
result, err := h.certificationService.ApplyContract(c.Request.Context(), certificationID)
|
||
if err != nil {
|
||
h.logger.Error("申请电子合同失败",
|
||
zap.String("certification_id", certificationID),
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
if strings.Contains(err.Error(), "不允许") {
|
||
h.response.BadRequest(c, err.Error())
|
||
} else {
|
||
h.response.InternalError(c, "申请失败,请稍后重试")
|
||
}
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "合同申请提交成功,请等待管理员审核")
|
||
}
|
||
|
||
// GetCertificationStatus 获取认证状态
|
||
// @Summary 获取认证状态
|
||
// @Description 查询当前用户的认证申请状态和进度
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Success 200 {object} dto.CertificationStatusResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/status [get]
|
||
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||
if err != nil {
|
||
h.logger.Error("获取认证状态失败",
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
if strings.Contains(err.Error(), "不存在") {
|
||
h.response.NotFound(c, "未找到认证申请记录")
|
||
} else {
|
||
h.response.InternalError(c, "查询失败,请稍后重试")
|
||
}
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "查询成功")
|
||
}
|
||
|
||
// GetCertificationDetails 获取认证详情
|
||
// @Summary 获取认证详情
|
||
// @Description 获取指定认证申请的详细信息
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path string true "认证申请ID"
|
||
// @Success 200 {object} dto.CertificationStatusResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/{id} [get]
|
||
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
certificationID := c.Param("id")
|
||
if certificationID == "" {
|
||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||
return
|
||
}
|
||
|
||
// 通过用户ID获取状态来确保用户只能查看自己的认证记录
|
||
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||
if err != nil {
|
||
h.logger.Error("获取认证详情失败",
|
||
zap.String("certification_id", certificationID),
|
||
zap.String("user_id", userID),
|
||
zap.Error(err),
|
||
)
|
||
if strings.Contains(err.Error(), "不存在") {
|
||
h.response.NotFound(c, "未找到认证申请记录")
|
||
} else {
|
||
h.response.InternalError(c, "查询失败,请稍后重试")
|
||
}
|
||
return
|
||
}
|
||
|
||
// 检查是否是用户自己的认证记录
|
||
if result.ID != certificationID {
|
||
h.response.Forbidden(c, "无权访问此认证记录")
|
||
return
|
||
}
|
||
|
||
h.response.Success(c, result, "查询成功")
|
||
}
|
||
|
||
// RetryStep 重试认证步骤
|
||
// @Summary 重试认证步骤
|
||
// @Description 重试失败的认证步骤(如人脸识别失败、签署失败等)
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Param id path string true "认证申请ID"
|
||
// @Param step query string true "重试步骤(face_verify, sign_contract)"
|
||
// @Success 200 {object} interfaces.APIResponse
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/{id}/retry [post]
|
||
func (h *CertificationHandler) RetryStep(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
certificationID := c.Param("id")
|
||
if certificationID == "" {
|
||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||
return
|
||
}
|
||
|
||
step := c.Query("step")
|
||
if step == "" {
|
||
h.response.BadRequest(c, "重试步骤不能为空")
|
||
return
|
||
}
|
||
|
||
// TODO: 实现重试逻辑
|
||
// 这里需要根据不同的步骤调用状态机进行状态重置
|
||
|
||
h.logger.Info("重试认证步骤",
|
||
zap.String("certification_id", certificationID),
|
||
zap.String("user_id", userID),
|
||
zap.String("step", step),
|
||
)
|
||
|
||
h.response.Success(c, gin.H{
|
||
"certification_id": certificationID,
|
||
"step": step,
|
||
"message": "重试操作已提交",
|
||
}, "重试操作成功")
|
||
}
|
||
|
||
// GetProgressStats 获取进度统计
|
||
// @Summary 获取进度统计
|
||
// @Description 获取用户认证申请的进度统计信息
|
||
// @Tags 认证
|
||
// @Accept json
|
||
// @Produce json
|
||
// @Success 200 {object} map[string]interface{}
|
||
// @Failure 400 {object} interfaces.APIResponse
|
||
// @Failure 500 {object} interfaces.APIResponse
|
||
// @Router /api/v1/certification/progress [get]
|
||
func (h *CertificationHandler) GetProgressStats(c *gin.Context) {
|
||
userID := c.GetString("user_id")
|
||
if userID == "" {
|
||
h.response.Unauthorized(c, "用户未认证")
|
||
return
|
||
}
|
||
|
||
// 获取认证状态
|
||
status, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||
if err != nil {
|
||
if strings.Contains(err.Error(), "不存在") {
|
||
h.response.Success(c, gin.H{
|
||
"has_certification": false,
|
||
"progress": 0,
|
||
"status": "",
|
||
"next_steps": []string{"开始企业认证"},
|
||
}, "查询成功")
|
||
return
|
||
}
|
||
h.response.InternalError(c, "查询失败")
|
||
return
|
||
}
|
||
|
||
// 构建进度统计
|
||
nextSteps := []string{}
|
||
if status.IsUserActionRequired {
|
||
switch status.Status {
|
||
case "pending":
|
||
nextSteps = append(nextSteps, "上传营业执照")
|
||
case "info_submitted":
|
||
nextSteps = append(nextSteps, "进行人脸识别")
|
||
case "face_verified":
|
||
nextSteps = append(nextSteps, "申请电子合同")
|
||
case "contract_approved":
|
||
nextSteps = append(nextSteps, "签署电子合同")
|
||
case "face_failed":
|
||
nextSteps = append(nextSteps, "重新进行人脸识别")
|
||
case "sign_failed":
|
||
nextSteps = append(nextSteps, "重新签署合同")
|
||
}
|
||
} else if status.IsAdminActionRequired {
|
||
nextSteps = append(nextSteps, "等待管理员审核")
|
||
} else {
|
||
nextSteps = append(nextSteps, "认证流程已完成")
|
||
}
|
||
|
||
result := gin.H{
|
||
"has_certification": true,
|
||
"certification_id": status.ID,
|
||
"progress": status.Progress,
|
||
"status": status.Status,
|
||
"status_name": status.StatusName,
|
||
"is_user_action_required": status.IsUserActionRequired,
|
||
"is_admin_action_required": status.IsAdminActionRequired,
|
||
"next_steps": nextSteps,
|
||
"created_at": status.CreatedAt,
|
||
"updated_at": status.UpdatedAt,
|
||
}
|
||
|
||
h.response.Success(c, result, "查询成功")
|
||
}
|
||
|
||
// parsePageParams 解析分页参数
|
||
func (h *CertificationHandler) parsePageParams(c *gin.Context) (int, int) {
|
||
page := 1
|
||
pageSize := 20
|
||
|
||
if pageStr := c.Query("page"); pageStr != "" {
|
||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||
page = p
|
||
}
|
||
}
|
||
|
||
if sizeStr := c.Query("page_size"); sizeStr != "" {
|
||
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
|
||
pageSize = s
|
||
}
|
||
}
|
||
|
||
return page, pageSize
|
||
}
|