基础架构

This commit is contained in:
2025-07-13 16:36:20 +08:00
parent e3d64e7485
commit 807004f78d
128 changed files with 17232 additions and 11396 deletions

View File

@@ -0,0 +1,21 @@
package admin
import (
"context"
"tyapi-server/internal/application/admin/dto/commands"
"tyapi-server/internal/application/admin/dto/queries"
"tyapi-server/internal/application/admin/dto/responses"
)
// AdminApplicationService 管理员应用服务接口
type AdminApplicationService interface {
Login(ctx context.Context, cmd *commands.AdminLoginCommand) (*responses.AdminLoginResponse, error)
CreateAdmin(ctx context.Context, cmd *commands.CreateAdminCommand) error
UpdateAdmin(ctx context.Context, cmd *commands.UpdateAdminCommand) error
ChangePassword(ctx context.Context, cmd *commands.ChangeAdminPasswordCommand) error
ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) (*responses.AdminListResponse, error)
GetAdminByID(ctx context.Context, query *queries.GetAdminInfoQuery) (*responses.AdminInfoResponse, error)
DeleteAdmin(ctx context.Context, cmd *commands.DeleteAdminCommand) error
GetAdminStats(ctx context.Context) (*responses.AdminStatsResponse, error)
}

View File

@@ -0,0 +1,164 @@
package admin
import (
"context"
"encoding/json"
"fmt"
"time"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"tyapi-server/internal/application/admin/dto/commands"
"tyapi-server/internal/application/admin/dto/queries"
"tyapi-server/internal/application/admin/dto/responses"
"tyapi-server/internal/domains/admin/entities"
"tyapi-server/internal/domains/admin/repositories"
)
// AdminApplicationServiceImpl 管理员应用服务实现
type AdminApplicationServiceImpl struct {
adminRepo repositories.AdminRepository
loginLogRepo repositories.AdminLoginLogRepository
operationLogRepo repositories.AdminOperationLogRepository
permissionRepo repositories.AdminPermissionRepository
logger *zap.Logger
}
// NewAdminApplicationService 创建管理员应用服务
func NewAdminApplicationService(
adminRepo repositories.AdminRepository,
loginLogRepo repositories.AdminLoginLogRepository,
operationLogRepo repositories.AdminOperationLogRepository,
permissionRepo repositories.AdminPermissionRepository,
logger *zap.Logger,
) AdminApplicationService {
return &AdminApplicationServiceImpl{
adminRepo: adminRepo,
loginLogRepo: loginLogRepo,
operationLogRepo: operationLogRepo,
permissionRepo: permissionRepo,
logger: logger,
}
}
func (s *AdminApplicationServiceImpl) Login(ctx context.Context, cmd *commands.AdminLoginCommand) (*responses.AdminLoginResponse, error) {
s.logger.Info("管理员登录", zap.String("username", cmd.Username))
admin, err := s.adminRepo.FindByUsername(ctx, cmd.Username)
if err != nil {
s.logger.Warn("管理员登录失败:用户不存在", zap.String("username", cmd.Username))
return nil, fmt.Errorf("用户名或密码错误")
}
if !admin.IsActive {
s.logger.Warn("管理员登录失败:账户已禁用", zap.String("username", cmd.Username))
return nil, fmt.Errorf("账户已被禁用,请联系管理员")
}
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(cmd.Password)); err != nil {
s.logger.Warn("管理员登录失败:密码错误", zap.String("username", cmd.Username))
return nil, fmt.Errorf("用户名或密码错误")
}
if err := s.adminRepo.UpdateLoginStats(ctx, admin.ID); err != nil {
s.logger.Error("更新登录统计失败", zap.Error(err))
}
// This part would ideally be in a separate auth service or helper
token, expiresAt, err := s.generateJWTToken(admin)
if err != nil {
return nil, fmt.Errorf("生成令牌失败: %w", err)
}
permissions, err := s.getAdminPermissions(ctx, admin)
if err != nil {
s.logger.Error("获取管理员权限失败", zap.Error(err))
permissions = []string{}
}
adminInfo := responses.AdminInfoResponse{
ID: admin.ID,
Username: admin.Username,
Email: admin.Email,
Phone: admin.Phone,
RealName: admin.RealName,
Role: admin.Role,
IsActive: admin.IsActive,
LastLoginAt: admin.LastLoginAt,
LoginCount: admin.LoginCount,
Permissions: permissions,
CreatedAt: admin.CreatedAt,
}
s.logger.Info("管理员登录成功", zap.String("username", cmd.Username))
return &responses.AdminLoginResponse{
Token: token,
ExpiresAt: expiresAt,
Admin: adminInfo,
}, nil
}
func (s *AdminApplicationServiceImpl) CreateAdmin(ctx context.Context, cmd *commands.CreateAdminCommand) error {
// ... implementation ...
return nil
}
func (s *AdminApplicationServiceImpl) UpdateAdmin(ctx context.Context, cmd *commands.UpdateAdminCommand) error {
// ... implementation ...
return nil
}
func (s *AdminApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangeAdminPasswordCommand) error {
// ... implementation ...
return nil
}
func (s *AdminApplicationServiceImpl) ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) (*responses.AdminListResponse, error) {
// ... implementation ...
return nil, nil
}
func (s *AdminApplicationServiceImpl) GetAdminByID(ctx context.Context, query *queries.GetAdminInfoQuery) (*responses.AdminInfoResponse, error) {
// ... implementation ...
return nil, nil
}
func (s *AdminApplicationServiceImpl) DeleteAdmin(ctx context.Context, cmd *commands.DeleteAdminCommand) error {
// ... implementation ...
return nil
}
func (s *AdminApplicationServiceImpl) GetAdminStats(ctx context.Context) (*responses.AdminStatsResponse, error) {
// ... implementation ...
return nil, nil
}
// Private helper methods from old service
func (s *AdminApplicationServiceImpl) getAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
rolePermissions, err := s.adminRepo.GetPermissionsByRole(ctx, admin.Role)
if err != nil {
return nil, err
}
permissions := make([]string, 0, len(rolePermissions))
for _, perm := range rolePermissions {
permissions = append(permissions, perm.Code)
}
if admin.Permissions != "" {
var customPermissions []string
if err := json.Unmarshal([]byte(admin.Permissions), &customPermissions); err == nil {
permissions = append(permissions, customPermissions...)
}
}
return permissions, nil
}
func (s *AdminApplicationServiceImpl) generateJWTToken(admin *entities.Admin) (string, time.Time, error) {
// This should be handled by a dedicated auth service
token := fmt.Sprintf("admin_token_%s_%d", admin.ID, time.Now().Unix())
expiresAt := time.Now().Add(24 * time.Hour)
return token, expiresAt, nil
}

View File

@@ -0,0 +1,44 @@
package commands
// AdminLoginCommand 管理员登录命令
type AdminLoginCommand struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
// CreateAdminCommand 创建管理员命令
type CreateAdminCommand struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
Email string `json:"email" binding:"required,email"`
Phone string `json:"phone"`
RealName string `json:"real_name" binding:"required"`
Role string `json:"role" binding:"required"`
Permissions []string `json:"permissions"`
OperatorID string `json:"-"`
}
// UpdateAdminCommand 更新管理员命令
type UpdateAdminCommand struct {
AdminID string `json:"-"`
Email string `json:"email" binding:"email"`
Phone string `json:"phone"`
RealName string `json:"real_name"`
Role string `json:"role"`
IsActive *bool `json:"is_active"`
Permissions []string `json:"permissions"`
OperatorID string `json:"-"`
}
// ChangeAdminPasswordCommand 修改密码命令
type ChangeAdminPasswordCommand struct {
AdminID string `json:"-"`
OldPassword string `json:"old_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
// DeleteAdminCommand 删除管理员命令
type DeleteAdminCommand struct {
AdminID string `json:"-"`
OperatorID string `json:"-"`
}

View File

@@ -0,0 +1,16 @@
package queries
// ListAdminsQuery 获取管理员列表查询
type ListAdminsQuery struct {
Page int `form:"page" binding:"min=1"`
PageSize int `form:"page_size" binding:"min=1,max=100"`
Username string `form:"username"`
Email string `form:"email"`
Role string `form:"role"`
IsActive *bool `form:"is_active"`
}
// GetAdminInfoQuery 获取管理员信息查询
type GetAdminInfoQuery struct {
AdminID string `uri:"id" binding:"required"`
}

View File

@@ -0,0 +1,45 @@
package responses
import (
"time"
"tyapi-server/internal/domains/admin/entities"
)
// AdminLoginResponse 管理员登录响应
type AdminLoginResponse struct {
Token string `json:"token"`
ExpiresAt time.Time `json:"expires_at"`
Admin AdminInfoResponse `json:"admin"`
}
// AdminInfoResponse 管理员信息响应
type AdminInfoResponse struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Phone string `json:"phone"`
RealName string `json:"real_name"`
Role entities.AdminRole `json:"role"`
IsActive bool `json:"is_active"`
LastLoginAt *time.Time `json:"last_login_at"`
LoginCount int `json:"login_count"`
Permissions []string `json:"permissions"`
CreatedAt time.Time `json:"created_at"`
}
// AdminListResponse 管理员列表响应
type AdminListResponse struct {
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
Admins []AdminInfoResponse `json:"admins"`
}
// AdminStatsResponse 管理员统计响应
type AdminStatsResponse struct {
TotalAdmins int64 `json:"total_admins"`
ActiveAdmins int64 `json:"active_admins"`
TodayLogins int64 `json:"today_logins"`
TotalOperations int64 `json:"total_operations"`
}

View File

@@ -0,0 +1,48 @@
package certification
import (
"context"
"tyapi-server/internal/application/certification/dto/commands"
"tyapi-server/internal/application/certification/dto/queries"
"tyapi-server/internal/application/certification/dto/responses"
)
// CertificationApplicationService 认证应用服务接口
type CertificationApplicationService interface {
// 认证申请管理
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error)
GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error)
GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error)
// 企业信息管理
CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
// 营业执照上传
UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error)
GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error)
// UploadBusinessLicense 上传营业执照并同步OCR识别
UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error)
// 人脸识别
InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error)
CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error
RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error)
// 合同管理
ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error)
ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error
RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error
CompleteContractSign(ctx context.Context, certificationID, contractURL string) error
RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error)
// 认证完成
CompleteCertification(ctx context.Context, certificationID string) error
// 重试和重启
RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error
RestartCertification(ctx context.Context, certificationID string) error
}

View File

@@ -0,0 +1,608 @@
package certification
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"tyapi-server/internal/application/certification/dto/commands"
"tyapi-server/internal/application/certification/dto/queries"
"tyapi-server/internal/application/certification/dto/responses"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services"
user_entities "tyapi-server/internal/domains/user/entities"
user_repositories "tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/ocr"
"tyapi-server/internal/shared/storage"
)
// CertificationApplicationServiceImpl 认证应用服务实现
type CertificationApplicationServiceImpl struct {
certRepo repositories.CertificationRepository
licenseRepo repositories.LicenseUploadRecordRepository
faceVerifyRepo repositories.FaceVerifyRecordRepository
contractRepo repositories.ContractRecordRepository
certService *services.CertificationService
stateMachine *services.CertificationStateMachine
storageService storage.StorageService
ocrService ocr.OCRService
enterpriseInfoRepo user_repositories.EnterpriseInfoRepository
logger *zap.Logger
}
// NewCertificationApplicationService 创建认证应用服务
func NewCertificationApplicationService(
certRepo repositories.CertificationRepository,
licenseRepo repositories.LicenseUploadRecordRepository,
faceVerifyRepo repositories.FaceVerifyRecordRepository,
contractRepo repositories.ContractRecordRepository,
certService *services.CertificationService,
stateMachine *services.CertificationStateMachine,
storageService storage.StorageService,
ocrService ocr.OCRService,
enterpriseInfoRepo user_repositories.EnterpriseInfoRepository,
logger *zap.Logger,
) CertificationApplicationService {
return &CertificationApplicationServiceImpl{
certRepo: certRepo,
licenseRepo: licenseRepo,
faceVerifyRepo: faceVerifyRepo,
contractRepo: contractRepo,
certService: certService,
stateMachine: stateMachine,
storageService: storageService,
ocrService: ocrService,
enterpriseInfoRepo: enterpriseInfoRepo,
logger: logger,
}
}
// CreateCertification 创建认证申请
func (s *CertificationApplicationServiceImpl) CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error) {
// 使用领域服务创建认证申请
certification, err := s.certService.CreateCertification(ctx, cmd.UserID)
if err != nil {
return nil, err
}
// 构建响应
response := &responses.CertificationResponse{
ID: certification.ID,
UserID: certification.UserID,
Status: certification.Status,
StatusName: string(certification.Status),
Progress: certification.GetProgressPercentage(),
IsUserActionRequired: certification.IsUserActionRequired(),
IsAdminActionRequired: certification.IsAdminActionRequired(),
InfoSubmittedAt: certification.InfoSubmittedAt,
FaceVerifiedAt: certification.FaceVerifiedAt,
ContractAppliedAt: certification.ContractAppliedAt,
ContractApprovedAt: certification.ContractApprovedAt,
ContractSignedAt: certification.ContractSignedAt,
CompletedAt: certification.CompletedAt,
ContractURL: certification.ContractURL,
SigningURL: certification.SigningURL,
RejectReason: certification.RejectReason,
CreatedAt: certification.CreatedAt,
UpdatedAt: certification.UpdatedAt,
}
s.logger.Info("认证申请创建成功",
zap.String("certification_id", certification.ID),
zap.String("user_id", cmd.UserID),
)
return response, nil
}
// CreateEnterpriseInfo 创建企业信息
func (s *CertificationApplicationServiceImpl) CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) {
// 检查用户是否已有企业信息
existingInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cmd.UserID)
if err == nil && existingInfo != nil {
return nil, fmt.Errorf("用户已有企业信息")
}
// 检查统一社会信用代码是否已存在
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, "")
if err != nil {
return nil, fmt.Errorf("检查企业信息失败: %w", err)
}
if exists {
return nil, fmt.Errorf("统一社会信用代码已存在")
}
// 创建企业信息
enterpriseInfo := &user_entities.EnterpriseInfo{
UserID: cmd.UserID,
CompanyName: cmd.CompanyName,
UnifiedSocialCode: cmd.UnifiedSocialCode,
LegalPersonName: cmd.LegalPersonName,
LegalPersonID: cmd.LegalPersonID,
}
createdEnterpriseInfo, err := s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
if err != nil {
s.logger.Error("创建企业信息失败", zap.Error(err))
return nil, fmt.Errorf("创建企业信息失败: %w", err)
}
s.logger.Info("企业信息创建成功",
zap.String("user_id", cmd.UserID),
zap.String("enterprise_id", enterpriseInfo.ID),
)
return &responses.EnterpriseInfoResponse{
ID: createdEnterpriseInfo.ID,
CompanyName: enterpriseInfo.CompanyName,
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
LegalPersonName: enterpriseInfo.LegalPersonName,
LegalPersonID: enterpriseInfo.LegalPersonID,
IsOCRVerified: enterpriseInfo.IsOCRVerified,
IsFaceVerified: enterpriseInfo.IsFaceVerified,
CreatedAt: enterpriseInfo.CreatedAt,
UpdatedAt: enterpriseInfo.UpdatedAt,
}, nil
}
// UploadLicense 上传营业执照
func (s *CertificationApplicationServiceImpl) UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error) {
// 1. 业务规则验证 - 调用领域服务
if err := s.certService.ValidateLicenseUpload(ctx, cmd.UserID, cmd.FileName, cmd.FileSize); err != nil {
return nil, err
}
// 2. 上传文件到存储服务
uploadResult, err := s.storageService.UploadFile(ctx, cmd.FileBytes, cmd.FileName)
if err != nil {
s.logger.Error("上传营业执照失败", zap.Error(err))
return nil, fmt.Errorf("上传营业执照失败: %w", err)
}
// 3. 创建营业执照上传记录 - 调用领域服务
licenseRecord, err := s.certService.CreateLicenseUploadRecord(ctx, cmd.UserID, cmd.FileName, cmd.FileSize, uploadResult)
if err != nil {
s.logger.Error("创建营业执照记录失败", zap.Error(err))
return nil, fmt.Errorf("创建营业执照记录失败: %w", err)
}
// 4. 异步处理OCR识别 - 使用任务队列或后台任务
go s.processOCRAsync(ctx, licenseRecord.ID, cmd.FileBytes)
s.logger.Info("营业执照上传成功",
zap.String("user_id", cmd.UserID),
zap.String("license_id", licenseRecord.ID),
zap.String("file_url", uploadResult.URL),
)
// 5. 构建响应
response := &responses.UploadLicenseResponse{
UploadRecordID: licenseRecord.ID,
FileURL: uploadResult.URL,
OCRProcessed: false,
OCRSuccess: false,
}
// 6. 如果OCR处理很快完成尝试获取结果
// 这里可以添加一个简单的轮询机制或者使用WebSocket推送结果
// 暂时返回基础信息前端可以通过查询接口获取OCR结果
return response, nil
}
// UploadBusinessLicense 上传营业执照并同步OCR识别
func (s *CertificationApplicationServiceImpl) UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error) {
s.logger.Info("开始处理营业执照上传",
zap.String("user_id", userID),
zap.String("file_name", fileName),
)
// 调用领域服务进行上传和OCR识别
uploadRecord, ocrResult, err := s.certService.UploadBusinessLicense(ctx, userID, fileBytes, fileName)
if err != nil {
s.logger.Error("营业执照上传失败", zap.Error(err))
return nil, err
}
// 构建响应
response := &responses.UploadLicenseResponse{
UploadRecordID: uploadRecord.ID,
FileURL: uploadRecord.FileURL,
OCRProcessed: uploadRecord.OCRProcessed,
OCRSuccess: uploadRecord.OCRSuccess,
OCRConfidence: uploadRecord.OCRConfidence,
OCRErrorMessage: uploadRecord.OCRErrorMessage,
}
// 如果OCR成功添加识别结果
if ocrResult != nil && uploadRecord.OCRSuccess {
response.EnterpriseName = ocrResult.CompanyName
response.CreditCode = ocrResult.UnifiedSocialCode
response.LegalPerson = ocrResult.LegalPersonName
}
s.logger.Info("营业执照上传完成",
zap.String("user_id", userID),
zap.String("upload_record_id", uploadRecord.ID),
zap.Bool("ocr_success", uploadRecord.OCRSuccess),
)
return response, nil
}
// SubmitEnterpriseInfo 提交企业信息
func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, cmd.UserID)
if err != nil {
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
}
// 设置认证ID
cmd.CertificationID = certification.ID
// 调用领域服务提交企业信息
if err := s.certService.SubmitEnterpriseInfo(ctx, certification.ID); err != nil {
return nil, err
}
// 创建企业信息
enterpriseInfo := &user_entities.EnterpriseInfo{
UserID: cmd.UserID,
CompanyName: cmd.CompanyName,
UnifiedSocialCode: cmd.UnifiedSocialCode,
LegalPersonName: cmd.LegalPersonName,
LegalPersonID: cmd.LegalPersonID,
}
*enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
if err != nil {
s.logger.Error("创建企业信息失败", zap.Error(err))
return nil, fmt.Errorf("创建企业信息失败: %w", err)
}
s.logger.Info("企业信息提交成功",
zap.String("user_id", cmd.UserID),
zap.String("certification_id", certification.ID),
zap.String("enterprise_id", enterpriseInfo.ID),
)
return &responses.EnterpriseInfoResponse{
ID: enterpriseInfo.ID,
CompanyName: enterpriseInfo.CompanyName,
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
LegalPersonName: enterpriseInfo.LegalPersonName,
LegalPersonID: enterpriseInfo.LegalPersonID,
IsOCRVerified: enterpriseInfo.IsOCRVerified,
IsFaceVerified: enterpriseInfo.IsFaceVerified,
CreatedAt: enterpriseInfo.CreatedAt,
UpdatedAt: enterpriseInfo.UpdatedAt,
}, nil
}
// InitiateFaceVerify 发起人脸识别验证
func (s *CertificationApplicationServiceImpl) InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error) {
// 根据用户ID获取认证申请 - 这里需要从Handler传入用户ID
// 由于cmd中没有UserID字段我们需要修改Handler的调用方式
// 暂时使用certificationID来获取认证申请
certification, err := s.certRepo.GetByID(ctx, cmd.CertificationID)
if err != nil {
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
// 调用领域服务发起人脸识别
faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, cmd.RealName, cmd.IDCardNumber)
if err != nil {
return nil, err
}
// 构建验证URL这里应该根据实际的人脸识别服务生成
verifyURL := fmt.Sprintf("/api/certification/face-verify/%s?return_url=%s", faceVerifyRecord.ID, cmd.ReturnURL)
s.logger.Info("人脸识别验证发起成功",
zap.String("certification_id", certification.ID),
zap.String("face_verify_id", faceVerifyRecord.ID),
)
return &responses.FaceVerifyResponse{
CertifyID: faceVerifyRecord.ID,
VerifyURL: verifyURL,
ExpiresAt: faceVerifyRecord.ExpiresAt,
}, nil
}
// ApplyContract 申请合同
func (s *CertificationApplicationServiceImpl) ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
}
// 调用领域服务申请合同
if err := s.certService.ApplyContract(ctx, certification.ID); err != nil {
return nil, err
}
// 重新获取更新后的认证申请
updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID)
if err != nil {
return nil, err
}
s.logger.Info("合同申请成功",
zap.String("user_id", userID),
zap.String("certification_id", certification.ID),
)
return s.buildCertificationResponse(&updatedCertification), nil
}
// GetCertificationStatus 获取认证状态
func (s *CertificationApplicationServiceImpl) GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, query.UserID)
if err != nil {
// 如果用户没有认证申请,返回一个表示未开始的状态
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
return &responses.CertificationResponse{
ID: "",
UserID: query.UserID,
Status: "not_started",
StatusName: "未开始认证",
Progress: 0,
IsUserActionRequired: true,
IsAdminActionRequired: false,
InfoSubmittedAt: nil,
FaceVerifiedAt: nil,
ContractAppliedAt: nil,
ContractApprovedAt: nil,
ContractSignedAt: nil,
CompletedAt: nil,
ContractURL: "",
SigningURL: "",
RejectReason: "",
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
}, nil
}
return nil, err
}
// 构建响应
response := s.buildCertificationResponse(certification)
return response, nil
}
// GetCertificationDetails 获取认证详情
func (s *CertificationApplicationServiceImpl) GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, query.UserID)
if err != nil {
// 如果用户没有认证申请,返回错误
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
return nil, fmt.Errorf("用户尚未创建认证申请")
}
return nil, err
}
// 获取认证申请详细信息
certificationWithDetails, err := s.certService.GetCertificationWithDetails(ctx, certification.ID)
if err != nil {
return nil, err
}
// 构建响应
response := s.buildCertificationResponse(certificationWithDetails)
// 添加企业信息
if certification.UserID != "" {
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, certification.UserID)
if err == nil && enterpriseInfo != nil {
response.Enterprise = &responses.EnterpriseInfoResponse{
ID: enterpriseInfo.ID,
CompanyName: enterpriseInfo.CompanyName,
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
LegalPersonName: enterpriseInfo.LegalPersonName,
LegalPersonID: enterpriseInfo.LegalPersonID,
IsOCRVerified: enterpriseInfo.IsOCRVerified,
IsFaceVerified: enterpriseInfo.IsFaceVerified,
CreatedAt: enterpriseInfo.CreatedAt,
UpdatedAt: enterpriseInfo.UpdatedAt,
}
}
}
return response, nil
}
// CompleteFaceVerify 完成人脸识别验证
func (s *CertificationApplicationServiceImpl) CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error {
return s.certService.CompleteFaceVerify(ctx, faceVerifyID, isSuccess)
}
// ApproveContract 管理员审核合同
func (s *CertificationApplicationServiceImpl) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error {
return s.certService.ApproveContract(ctx, certificationID, adminID, signingURL, approvalNotes)
}
// RejectContract 管理员拒绝合同
func (s *CertificationApplicationServiceImpl) RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error {
return s.certService.RejectContract(ctx, certificationID, adminID, rejectReason)
}
// CompleteContractSign 完成合同签署
func (s *CertificationApplicationServiceImpl) CompleteContractSign(ctx context.Context, certificationID, contractURL string) error {
return s.certService.CompleteContractSign(ctx, certificationID, contractURL)
}
// CompleteCertification 完成认证
func (s *CertificationApplicationServiceImpl) CompleteCertification(ctx context.Context, certificationID string) error {
return s.certService.CompleteCertification(ctx, certificationID)
}
// RetryStep 重试认证步骤
func (s *CertificationApplicationServiceImpl) RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error {
switch cmd.Step {
case "face_verify":
return s.certService.RetryFaceVerify(ctx, cmd.CertificationID)
case "restart":
return s.certService.RestartCertification(ctx, cmd.CertificationID)
default:
return fmt.Errorf("不支持的重试步骤: %s", cmd.Step)
}
}
// GetCertificationProgress 获取认证进度
func (s *CertificationApplicationServiceImpl) GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, userID)
if err != nil {
// 如果用户没有认证申请,返回未开始状态
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
return map[string]interface{}{
"certification_id": "",
"user_id": userID,
"current_status": "not_started",
"status_name": "未开始认证",
"progress_percentage": 0,
"is_user_action_required": true,
"is_admin_action_required": false,
"next_valid_statuses": []string{"pending"},
"message": "用户尚未开始认证流程",
"created_at": nil,
"updated_at": nil,
}, nil
}
return nil, err
}
// 获取认证进度
return s.certService.GetCertificationProgress(ctx, certification.ID)
}
// RetryFaceVerify 重试人脸识别
func (s *CertificationApplicationServiceImpl) RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
}
// 调用领域服务重试人脸识别
if err := s.certService.RetryFaceVerify(ctx, certification.ID); err != nil {
return nil, err
}
// 重新发起人脸识别
faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, "", "")
if err != nil {
return nil, err
}
// 构建验证URL
verifyURL := fmt.Sprintf("/api/certification/face-verify/%s", faceVerifyRecord.ID)
return &responses.FaceVerifyResponse{
CertifyID: faceVerifyRecord.ID,
VerifyURL: verifyURL,
ExpiresAt: faceVerifyRecord.ExpiresAt,
}, nil
}
// RetryContractSign 重试合同签署
func (s *CertificationApplicationServiceImpl) RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error) {
// 根据用户ID获取认证申请
certification, err := s.certRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
}
// 重新获取更新后的认证申请
updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID)
if err != nil {
return nil, err
}
s.logger.Info("合同签署重试准备完成",
zap.String("user_id", userID),
zap.String("certification_id", certification.ID),
)
return s.buildCertificationResponse(&updatedCertification), nil
}
// RestartCertification 重新开始认证
func (s *CertificationApplicationServiceImpl) RestartCertification(ctx context.Context, certificationID string) error {
return s.certService.RestartCertification(ctx, certificationID)
}
// buildCertificationResponse 构建认证响应
func (s *CertificationApplicationServiceImpl) buildCertificationResponse(certification *entities.Certification) *responses.CertificationResponse {
return &responses.CertificationResponse{
ID: certification.ID,
UserID: certification.UserID,
Status: certification.Status,
StatusName: string(certification.Status),
Progress: certification.GetProgressPercentage(),
IsUserActionRequired: certification.IsUserActionRequired(),
IsAdminActionRequired: certification.IsAdminActionRequired(),
InfoSubmittedAt: certification.InfoSubmittedAt,
FaceVerifiedAt: certification.FaceVerifiedAt,
ContractAppliedAt: certification.ContractAppliedAt,
ContractApprovedAt: certification.ContractApprovedAt,
ContractSignedAt: certification.ContractSignedAt,
CompletedAt: certification.CompletedAt,
ContractURL: certification.ContractURL,
SigningURL: certification.SigningURL,
RejectReason: certification.RejectReason,
CreatedAt: certification.CreatedAt,
UpdatedAt: certification.UpdatedAt,
}
}
// processOCRAsync 异步处理OCR识别
func (s *CertificationApplicationServiceImpl) processOCRAsync(ctx context.Context, licenseID string, fileBytes []byte) {
// 调用领域服务处理OCR识别
if err := s.certService.ProcessOCRAsync(ctx, licenseID, fileBytes); err != nil {
s.logger.Error("OCR处理失败",
zap.String("license_id", licenseID),
zap.Error(err),
)
}
}
// GetLicenseOCRResult 获取营业执照OCR识别结果
func (s *CertificationApplicationServiceImpl) GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error) {
// 获取营业执照上传记录
licenseRecord, err := s.licenseRepo.GetByID(ctx, recordID)
if err != nil {
s.logger.Error("获取营业执照记录失败", zap.Error(err))
return nil, fmt.Errorf("获取营业执照记录失败: %w", err)
}
// 构建响应
response := &responses.UploadLicenseResponse{
UploadRecordID: licenseRecord.ID,
FileURL: licenseRecord.FileURL,
OCRProcessed: licenseRecord.OCRProcessed,
OCRSuccess: licenseRecord.OCRSuccess,
OCRConfidence: licenseRecord.OCRConfidence,
OCRErrorMessage: licenseRecord.OCRErrorMessage,
}
// 如果OCR成功解析OCR结果
if licenseRecord.OCRSuccess && licenseRecord.OCRRawData != "" {
// 这里可以解析OCR原始数据提取企业信息
// 简化处理,直接返回原始数据中的关键信息
// 实际项目中可以使用JSON解析
response.EnterpriseName = "已识别" // 从OCR数据中提取
response.CreditCode = "已识别" // 从OCR数据中提取
response.LegalPerson = "已识别" // 从OCR数据中提取
}
return response, nil
}

View File

@@ -0,0 +1,62 @@
package commands
// CreateCertificationCommand 创建认证申请命令
// 用于用户发起企业认证流程的初始请求
type CreateCertificationCommand struct {
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识从JWT token获取"`
}
// UploadLicenseCommand 上传营业执照命令
// 用于处理营业执照文件上传的业务逻辑
type UploadLicenseCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
FileBytes []byte `json:"-" comment:"营业执照文件的二进制内容从multipart/form-data获取"`
FileName string `json:"-" comment:"营业执照文件的原始文件名从multipart/form-data获取"`
FileSize int64 `json:"-" comment:"营业执照文件的大小字节从multipart/form-data获取"`
}
// SubmitEnterpriseInfoCommand 提交企业信息命令
// 用于用户提交企业四要素信息,完成企业信息验证
type SubmitEnterpriseInfoCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
CertificationID string `json:"-" comment:"认证申请唯一标识从URL路径获取不在JSON中暴露"`
CompanyName string `json:"company_name" binding:"required" comment:"企业名称,如:北京科技有限公司"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required" comment:"统一社会信用代码18位企业唯一标识91110000123456789X"`
LegalPersonName string `json:"legal_person_name" binding:"required" comment:"法定代表人姓名,如:张三"`
LegalPersonID string `json:"legal_person_id" binding:"required" comment:"法定代表人身份证号码18位110101199001011234"`
LicenseUploadRecordID string `json:"license_upload_record_id" binding:"required" comment:"营业执照上传记录唯一标识,关联已上传的营业执照文件"`
}
// InitiateFaceVerifyCommand 初始化人脸识别命令
// 用于发起人脸识别验证流程,验证法定代表人身份
type InitiateFaceVerifyCommand struct {
CertificationID string `json:"-" comment:"认证申请唯一标识从URL路径获取不在JSON中暴露"`
RealName string `json:"real_name" binding:"required" comment:"真实姓名,必须与营业执照上的法定代表人姓名一致"`
IDCardNumber string `json:"id_card_number" binding:"required" comment:"身份证号码18位用于人脸识别身份验证"`
ReturnURL string `json:"return_url" binding:"required" comment:"人脸识别完成后的回调地址,用于跳转回应用"`
}
// ApplyContractCommand 申请合同命令
// 用于用户申请电子合同,进入合同签署流程
type ApplyContractCommand struct {
CertificationID string `json:"-" comment:"认证申请唯一标识从URL路径获取不在JSON中暴露"`
}
// RetryStepCommand 重试认证步骤命令
// 用于用户重试失败的认证步骤,如人脸识别失败后的重试
type RetryStepCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
CertificationID string `json:"-" comment:"认证申请唯一标识从URL路径获取不在JSON中暴露"`
Step string `json:"step" binding:"required" comment:"重试的步骤名称face_verify人脸识别、contract_sign合同签署"`
}
// CreateEnterpriseInfoCommand 创建企业信息命令
// 用于创建企业基本信息,通常在企业认证流程中使用
// @Description 创建企业信息请求参数
type CreateEnterpriseInfoCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
CompanyName string `json:"company_name" binding:"required" example:"示例企业有限公司" comment:"企业名称,如:示例企业有限公司"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required" example:"91110000123456789X" comment:"统一社会信用代码18位企业唯一标识91110000123456789X"`
LegalPersonName string `json:"legal_person_name" binding:"required" example:"张三" comment:"法定代表人姓名,如:张三"`
LegalPersonID string `json:"legal_person_id" binding:"required" example:"110101199001011234" comment:"法定代表人身份证号码18位110101199001011234"`
}

View File

@@ -0,0 +1,13 @@
package queries
// GetCertificationStatusQuery 获取认证状态查询
// 用于查询用户当前认证申请的进度状态
type GetCertificationStatusQuery struct {
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
}
// GetCertificationDetailsQuery 获取认证详情查询
// 用于查询用户认证申请的详细信息,包括所有相关记录
type GetCertificationDetailsQuery struct {
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
}

View File

@@ -0,0 +1,66 @@
package responses
import (
"time"
"tyapi-server/internal/domains/certification/enums"
)
// CertificationResponse 认证响应
type CertificationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StatusName string `json:"status_name"`
Progress int `json:"progress"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsAdminActionRequired bool `json:"is_admin_action_required"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
ContractURL string `json:"contract_url,omitempty"`
SigningURL string `json:"signing_url,omitempty"`
RejectReason string `json:"reject_reason,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// EnterpriseInfoResponse 企业信息响应
type EnterpriseInfoResponse struct {
ID string `json:"id"`
CertificationID string `json:"certification_id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
LegalPersonName string `json:"legal_person_name"`
LegalPersonID string `json:"legal_person_id"`
LicenseUploadRecordID string `json:"license_upload_record_id"`
IsOCRVerified bool `json:"is_ocr_verified"`
IsFaceVerified bool `json:"is_face_verified"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// UploadLicenseResponse 上传营业执照响应
type UploadLicenseResponse struct {
UploadRecordID string `json:"upload_record_id"`
FileURL string `json:"file_url"`
OCRProcessed bool `json:"ocr_processed"`
OCRSuccess bool `json:"ocr_success"`
// OCR识别结果如果成功
EnterpriseName string `json:"enterprise_name,omitempty"`
CreditCode string `json:"credit_code,omitempty"`
LegalPerson string `json:"legal_person,omitempty"`
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
OCRErrorMessage string `json:"ocr_error_message,omitempty"`
}
// FaceVerifyResponse 人脸识别响应
type FaceVerifyResponse struct {
CertifyID string `json:"certify_id"`
VerifyURL string `json:"verify_url"`
ExpiresAt time.Time `json:"expires_at"`
}

View File

@@ -0,0 +1,55 @@
package responses
import "time"
// BusinessLicenseResult 营业执照识别结果
type BusinessLicenseResult struct {
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
RegisteredCapital string `json:"registered_capital"` // 注册资本
BusinessScope string `json:"business_scope"` // 经营范围
Address string `json:"address"` // 企业地址
IssueDate string `json:"issue_date"` // 发证日期
ValidPeriod string `json:"valid_period"` // 有效期
Confidence float64 `json:"confidence"` // 识别置信度
ProcessedAt time.Time `json:"processed_at"` // 处理时间
}
// IDCardResult 身份证识别结果
type IDCardResult struct {
Name string `json:"name"` // 姓名
IDCardNumber string `json:"id_card_number"` // 身份证号
Gender string `json:"gender"` // 性别
Nation string `json:"nation"` // 民族
Birthday string `json:"birthday"` // 出生日期
Address string `json:"address"` // 住址
IssuingAgency string `json:"issuing_agency"` // 签发机关
ValidPeriod string `json:"valid_period"` // 有效期限
Side string `json:"side"` // 身份证面front/back
Confidence float64 `json:"confidence"` // 识别置信度
ProcessedAt time.Time `json:"processed_at"` // 处理时间
}
// GeneralTextResult 通用文字识别结果
type GeneralTextResult struct {
Words []TextLine `json:"words"` // 识别的文字行
Confidence float64 `json:"confidence"` // 整体置信度
ProcessedAt time.Time `json:"processed_at"` // 处理时间
}
// TextLine 文字行
type TextLine struct {
Text string `json:"text"` // 文字内容
Confidence float64 `json:"confidence"` // 置信度
Position Position `json:"position"` // 位置信息
}
// Position 位置信息
type Position struct {
X int `json:"x"` // X坐标
Y int `json:"y"` // Y坐标
Width int `json:"width"` // 宽度
Height int `json:"height"` // 高度
}

View File

@@ -0,0 +1,69 @@
package commands
import (
"time"
"github.com/shopspring/decimal"
)
// CreateWalletCommand 创建钱包命令
type CreateWalletCommand struct {
UserID string `json:"user_id" binding:"required"`
}
// UpdateWalletCommand 更新钱包命令
type UpdateWalletCommand struct {
UserID string `json:"user_id" binding:"required"`
Balance decimal.Decimal `json:"balance"`
IsActive *bool `json:"is_active"`
}
// RechargeWalletCommand 充值钱包命令
type RechargeWalletCommand struct {
UserID string `json:"user_id" binding:"required"`
Amount decimal.Decimal `json:"amount" binding:"required"`
}
// RechargeCommand 充值命令
type RechargeCommand struct {
UserID string `json:"user_id" binding:"required"`
Amount decimal.Decimal `json:"amount" binding:"required"`
}
// WithdrawWalletCommand 提现钱包命令
type WithdrawWalletCommand struct {
UserID string `json:"user_id" binding:"required"`
Amount decimal.Decimal `json:"amount" binding:"required"`
}
// WithdrawCommand 提现命令
type WithdrawCommand struct {
UserID string `json:"user_id" binding:"required"`
Amount decimal.Decimal `json:"amount" binding:"required"`
}
// CreateUserSecretsCommand 创建用户密钥命令
type CreateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required"`
ExpiresAt *time.Time `json:"expires_at"`
}
// RegenerateAccessKeyCommand 重新生成访问密钥命令
type RegenerateAccessKeyCommand struct {
UserID string `json:"user_id" binding:"required"`
ExpiresAt *time.Time `json:"expires_at"`
}
// DeactivateUserSecretsCommand 停用用户密钥命令
type DeactivateUserSecretsCommand struct {
UserID string `json:"user_id" binding:"required"`
}
// WalletTransactionCommand 钱包交易命令
type WalletTransactionCommand struct {
UserID string `json:"user_id" binding:"required"`
FromUserID string `json:"from_user_id" binding:"required"`
ToUserID string `json:"to_user_id" binding:"required"`
Amount decimal.Decimal `json:"amount" binding:"required"`
Notes string `json:"notes"`
}

View File

@@ -0,0 +1,21 @@
package queries
// GetWalletInfoQuery 获取钱包信息查询
type GetWalletInfoQuery struct {
UserID string `form:"user_id" binding:"required"`
}
// GetWalletQuery 获取钱包查询
type GetWalletQuery struct {
UserID string `form:"user_id" binding:"required"`
}
// GetWalletStatsQuery 获取钱包统计查询
type GetWalletStatsQuery struct {
UserID string `form:"user_id" binding:"required"`
}
// GetUserSecretsQuery 获取用户密钥查询
type GetUserSecretsQuery struct {
UserID string `form:"user_id" binding:"required"`
}

View File

@@ -0,0 +1,51 @@
package responses
import (
"time"
"github.com/shopspring/decimal"
)
// WalletResponse 钱包响应
type WalletResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
IsActive bool `json:"is_active"`
Balance decimal.Decimal `json:"balance"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TransactionResponse 交易响应
type TransactionResponse struct {
TransactionID string `json:"transaction_id"`
FromUserID string `json:"from_user_id"`
ToUserID string `json:"to_user_id"`
Amount decimal.Decimal `json:"amount"`
FromBalance decimal.Decimal `json:"from_balance"`
ToBalance decimal.Decimal `json:"to_balance"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
}
// UserSecretsResponse 用户密钥响应
type UserSecretsResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
AccessID string `json:"access_id"`
AccessKey string `json:"access_key"`
IsActive bool `json:"is_active"`
LastUsedAt *time.Time `json:"last_used_at"`
ExpiresAt *time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletStatsResponse 钱包统计响应
type WalletStatsResponse struct {
TotalWallets int64 `json:"total_wallets"`
ActiveWallets int64 `json:"active_wallets"`
TotalBalance decimal.Decimal `json:"total_balance"`
TodayTransactions int64 `json:"today_transactions"`
TodayVolume decimal.Decimal `json:"today_volume"`
}

View File

@@ -0,0 +1,24 @@
package finance
import (
"context"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
)
// FinanceApplicationService 财务应用服务接口
type FinanceApplicationService interface {
CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error)
GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error)
UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error
Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error)
Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error)
CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error)
GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error)
RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error)
DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error
WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error)
GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error)
}

View File

@@ -0,0 +1,89 @@
package finance
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/application/finance/dto/commands"
"tyapi-server/internal/application/finance/dto/queries"
"tyapi-server/internal/application/finance/dto/responses"
"tyapi-server/internal/domains/finance/repositories"
)
// FinanceApplicationServiceImpl 财务应用服务实现
type FinanceApplicationServiceImpl struct {
walletRepo repositories.WalletRepository
userSecretsRepo repositories.UserSecretsRepository
logger *zap.Logger
}
// NewFinanceApplicationService 创建财务应用服务
func NewFinanceApplicationService(
walletRepo repositories.WalletRepository,
userSecretsRepo repositories.UserSecretsRepository,
logger *zap.Logger,
) FinanceApplicationService {
return &FinanceApplicationServiceImpl{
walletRepo: walletRepo,
userSecretsRepo: userSecretsRepo,
logger: logger,
}
}
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error {
// ... implementation from old service
return fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}
func (s *FinanceApplicationServiceImpl) GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error) {
// ... implementation from old service
return nil, fmt.Errorf("not implemented")
}

View File

@@ -0,0 +1,57 @@
package commands
// RegisterUserCommand 用户注册命令
// @Description 用户注册请求参数
type RegisterUserCommand struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Password string `json:"password" binding:"required,min=6,max=128" example:"password123"`
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password" example:"password123"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
}
// LoginWithPasswordCommand 密码登录命令
// @Description 使用密码进行用户登录请求参数
type LoginWithPasswordCommand struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Password string `json:"password" binding:"required" example:"password123"`
}
// LoginWithSMSCommand 短信验证码登录命令
// @Description 使用短信验证码进行用户登录请求参数
type LoginWithSMSCommand struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
}
// ChangePasswordCommand 修改密码命令
// @Description 修改用户密码请求参数
type ChangePasswordCommand struct {
UserID string `json:"-"`
OldPassword string `json:"old_password" binding:"required" example:"oldpassword123"`
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
}
// SendCodeCommand 发送验证码命令
// @Description 发送短信验证码请求参数
type SendCodeCommand struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
}
// UpdateProfileCommand 更新用户信息命令
// @Description 更新用户基本信息请求参数
type UpdateProfileCommand struct {
UserID string `json:"-"`
Phone string `json:"phone" binding:"omitempty,len=11" example:"13800138000"`
// 可以在这里添加更多用户信息字段,如昵称、头像等
}
// VerifyCodeCommand 验证验证码命令
// @Description 验证短信验证码请求参数
type VerifyCodeCommand struct {
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
Code string `json:"code" binding:"required,len=6" example:"123456"`
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
}

View File

@@ -0,0 +1,6 @@
package queries
// GetUserQuery 获取用户信息查询
type GetUserQuery struct {
UserID string `json:"user_id"`
}

View File

@@ -0,0 +1,63 @@
package responses
import (
"time"
)
// RegisterUserResponse 用户注册响应
// @Description 用户注册成功响应
type RegisterUserResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
Phone string `json:"phone" example:"13800138000"`
}
// EnterpriseInfoResponse 企业信息响应
// @Description 企业信息响应
type EnterpriseInfoResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
CompanyName string `json:"company_name" example:"示例企业有限公司"`
UnifiedSocialCode string `json:"unified_social_code" example:"91110000123456789X"`
LegalPersonName string `json:"legal_person_name" example:"张三"`
LegalPersonID string `json:"legal_person_id" example:"110101199001011234"`
IsOCRVerified bool `json:"is_ocr_verified" example:"false"`
IsFaceVerified bool `json:"is_face_verified" example:"false"`
IsCertified bool `json:"is_certified" example:"false"`
CertifiedAt *time.Time `json:"certified_at,omitempty" example:"2024-01-01T00:00:00Z"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
}
// LoginUserResponse 用户登录响应
// @Description 用户登录成功响应
type LoginUserResponse struct {
User *UserProfileResponse `json:"user"`
AccessToken string `json:"access_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
TokenType string `json:"token_type" example:"Bearer"`
ExpiresIn int64 `json:"expires_in" example:"86400"`
LoginMethod string `json:"login_method" example:"password"`
}
// UserProfileResponse 用户信息响应
// @Description 用户基本信息
type UserProfileResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
Phone string `json:"phone" example:"13800138000"`
EnterpriseInfo *EnterpriseInfoResponse `json:"enterprise_info,omitempty"`
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
}
// SendCodeResponse 发送验证码响应
// @Description 发送短信验证码成功响应
type SendCodeResponse struct {
Message string `json:"message" example:"验证码发送成功"`
ExpiresAt time.Time `json:"expires_at" example:"2024-01-01T00:05:00Z"`
}
// UpdateProfileResponse 更新用户信息响应
// @Description 更新用户信息成功响应
type UpdateProfileResponse struct {
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
Phone string `json:"phone" example:"13800138000"`
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
}

View File

@@ -0,0 +1,22 @@
package user
import (
"context"
"tyapi-server/internal/application/user/dto/commands"
"tyapi-server/internal/domains/user/entities"
)
func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error {
// 1. 检查频率限制
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene)); err != nil {
return err
}
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,18 @@
package user
import (
"context"
"tyapi-server/internal/application/user/dto/commands"
"tyapi-server/internal/application/user/dto/responses"
)
// UserApplicationService 用户应用服务接口
type UserApplicationService interface {
Register(ctx context.Context, cmd *commands.RegisterUserCommand) (*responses.RegisterUserResponse, error)
LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error)
LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error)
ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error
GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error)
SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error
}

View File

@@ -0,0 +1,225 @@
package user
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/application/user/dto/commands"
"tyapi-server/internal/application/user/dto/queries"
"tyapi-server/internal/application/user/dto/responses"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/domains/user/events"
"tyapi-server/internal/domains/user/repositories"
user_service "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/middleware"
)
// UserApplicationServiceImpl 用户应用服务实现
type UserApplicationServiceImpl struct {
userRepo repositories.UserRepository
enterpriseInfoRepo repositories.EnterpriseInfoRepository
smsCodeService *user_service.SMSCodeService
eventBus interfaces.EventBus
jwtAuth *middleware.JWTAuthMiddleware
logger *zap.Logger
}
// NewUserApplicationService 创建用户应用服务
func NewUserApplicationService(
userRepo repositories.UserRepository,
enterpriseInfoRepo repositories.EnterpriseInfoRepository,
smsCodeService *user_service.SMSCodeService,
eventBus interfaces.EventBus,
jwtAuth *middleware.JWTAuthMiddleware,
logger *zap.Logger,
) UserApplicationService {
return &UserApplicationServiceImpl{
userRepo: userRepo,
enterpriseInfoRepo: enterpriseInfoRepo,
smsCodeService: smsCodeService,
eventBus: eventBus,
jwtAuth: jwtAuth,
logger: logger,
}
}
// Register 用户注册
func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands.RegisterUserCommand) (*responses.RegisterUserResponse, error) {
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneRegister); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
if _, err := s.userRepo.GetByPhone(ctx, cmd.Phone); err == nil {
return nil, fmt.Errorf("手机号已存在")
}
user, err := entities.NewUser(cmd.Phone, cmd.Password)
if err != nil {
return nil, fmt.Errorf("创建用户失败: %w", err)
}
createdUser, err := s.userRepo.Create(ctx, *user)
if err != nil {
s.logger.Error("创建用户失败", zap.Error(err))
return nil, fmt.Errorf("创建用户失败: %w", err)
}
event := events.NewUserRegisteredEvent(user, "")
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
}
s.logger.Info("用户注册成功", zap.String("user_id", user.ID), zap.String("phone", user.Phone))
return &responses.RegisterUserResponse{
ID: createdUser.ID,
Phone: user.Phone,
}, nil
}
// LoginWithPassword 密码登录
func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error) {
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
if err != nil {
return nil, fmt.Errorf("用户名或密码错误")
}
if !user.CanLogin() {
return nil, fmt.Errorf("用户状态异常,无法登录")
}
if !user.CheckPassword(cmd.Password) {
return nil, fmt.Errorf("用户名或密码错误")
}
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone)
if err != nil {
s.logger.Error("生成令牌失败", zap.Error(err))
return nil, fmt.Errorf("生成访问令牌失败")
}
userProfile, err := s.GetUserProfile(ctx, user.ID)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %w", err)
}
return &responses.LoginUserResponse{
User: userProfile,
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: 86400, // 24h
LoginMethod: "password",
}, nil
}
// LoginWithSMS 短信验证码登录
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
return nil, fmt.Errorf("验证码错误或已过期")
}
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
if err != nil {
return nil, fmt.Errorf("用户不存在")
}
if !user.CanLogin() {
return nil, fmt.Errorf("用户状态异常,无法登录")
}
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone)
if err != nil {
s.logger.Error("生成令牌失败", zap.Error(err))
return nil, fmt.Errorf("生成访问令牌失败")
}
userProfile, err := s.GetUserProfile(ctx, user.ID)
if err != nil {
return nil, fmt.Errorf("获取用户信息失败: %w", err)
}
return &responses.LoginUserResponse{
User: userProfile,
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: 86400, // 24h
LoginMethod: "sms",
}, nil
}
// ChangePassword 修改密码
func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error {
user, err := s.userRepo.GetByID(ctx, cmd.UserID)
if err != nil {
return fmt.Errorf("用户不存在: %w", err)
}
if err := s.smsCodeService.VerifyCode(ctx, user.Phone, cmd.Code, entities.SMSSceneChangePassword); err != nil {
return fmt.Errorf("验证码错误或已过期")
}
if err := user.ChangePassword(cmd.OldPassword, cmd.NewPassword, cmd.ConfirmNewPassword); err != nil {
return err
}
if err := s.userRepo.Update(ctx, user); err != nil {
return fmt.Errorf("密码更新失败: %w", err)
}
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, "")
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
}
s.logger.Info("密码修改成功", zap.String("user_id", cmd.UserID))
return nil
}
// GetUserProfile 获取用户信息
func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error) {
if userID == "" {
return nil, fmt.Errorf("用户ID不能为空")
}
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("用户不存在: %w", err)
}
response := &responses.UserProfileResponse{
ID: user.ID,
Phone: user.Phone,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
// 获取企业信息(如果存在)
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
if err != nil {
s.logger.Debug("用户暂无企业信息", zap.String("user_id", userID))
} else {
response.EnterpriseInfo = &responses.EnterpriseInfoResponse{
ID: enterpriseInfo.ID,
CompanyName: enterpriseInfo.CompanyName,
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
LegalPersonName: enterpriseInfo.LegalPersonName,
LegalPersonID: enterpriseInfo.LegalPersonID,
IsOCRVerified: enterpriseInfo.IsOCRVerified,
IsFaceVerified: enterpriseInfo.IsFaceVerified,
IsCertified: enterpriseInfo.IsCertified,
CertifiedAt: enterpriseInfo.CertifiedAt,
CreatedAt: enterpriseInfo.CreatedAt,
UpdatedAt: enterpriseInfo.UpdatedAt,
}
}
return response, nil
}
func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries.GetUserQuery) (*responses.UserProfileResponse, error) {
// ... implementation
return nil, fmt.Errorf("not implemented")
}