This commit is contained in:
2026-04-21 22:36:48 +08:00
commit 488c695fdf
748 changed files with 266838 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
package commands
// CreateWalletCommand 创建钱包命令
type CreateWalletCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
}
// TransferRechargeCommand 对公转账充值命令
type TransferRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
TransferOrderID string `json:"transfer_order_id" binding:"required" comment:"转账订单号"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// GiftRechargeCommand 赠送充值命令
type GiftRechargeCommand struct {
UserID string `json:"user_id" binding:"required,uuid"`
Amount string `json:"amount" binding:"required"`
Notes string `json:"notes" binding:"omitempty,max=500" comment:"备注信息"`
}
// CreateAlipayRechargeCommand 创建支付宝充值订单命令
type CreateAlipayRechargeCommand struct {
UserID string `json:"-"` // 用户ID从token获取
Amount string `json:"amount" binding:"required"` // 充值金额
Subject string `json:"-"` // 订单标题
Platform string `json:"platform" binding:"required,oneof=app h5 pc"` // 支付平台app/h5/pc
}
// CreateWechatRechargeCommand 创建微信充值订单命令
type CreateWechatRechargeCommand struct {
UserID string `json:"-"` // 用户ID从token获取
Amount string `json:"amount" binding:"required"` // 充值金额
Subject string `json:"-"` // 订单标题
Platform string `json:"platform" binding:"required,oneof=wx_native native wx_h5 h5"` // 仅支持微信Native扫码兼容传入native/wx_h5/h5
OpenID string `json:"openid" binding:"omitempty"` // 前端可直接传入的 openid用于小程序/H5
}

View File

@@ -0,0 +1,121 @@
package dto
import (
"time"
"hyapi-server/internal/domains/finance/entities"
"hyapi-server/internal/domains/finance/value_objects"
"github.com/shopspring/decimal"
)
// InvoiceApplicationResponse 发票申请响应
type InvoiceApplicationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
Amount decimal.Decimal `json:"amount"`
Status entities.ApplicationStatus `json:"status"`
InvoiceInfo *value_objects.InvoiceInfo `json:"invoice_info"`
CreatedAt time.Time `json:"created_at"`
}
// InvoiceInfoResponse 发票信息响应
type InvoiceInfoResponse struct {
CompanyName string `json:"company_name"` // 从企业认证信息获取,只读
TaxpayerID string `json:"taxpayer_id"` // 从企业认证信息获取,只读
BankName string `json:"bank_name"` // 用户可编辑
BankAccount string `json:"bank_account"` // 用户可编辑
CompanyAddress string `json:"company_address"` // 用户可编辑
CompanyPhone string `json:"company_phone"` // 用户可编辑
ReceivingEmail string `json:"receiving_email"` // 用户可编辑
IsComplete bool `json:"is_complete"`
MissingFields []string `json:"missing_fields,omitempty"`
// 字段权限标识
CompanyNameReadOnly bool `json:"company_name_read_only"` // 公司名称是否只读
TaxpayerIDReadOnly bool `json:"taxpayer_id_read_only"` // 纳税人识别号是否只读
}
// InvoiceRecordResponse 发票记录响应
type InvoiceRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
Amount decimal.Decimal `json:"amount"`
Status entities.ApplicationStatus `json:"status"`
// 开票信息(快照数据)
CompanyName string `json:"company_name"` // 公司名称
TaxpayerID string `json:"taxpayer_id"` // 纳税人识别号
BankName string `json:"bank_name"` // 开户银行
BankAccount string `json:"bank_account"` // 银行账号
CompanyAddress string `json:"company_address"` // 企业地址
CompanyPhone string `json:"company_phone"` // 企业电话
ReceivingEmail string `json:"receiving_email"` // 接收邮箱
// 文件信息
FileName *string `json:"file_name,omitempty"`
FileSize *int64 `json:"file_size,omitempty"`
FileURL *string `json:"file_url,omitempty"`
// 时间信息
ProcessedAt *time.Time `json:"processed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
// 拒绝原因
RejectReason *string `json:"reject_reason,omitempty"`
}
// InvoiceRecordsResponse 发票记录列表响应
type InvoiceRecordsResponse struct {
Records []*InvoiceRecordResponse `json:"records"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// FileDownloadResponse 文件下载响应
type FileDownloadResponse struct {
FileID string `json:"file_id"`
FileName string `json:"file_name"`
FileSize int64 `json:"file_size"`
FileURL string `json:"file_url"`
FileContent []byte `json:"file_content"`
}
// AvailableAmountResponse 可开票金额响应
type AvailableAmountResponse struct {
AvailableAmount decimal.Decimal `json:"available_amount"` // 可开票金额
TotalRecharged decimal.Decimal `json:"total_recharged"` // 总充值金额
TotalGifted decimal.Decimal `json:"total_gifted"` // 总赠送金额
TotalInvoiced decimal.Decimal `json:"total_invoiced"` // 已开票金额
PendingApplications decimal.Decimal `json:"pending_applications"` // 待处理申请金额
}
// PendingApplicationResponse 待处理申请响应
type PendingApplicationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
InvoiceType value_objects.InvoiceType `json:"invoice_type"`
Amount decimal.Decimal `json:"amount"`
Status entities.ApplicationStatus `json:"status"`
CompanyName string `json:"company_name"`
TaxpayerID string `json:"taxpayer_id"`
BankName string `json:"bank_name"`
BankAccount string `json:"bank_account"`
CompanyAddress string `json:"company_address"`
CompanyPhone string `json:"company_phone"`
ReceivingEmail string `json:"receiving_email"`
FileName *string `json:"file_name,omitempty"`
FileSize *int64 `json:"file_size,omitempty"`
FileURL *string `json:"file_url,omitempty"`
ProcessedAt *time.Time `json:"processed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
RejectReason *string `json:"reject_reason,omitempty"`
}
// PendingApplicationsResponse 待处理申请列表响应
type PendingApplicationsResponse struct {
Applications []*PendingApplicationResponse `json:"applications"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}

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,25 @@
package responses
import (
"time"
"github.com/shopspring/decimal"
)
// AlipayOrderStatusResponse 支付宝订单状态响应
type AlipayOrderStatusResponse struct {
OutTradeNo string `json:"out_trade_no"` // 商户订单号
TradeNo *string `json:"trade_no"` // 支付宝交易号
Status string `json:"status"` // 订单状态
Amount decimal.Decimal `json:"amount"` // 订单金额
Subject string `json:"subject"` // 订单标题
Platform string `json:"platform"` // 支付平台
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
NotifyTime *time.Time `json:"notify_time"` // 异步通知时间
ReturnTime *time.Time `json:"return_time"` // 同步返回时间
ErrorCode *string `json:"error_code"` // 错误码
ErrorMessage *string `json:"error_message"` // 错误信息
IsProcessing bool `json:"is_processing"` // 是否处理中
CanRetry bool `json:"can_retry"` // 是否可以重试
}

View File

@@ -0,0 +1,171 @@
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"`
BalanceStatus string `json:"balance_status"` // normal, low, arrears
IsArrears bool `json:"is_arrears"` // 是否欠费
IsLowBalance bool `json:"is_low_balance"` // 是否余额较低
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TransactionResponse 交易响应
type TransactionResponse struct {
TransactionID string `json:"transaction_id"`
Amount decimal.Decimal `json:"amount"`
}
// 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"`
}
// RechargeRecordResponse 充值记录响应
type RechargeRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Amount decimal.Decimal `json:"amount"`
RechargeType string `json:"recharge_type"`
Status string `json:"status"`
AlipayOrderID string `json:"alipay_order_id,omitempty"`
WechatOrderID string `json:"wechat_order_id,omitempty"`
TransferOrderID string `json:"transfer_order_id,omitempty"`
Platform string `json:"platform,omitempty"` // 支付平台pc/wx_native等
Notes string `json:"notes,omitempty"`
OperatorID string `json:"operator_id,omitempty"`
CompanyName string `json:"company_name,omitempty"`
User *UserSimpleResponse `json:"user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionResponse 钱包交易记录响应
type WalletTransactionResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
ApiCallID string `json:"api_call_id"`
TransactionID string `json:"transaction_id"`
ProductID string `json:"product_id"`
ProductName string `json:"product_name"`
Amount decimal.Decimal `json:"amount"`
CompanyName string `json:"company_name,omitempty"`
User *UserSimpleResponse `json:"user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// WalletTransactionListResponse 钱包交易记录列表响应
type WalletTransactionListResponse struct {
Items []WalletTransactionResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// RechargeRecordListResponse 充值记录列表响应
type RechargeRecordListResponse struct {
Items []RechargeRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
// AlipayRechargeOrderResponse 支付宝充值订单响应
type AlipayRechargeOrderResponse struct {
PayURL string `json:"pay_url"` // 支付链接
OutTradeNo string `json:"out_trade_no"` // 商户订单号
Amount decimal.Decimal `json:"amount"` // 充值金额
Platform string `json:"platform"` // 支付平台
Subject string `json:"subject"` // 订单标题
}
// RechargeConfigResponse 充值配置响应
type RechargeConfigResponse struct {
MinAmount string `json:"min_amount"` // 最低充值金额
MaxAmount string `json:"max_amount"` // 最高充值金额
RechargeBonusEnabled bool `json:"recharge_bonus_enabled"` // 是否启用充值赠送
ApiStoreRechargeTip string `json:"api_store_recharge_tip"` // API 商店充值提示(大额/批量联系商务)
AlipayRechargeBonus []AlipayRechargeBonusRuleResponse `json:"alipay_recharge_bonus"`
}
// AlipayRechargeBonusRuleResponse 支付宝充值赠送规则响应
type AlipayRechargeBonusRuleResponse struct {
RechargeAmount float64 `json:"recharge_amount"`
BonusAmount float64 `json:"bonus_amount"`
}
// UserSimpleResponse 用户简单信息响应
type UserSimpleResponse struct {
ID string `json:"id"`
CompanyName string `json:"company_name"`
Phone string `json:"phone"`
}
// PurchaseRecordResponse 购买记录响应
type PurchaseRecordResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
OrderNo string `json:"order_no"`
TradeNo *string `json:"trade_no,omitempty"`
ProductID string `json:"product_id"`
ProductCode string `json:"product_code"`
ProductName string `json:"product_name"`
Category string `json:"category,omitempty"`
Subject string `json:"subject"`
Amount decimal.Decimal `json:"amount"`
PayAmount *decimal.Decimal `json:"pay_amount,omitempty"`
Status string `json:"status"`
Platform string `json:"platform"`
PayChannel string `json:"pay_channel"`
PaymentType string `json:"payment_type"`
BuyerID string `json:"buyer_id,omitempty"`
SellerID string `json:"seller_id,omitempty"`
ReceiptAmount decimal.Decimal `json:"receipt_amount,omitempty"`
NotifyTime *time.Time `json:"notify_time,omitempty"`
ReturnTime *time.Time `json:"return_time,omitempty"`
PayTime *time.Time `json:"pay_time,omitempty"`
FilePath *string `json:"file_path,omitempty"`
FileSize *int64 `json:"file_size,omitempty"`
Remark string `json:"remark,omitempty"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMessage string `json:"error_message,omitempty"`
CompanyName string `json:"company_name,omitempty"`
User *UserSimpleResponse `json:"user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// PurchaseRecordListResponse 购买记录列表响应
type PurchaseRecordListResponse struct {
Items []PurchaseRecordResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}

View File

@@ -0,0 +1,25 @@
package responses
import (
"time"
"github.com/shopspring/decimal"
)
// WechatOrderStatusResponse 微信订单状态响应
type WechatOrderStatusResponse struct {
OutTradeNo string `json:"out_trade_no"` // 商户订单号
TransactionID *string `json:"transaction_id"` // 微信支付交易号
Status string `json:"status"` // 订单状态
Amount decimal.Decimal `json:"amount"` // 订单金额
Subject string `json:"subject"` // 订单标题
Platform string `json:"platform"` // 支付平台
CreatedAt time.Time `json:"created_at"` // 创建时间
UpdatedAt time.Time `json:"updated_at"` // 更新时间
NotifyTime *time.Time `json:"notify_time"` // 异步通知时间
ReturnTime *time.Time `json:"return_time"` // 同步返回时间
ErrorCode *string `json:"error_code"` // 错误码
ErrorMessage *string `json:"error_message"` // 错误信息
IsProcessing bool `json:"is_processing"` // 是否处理中
CanRetry bool `json:"can_retry"` // 是否可以重试
}

View File

@@ -0,0 +1,12 @@
package responses
import "github.com/shopspring/decimal"
// WechatRechargeOrderResponse 微信充值下单响应
type WechatRechargeOrderResponse struct {
OutTradeNo string `json:"out_trade_no"` // 商户订单号
Amount decimal.Decimal `json:"amount"` // 充值金额
Platform string `json:"platform"` // 支付平台
Subject string `json:"subject"` // 订单标题
PrepayData interface{} `json:"prepay_data"` // 预支付数据APP预支付ID或JSAPI参数
}

View File

@@ -0,0 +1,52 @@
package finance
import (
"context"
"net/http"
"hyapi-server/internal/application/finance/dto/commands"
"hyapi-server/internal/application/finance/dto/queries"
"hyapi-server/internal/application/finance/dto/responses"
"hyapi-server/internal/shared/interfaces"
)
// FinanceApplicationService 财务应用服务接口
type FinanceApplicationService interface {
// 钱包管理
CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error)
GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error)
// 充值管理
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error)
CreateWechatRechargeOrder(ctx context.Context, cmd *commands.CreateWechatRechargeCommand) (*responses.WechatRechargeOrderResponse, error)
TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error)
GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error)
// 交易记录
GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
GetAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error)
// 导出功能
ExportAdminWalletTransactions(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
ExportAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, format string) ([]byte, error)
// 支付宝回调处理
HandleAlipayCallback(ctx context.Context, r *http.Request) error
HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error)
GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error)
// 微信支付回调处理
HandleWechatPayCallback(ctx context.Context, r *http.Request) error
HandleWechatRefundCallback(ctx context.Context, r *http.Request) error
GetWechatOrderStatus(ctx context.Context, outTradeNo string) (*responses.WechatOrderStatusResponse, error)
// 充值记录
GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error)
// 购买记录
GetUserPurchaseRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
GetAdminPurchaseRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.PurchaseRecordListResponse, error)
// 获取充值配置
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,786 @@
package finance
import (
"context"
"fmt"
"mime/multipart"
"time"
"hyapi-server/internal/application/finance/dto"
"hyapi-server/internal/config"
"hyapi-server/internal/domains/finance/entities"
finance_repo "hyapi-server/internal/domains/finance/repositories"
"hyapi-server/internal/domains/finance/services"
"hyapi-server/internal/domains/finance/value_objects"
user_repo "hyapi-server/internal/domains/user/repositories"
user_service "hyapi-server/internal/domains/user/services"
"hyapi-server/internal/infrastructure/external/notification"
"hyapi-server/internal/infrastructure/external/storage"
"github.com/shopspring/decimal"
"go.uber.org/zap"
)
// ==================== 用户端发票应用服务 ====================
// InvoiceApplicationService 发票应用服务接口
// 职责:跨域协调、数据聚合、事务管理、外部服务调用
type InvoiceApplicationService interface {
// ApplyInvoice 申请开票
ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error)
// GetUserInvoiceInfo 获取用户发票信息
GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error)
// UpdateUserInvoiceInfo 更新用户发票信息
UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error
// GetUserInvoiceRecords 获取用户开票记录
GetUserInvoiceRecords(ctx context.Context, userID string, req GetInvoiceRecordsRequest) (*dto.InvoiceRecordsResponse, error)
// DownloadInvoiceFile 下载发票文件
DownloadInvoiceFile(ctx context.Context, userID string, applicationID string) (*dto.FileDownloadResponse, error)
// GetAvailableAmount 获取可开票金额
GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error)
}
// InvoiceApplicationServiceImpl 发票应用服务实现
type InvoiceApplicationServiceImpl struct {
// 仓储层依赖
invoiceRepo finance_repo.InvoiceApplicationRepository
userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository
userRepo user_repo.UserRepository
rechargeRecordRepo finance_repo.RechargeRecordRepository
walletRepo finance_repo.WalletRepository
// 领域服务依赖
invoiceDomainService services.InvoiceDomainService
invoiceAggregateService services.InvoiceAggregateService
userInvoiceInfoService services.UserInvoiceInfoService
userAggregateService user_service.UserAggregateService
// 外部服务依赖
storageService *storage.QiNiuStorageService
logger *zap.Logger
wechatWorkServer *notification.WeChatWorkService
}
// NewInvoiceApplicationService 创建发票应用服务
func NewInvoiceApplicationService(
invoiceRepo finance_repo.InvoiceApplicationRepository,
userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository,
userRepo user_repo.UserRepository,
userAggregateService user_service.UserAggregateService,
rechargeRecordRepo finance_repo.RechargeRecordRepository,
walletRepo finance_repo.WalletRepository,
invoiceDomainService services.InvoiceDomainService,
invoiceAggregateService services.InvoiceAggregateService,
userInvoiceInfoService services.UserInvoiceInfoService,
storageService *storage.QiNiuStorageService,
logger *zap.Logger,
cfg *config.Config,
) InvoiceApplicationService {
var wechatSvc *notification.WeChatWorkService
if cfg != nil && cfg.WechatWork.WebhookURL != "" {
wechatSvc = notification.NewWeChatWorkService(cfg.WechatWork.WebhookURL, cfg.WechatWork.Secret, logger)
}
return &InvoiceApplicationServiceImpl{
invoiceRepo: invoiceRepo,
userInvoiceInfoRepo: userInvoiceInfoRepo,
userRepo: userRepo,
userAggregateService: userAggregateService,
rechargeRecordRepo: rechargeRecordRepo,
walletRepo: walletRepo,
invoiceDomainService: invoiceDomainService,
invoiceAggregateService: invoiceAggregateService,
userInvoiceInfoService: userInvoiceInfoService,
storageService: storageService,
logger: logger,
wechatWorkServer: wechatSvc,
}
}
// ApplyInvoice 申请开票
func (s *InvoiceApplicationServiceImpl) ApplyInvoice(ctx context.Context, userID string, req ApplyInvoiceRequest) (*dto.InvoiceApplicationResponse, error) {
// 1. 验证用户是否存在
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, err
}
if user.ID == "" {
return nil, fmt.Errorf("用户不存在")
}
// 2. 验证发票类型
invoiceType := value_objects.InvoiceType(req.InvoiceType)
if !invoiceType.IsValid() {
return nil, fmt.Errorf("无效的发票类型")
}
// 3. 获取用户企业认证信息
userWithEnterprise, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
if err != nil {
return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err)
}
// 4. 检查用户是否有企业认证信息
if userWithEnterprise.EnterpriseInfo == nil {
return nil, fmt.Errorf("用户未完成企业认证,无法申请开票")
}
// 5. 获取用户开票信息
userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo(
ctx,
userID,
userWithEnterprise.EnterpriseInfo.CompanyName,
userWithEnterprise.EnterpriseInfo.UnifiedSocialCode,
)
if err != nil {
return nil, err
}
// 6. 验证开票信息完整性
invoiceInfo := value_objects.NewInvoiceInfo(
userInvoiceInfo.CompanyName,
userInvoiceInfo.TaxpayerID,
userInvoiceInfo.BankName,
userInvoiceInfo.BankAccount,
userInvoiceInfo.CompanyAddress,
userInvoiceInfo.CompanyPhone,
userInvoiceInfo.ReceivingEmail,
)
if err := s.userInvoiceInfoService.ValidateInvoiceInfo(ctx, invoiceInfo, invoiceType); err != nil {
return nil, err
}
// 7. 计算可开票金额
availableAmount, err := s.calculateAvailableAmount(ctx, userID)
if err != nil {
return nil, fmt.Errorf("计算可开票金额失败: %w", err)
}
// 8. 验证开票金额
amount, err := decimal.NewFromString(req.Amount)
if err != nil {
return nil, fmt.Errorf("无效的金额格式: %w", err)
}
if err := s.invoiceDomainService.ValidateInvoiceAmount(ctx, amount, availableAmount); err != nil {
return nil, err
}
// 9. 调用聚合服务申请开票
aggregateReq := services.ApplyInvoiceRequest{
InvoiceType: invoiceType,
Amount: req.Amount,
InvoiceInfo: invoiceInfo,
}
application, err := s.invoiceAggregateService.ApplyInvoice(ctx, userID, aggregateReq)
if err != nil {
return nil, err
}
// 10. 构建响应DTO
resp := &dto.InvoiceApplicationResponse{
ID: application.ID,
UserID: application.UserID,
InvoiceType: application.InvoiceType,
Amount: application.Amount,
Status: application.Status,
InvoiceInfo: invoiceInfo,
CreatedAt: application.CreatedAt,
}
// 11. 企业微信通知(忽略发送错误),只使用企业名称和联系电话
if s.wechatWorkServer != nil {
companyName := userWithEnterprise.EnterpriseInfo.CompanyName
phone := user.Phone
if userWithEnterprise.EnterpriseInfo.LegalPersonPhone != "" {
phone = userWithEnterprise.EnterpriseInfo.LegalPersonPhone
}
content := fmt.Sprintf(
"### 【海宇数据】用户申请开发票\n"+
"> 企业名称:%s\n"+
"> 联系手机:%s\n"+
"> 申请开票金额:%s 元\n"+
"> 发票类型:%s\n"+
"> 申请时间:%s\n",
companyName,
phone,
application.Amount.String(),
string(application.InvoiceType),
time.Now().Format("2006-01-02 15:04:05"),
)
_ = s.wechatWorkServer.SendMarkdownMessage(ctx, content)
}
return resp, nil
}
// GetUserInvoiceInfo 获取用户发票信息
func (s *InvoiceApplicationServiceImpl) GetUserInvoiceInfo(ctx context.Context, userID string) (*dto.InvoiceInfoResponse, error) {
// 1. 获取用户企业认证信息
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
if err != nil {
return nil, fmt.Errorf("获取用户企业认证信息失败: %w", err)
}
// 2. 获取企业认证信息
var companyName, taxpayerID string
var companyNameReadOnly, taxpayerIDReadOnly bool
if user.EnterpriseInfo != nil {
companyName = user.EnterpriseInfo.CompanyName
taxpayerID = user.EnterpriseInfo.UnifiedSocialCode
companyNameReadOnly = true
taxpayerIDReadOnly = true
}
// 3. 获取用户开票信息(包含企业认证信息)
userInvoiceInfo, err := s.userInvoiceInfoService.GetUserInvoiceInfoWithEnterpriseInfo(ctx, userID, companyName, taxpayerID)
if err != nil {
return nil, err
}
// 4. 构建响应DTO
return &dto.InvoiceInfoResponse{
CompanyName: userInvoiceInfo.CompanyName,
TaxpayerID: userInvoiceInfo.TaxpayerID,
BankName: userInvoiceInfo.BankName,
BankAccount: userInvoiceInfo.BankAccount,
CompanyAddress: userInvoiceInfo.CompanyAddress,
CompanyPhone: userInvoiceInfo.CompanyPhone,
ReceivingEmail: userInvoiceInfo.ReceivingEmail,
IsComplete: userInvoiceInfo.IsComplete(),
MissingFields: userInvoiceInfo.GetMissingFields(),
// 字段权限标识
CompanyNameReadOnly: companyNameReadOnly, // 公司名称只读(从企业认证信息获取)
TaxpayerIDReadOnly: taxpayerIDReadOnly, // 纳税人识别号只读(从企业认证信息获取)
}, nil
}
// UpdateUserInvoiceInfo 更新用户发票信息
func (s *InvoiceApplicationServiceImpl) UpdateUserInvoiceInfo(ctx context.Context, userID string, req UpdateInvoiceInfoRequest) error {
// 1. 获取用户企业认证信息
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
if err != nil {
return fmt.Errorf("获取用户企业认证信息失败: %w", err)
}
// 2. 检查用户是否有企业认证信息
if user.EnterpriseInfo == nil {
return fmt.Errorf("用户未完成企业认证,无法创建开票信息")
}
// 3. 创建开票信息对象,公司名称和纳税人识别号从企业认证信息中获取
invoiceInfo := value_objects.NewInvoiceInfo(
"", // 公司名称将由服务层从企业认证信息中获取
"", // 纳税人识别号将由服务层从企业认证信息中获取
req.BankName,
req.BankAccount,
req.CompanyAddress,
req.CompanyPhone,
req.ReceivingEmail,
)
// 4. 使用包含企业认证信息的方法
_, err = s.userInvoiceInfoService.CreateOrUpdateUserInvoiceInfoWithEnterpriseInfo(
ctx,
userID,
invoiceInfo,
user.EnterpriseInfo.CompanyName,
user.EnterpriseInfo.UnifiedSocialCode,
)
return err
}
// GetUserInvoiceRecords 获取用户开票记录
func (s *InvoiceApplicationServiceImpl) GetUserInvoiceRecords(ctx context.Context, userID string, req GetInvoiceRecordsRequest) (*dto.InvoiceRecordsResponse, error) {
// 1. 验证用户是否存在
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, err
}
if user.ID == "" {
return nil, fmt.Errorf("用户不存在")
}
// 2. 获取发票申请记录
var status entities.ApplicationStatus
if req.Status != "" {
status = entities.ApplicationStatus(req.Status)
}
// 3. 解析时间范围
var startTime, endTime *time.Time
if req.StartTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil {
startTime = &t
}
}
if req.EndTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil {
endTime = &t
}
}
// 4. 获取发票申请记录(需要更新仓储层方法以支持时间筛选)
applications, total, err := s.invoiceRepo.FindByUserIDAndStatusWithTimeRange(ctx, userID, status, startTime, endTime, req.Page, req.PageSize)
if err != nil {
return nil, err
}
// 5. 构建响应DTO
records := make([]*dto.InvoiceRecordResponse, len(applications))
for i, app := range applications {
// 使用快照信息(申请时的开票信息)
records[i] = &dto.InvoiceRecordResponse{
ID: app.ID,
UserID: app.UserID,
InvoiceType: app.InvoiceType,
Amount: app.Amount,
Status: app.Status,
CompanyName: app.CompanyName, // 使用快照的公司名称
TaxpayerID: app.TaxpayerID, // 使用快照的纳税人识别号
BankName: app.BankName, // 使用快照的银行名称
BankAccount: app.BankAccount, // 使用快照的银行账号
CompanyAddress: app.CompanyAddress, // 使用快照的企业地址
CompanyPhone: app.CompanyPhone, // 使用快照的企业电话
ReceivingEmail: app.ReceivingEmail, // 使用快照的接收邮箱
FileName: app.FileName,
FileSize: app.FileSize,
FileURL: app.FileURL,
ProcessedAt: app.ProcessedAt,
CreatedAt: app.CreatedAt,
RejectReason: app.RejectReason,
}
}
return &dto.InvoiceRecordsResponse{
Records: records,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: (int(total) + req.PageSize - 1) / req.PageSize,
}, nil
}
// DownloadInvoiceFile 下载发票文件
func (s *InvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context, userID string, applicationID string) (*dto.FileDownloadResponse, error) {
// 1. 查找申请记录
application, err := s.invoiceRepo.FindByID(ctx, applicationID)
if err != nil {
return nil, err
}
if application == nil {
return nil, fmt.Errorf("申请记录不存在")
}
// 2. 验证权限(只能下载自己的发票)
if application.UserID != userID {
return nil, fmt.Errorf("无权访问此发票")
}
// 3. 验证状态(只能下载已完成的发票)
if application.Status != entities.ApplicationStatusCompleted {
return nil, fmt.Errorf("发票尚未通过审核")
}
// 4. 验证文件信息
if application.FileURL == nil || *application.FileURL == "" {
return nil, fmt.Errorf("发票文件不存在")
}
// 5. 从七牛云下载文件内容
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
if err != nil {
return nil, fmt.Errorf("下载文件失败: %w", err)
}
// 6. 构建响应DTO
return &dto.FileDownloadResponse{
FileID: *application.FileID,
FileName: *application.FileName,
FileSize: *application.FileSize,
FileURL: *application.FileURL,
FileContent: fileContent,
}, nil
}
// GetAvailableAmount 获取可开票金额
func (s *InvoiceApplicationServiceImpl) GetAvailableAmount(ctx context.Context, userID string) (*dto.AvailableAmountResponse, error) {
// 1. 验证用户是否存在
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, err
}
if user.ID == "" {
return nil, fmt.Errorf("用户不存在")
}
// 2. 计算可开票金额
availableAmount, err := s.calculateAvailableAmount(ctx, userID)
if err != nil {
return nil, err
}
// 3. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
if err != nil {
return nil, err
}
// 4. 获取待处理申请金额
pendingAmount, err := s.getPendingApplicationsAmount(ctx, userID)
if err != nil {
return nil, err
}
// 5. 构建响应DTO
return &dto.AvailableAmountResponse{
AvailableAmount: availableAmount,
TotalRecharged: realRecharged, // 使用真实充值金额(支付宝充值+微信充值+对公转账)
TotalGifted: totalGifted,
TotalInvoiced: totalInvoiced,
PendingApplications: pendingAmount,
}, nil
}
// calculateAvailableAmount 计算可开票金额(私有方法)
func (s *InvoiceApplicationServiceImpl) calculateAvailableAmount(ctx context.Context, userID string) (decimal.Decimal, error) {
// 1. 获取真实充值金额(支付宝充值+微信充值+对公转账)和总赠送金额
realRecharged, totalGifted, totalInvoiced, err := s.getAmountSummary(ctx, userID)
if err != nil {
return decimal.Zero, err
}
// 2. 获取待处理中的申请金额
pendingAmount, err := s.getPendingApplicationsAmount(ctx, userID)
if err != nil {
return decimal.Zero, err
}
fmt.Println("realRecharged", realRecharged)
fmt.Println("totalGifted", totalGifted)
fmt.Println("totalInvoiced", totalInvoiced)
fmt.Println("pendingAmount", pendingAmount)
// 3. 计算可开票金额:真实充值金额 - 已开票 - 待处理申请
// 可开票金额 = 真实充值金额(支付宝充值+微信充值+对公转账) - 已开票金额 - 待处理申请金额
availableAmount := realRecharged.Sub(totalInvoiced).Sub(pendingAmount)
fmt.Println("availableAmount", availableAmount)
// 确保可开票金额不为负数
if availableAmount.LessThan(decimal.Zero) {
availableAmount = decimal.Zero
}
return availableAmount, nil
}
// getAmountSummary 获取金额汇总(私有方法)
func (s *InvoiceApplicationServiceImpl) getAmountSummary(ctx context.Context, userID string) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error) {
// 1. 获取用户所有成功的充值记录
rechargeRecords, err := s.rechargeRecordRepo.GetByUserID(ctx, userID)
if err != nil {
return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取充值记录失败: %w", err)
}
// 2. 计算真实充值金额(支付宝充值 + 微信充值 + 对公转账)和总赠送金额
var realRecharged decimal.Decimal // 真实充值金额:支付宝充值 + 微信充值 + 对公转账
var totalGifted decimal.Decimal // 总赠送金额
for _, record := range rechargeRecords {
if record.IsSuccess() {
if record.RechargeType == entities.RechargeTypeGift {
// 赠送金额不计入可开票金额
totalGifted = totalGifted.Add(record.Amount)
} else if record.RechargeType == entities.RechargeTypeAlipay || record.RechargeType == entities.RechargeTypeWechat || record.RechargeType == entities.RechargeTypeTransfer {
// 支付宝充值、微信充值和对公转账计入可开票金额
realRecharged = realRecharged.Add(record.Amount)
}
}
}
// 3. 获取用户所有发票申请记录(包括待处理、已完成、已拒绝)
applications, _, err := s.invoiceRepo.FindByUserID(ctx, userID, 1, 1000) // 获取所有记录
if err != nil {
return decimal.Zero, decimal.Zero, decimal.Zero, fmt.Errorf("获取发票申请记录失败: %w", err)
}
var totalInvoiced decimal.Decimal
for _, application := range applications {
// 计算已完成的发票申请金额
if application.IsCompleted() {
totalInvoiced = totalInvoiced.Add(application.Amount)
}
// 注意:待处理中的申请金额不计算在已开票金额中,但会在可开票金额计算时被扣除
}
return realRecharged, totalGifted, totalInvoiced, nil
}
// getPendingApplicationsAmount 获取待处理申请的总金额(私有方法)
func (s *InvoiceApplicationServiceImpl) getPendingApplicationsAmount(ctx context.Context, userID string) (decimal.Decimal, error) {
// 获取用户所有发票申请记录
applications, _, err := s.invoiceRepo.FindByUserID(ctx, userID, 1, 1000)
if err != nil {
return decimal.Zero, fmt.Errorf("获取发票申请记录失败: %w", err)
}
var pendingAmount decimal.Decimal
for _, application := range applications {
// 只计算待处理状态的申请金额
if application.Status == entities.ApplicationStatusPending {
pendingAmount = pendingAmount.Add(application.Amount)
}
}
return pendingAmount, nil
}
// ==================== 管理员端发票应用服务 ====================
// AdminInvoiceApplicationService 管理员发票应用服务接口
type AdminInvoiceApplicationService interface {
// GetPendingApplications 获取发票申请列表(支持筛选)
GetPendingApplications(ctx context.Context, req GetPendingApplicationsRequest) (*dto.PendingApplicationsResponse, error)
// ApproveInvoiceApplication 通过发票申请
ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error
// RejectInvoiceApplication 拒绝发票申请
RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error
// DownloadInvoiceFile 下载发票文件(管理员)
DownloadInvoiceFile(ctx context.Context, applicationID string) (*dto.FileDownloadResponse, error)
}
// AdminInvoiceApplicationServiceImpl 管理员发票应用服务实现
type AdminInvoiceApplicationServiceImpl struct {
invoiceRepo finance_repo.InvoiceApplicationRepository
userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository
userRepo user_repo.UserRepository
invoiceAggregateService services.InvoiceAggregateService
storageService *storage.QiNiuStorageService
logger *zap.Logger
}
// NewAdminInvoiceApplicationService 创建管理员发票应用服务
func NewAdminInvoiceApplicationService(
invoiceRepo finance_repo.InvoiceApplicationRepository,
userInvoiceInfoRepo finance_repo.UserInvoiceInfoRepository,
userRepo user_repo.UserRepository,
invoiceAggregateService services.InvoiceAggregateService,
storageService *storage.QiNiuStorageService,
logger *zap.Logger,
) AdminInvoiceApplicationService {
return &AdminInvoiceApplicationServiceImpl{
invoiceRepo: invoiceRepo,
userInvoiceInfoRepo: userInvoiceInfoRepo,
userRepo: userRepo,
invoiceAggregateService: invoiceAggregateService,
storageService: storageService,
logger: logger,
}
}
// GetPendingApplications 获取发票申请列表(支持筛选)
func (s *AdminInvoiceApplicationServiceImpl) GetPendingApplications(ctx context.Context, req GetPendingApplicationsRequest) (*dto.PendingApplicationsResponse, error) {
// 1. 解析状态筛选
var status entities.ApplicationStatus
if req.Status != "" {
status = entities.ApplicationStatus(req.Status)
}
// 2. 解析时间范围
var startTime, endTime *time.Time
if req.StartTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil {
startTime = &t
}
}
if req.EndTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil {
endTime = &t
}
}
// 3. 获取发票申请记录(支持筛选)
var applications []*entities.InvoiceApplication
var total int64
var err error
if status != "" {
// 按状态筛选
applications, total, err = s.invoiceRepo.FindByStatusWithTimeRange(ctx, status, startTime, endTime, req.Page, req.PageSize)
} else {
// 获取所有记录(按时间筛选)
applications, total, err = s.invoiceRepo.FindAllWithTimeRange(ctx, startTime, endTime, req.Page, req.PageSize)
}
if err != nil {
return nil, err
}
// 4. 构建响应DTO
pendingApplications := make([]*dto.PendingApplicationResponse, len(applications))
for i, app := range applications {
// 使用快照信息
pendingApplications[i] = &dto.PendingApplicationResponse{
ID: app.ID,
UserID: app.UserID,
InvoiceType: app.InvoiceType,
Amount: app.Amount,
Status: app.Status,
CompanyName: app.CompanyName, // 使用快照的公司名称
TaxpayerID: app.TaxpayerID, // 使用快照的纳税人识别号
BankName: app.BankName, // 使用快照的银行名称
BankAccount: app.BankAccount, // 使用快照的银行账号
CompanyAddress: app.CompanyAddress, // 使用快照的企业地址
CompanyPhone: app.CompanyPhone, // 使用快照的企业电话
ReceivingEmail: app.ReceivingEmail, // 使用快照的接收邮箱
FileName: app.FileName,
FileSize: app.FileSize,
FileURL: app.FileURL,
ProcessedAt: app.ProcessedAt,
CreatedAt: app.CreatedAt,
RejectReason: app.RejectReason,
}
}
return &dto.PendingApplicationsResponse{
Applications: pendingApplications,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: (int(total) + req.PageSize - 1) / req.PageSize,
}, nil
}
// ApproveInvoiceApplication 通过发票申请
func (s *AdminInvoiceApplicationServiceImpl) ApproveInvoiceApplication(ctx context.Context, applicationID string, file multipart.File, req ApproveInvoiceRequest) error {
// 1. 验证申请是否存在
application, err := s.invoiceRepo.FindByID(ctx, applicationID)
if err != nil {
return err
}
if application == nil {
return fmt.Errorf("发票申请不存在")
}
// 2. 验证申请状态
if application.Status != entities.ApplicationStatusPending {
return fmt.Errorf("发票申请状态不允许处理")
}
// 3. 调用聚合服务处理申请
aggregateReq := services.ApproveInvoiceRequest{
AdminNotes: req.AdminNotes,
}
return s.invoiceAggregateService.ApproveInvoiceApplication(ctx, applicationID, file, aggregateReq)
}
// RejectInvoiceApplication 拒绝发票申请
func (s *AdminInvoiceApplicationServiceImpl) RejectInvoiceApplication(ctx context.Context, applicationID string, req RejectInvoiceRequest) error {
// 1. 验证申请是否存在
application, err := s.invoiceRepo.FindByID(ctx, applicationID)
if err != nil {
return err
}
if application == nil {
return fmt.Errorf("发票申请不存在")
}
// 2. 验证申请状态
if application.Status != entities.ApplicationStatusPending {
return fmt.Errorf("发票申请状态不允许处理")
}
// 3. 调用聚合服务处理申请
aggregateReq := services.RejectInvoiceRequest{
Reason: req.Reason,
}
return s.invoiceAggregateService.RejectInvoiceApplication(ctx, applicationID, aggregateReq)
}
// ==================== 请求和响应DTO ====================
type ApplyInvoiceRequest struct {
InvoiceType string `json:"invoice_type" binding:"required"` // 发票类型general/special
Amount string `json:"amount" binding:"required"` // 开票金额
}
type UpdateInvoiceInfoRequest struct {
CompanyName string `json:"company_name"` // 公司名称(从企业认证信息获取,用户不可修改)
TaxpayerID string `json:"taxpayer_id"` // 纳税人识别号(从企业认证信息获取,用户不可修改)
BankName string `json:"bank_name"` // 银行名称
CompanyAddress string `json:"company_address"` // 公司地址
BankAccount string `json:"bank_account"` // 银行账户
CompanyPhone string `json:"company_phone"` // 企业注册电话
ReceivingEmail string `json:"receiving_email" binding:"required,email"` // 发票接收邮箱
}
type GetInvoiceRecordsRequest struct {
Page int `json:"page"` // 页码
PageSize int `json:"page_size"` // 每页数量
Status string `json:"status"` // 状态筛选
StartTime string `json:"start_time"` // 开始时间 (格式: 2006-01-02 15:04:05)
EndTime string `json:"end_time"` // 结束时间 (格式: 2006-01-02 15:04:05)
}
type GetPendingApplicationsRequest struct {
Page int `json:"page"` // 页码
PageSize int `json:"page_size"` // 每页数量
Status string `json:"status"` // 状态筛选pending/completed/rejected
StartTime string `json:"start_time"` // 开始时间 (格式: 2006-01-02 15:04:05)
EndTime string `json:"end_time"` // 结束时间 (格式: 2006-01-02 15:04:05)
}
type ApproveInvoiceRequest struct {
AdminNotes string `json:"admin_notes"` // 管理员备注
}
type RejectInvoiceRequest struct {
Reason string `json:"reason" binding:"required"` // 拒绝原因
}
// DownloadInvoiceFile 下载发票文件(管理员)
func (s *AdminInvoiceApplicationServiceImpl) DownloadInvoiceFile(ctx context.Context, applicationID string) (*dto.FileDownloadResponse, error) {
// 1. 查找申请记录
application, err := s.invoiceRepo.FindByID(ctx, applicationID)
if err != nil {
return nil, err
}
if application == nil {
return nil, fmt.Errorf("申请记录不存在")
}
// 2. 验证状态(只能下载已完成的发票)
if application.Status != entities.ApplicationStatusCompleted {
return nil, fmt.Errorf("发票尚未通过审核")
}
// 3. 验证文件信息
if application.FileURL == nil || *application.FileURL == "" {
return nil, fmt.Errorf("发票文件不存在")
}
// 4. 从七牛云下载文件内容
fileContent, err := s.storageService.DownloadFile(ctx, *application.FileURL)
if err != nil {
return nil, fmt.Errorf("下载文件失败: %w", err)
}
// 5. 构建响应DTO
return &dto.FileDownloadResponse{
FileID: *application.FileID,
FileName: *application.FileName,
FileSize: *application.FileSize,
FileURL: *application.FileURL,
FileContent: fileContent,
}, nil
}