This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -468,6 +468,77 @@ func (h *ApiHandler) GetAdminApiCalls(c *gin.Context) {
h.responseBuilder.Success(c, result, "获取API调用记录成功")
}
// ExportAdminApiCalls 导出管理端API调用记录
// @Summary 导出管理端API调用记录
// @Description 管理员导出API调用记录支持Excel和CSV格式
// @Tags API调用管理
// @Accept json
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv
// @Security Bearer
// @Param user_ids query string false "用户ID列表逗号分隔"
// @Param product_ids query string false "产品ID列表逗号分隔"
// @Param start_time query string false "开始时间" format(date-time)
// @Param end_time query string false "结束时间" format(date-time)
// @Param format query string false "导出格式" Enums(excel, csv) default(excel)
// @Success 200 {file} file "导出文件"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/admin/api-calls/export [get]
func (h *ApiHandler) ExportAdminApiCalls(c *gin.Context) {
// 解析查询参数
filters := make(map[string]interface{})
// 用户ID筛选
if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds
}
// 产品ID筛选
if productIds := c.Query("product_ids"); productIds != "" {
filters["product_ids"] = productIds
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 获取导出格式默认为excel
format := c.DefaultQuery("format", "excel")
if format != "excel" && format != "csv" {
h.responseBuilder.BadRequest(c, "不支持的导出格式")
return
}
// 调用应用服务导出数据
fileData, err := h.appService.ExportAdminApiCalls(c.Request.Context(), filters, format)
if err != nil {
h.logger.Error("导出API调用记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出API调用记录失败")
return
}
// 设置响应头
contentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename := "API调用记录.xlsx"
if format == "csv" {
contentType = "text/csv;charset=utf-8"
filename = "API调用记录.csv"
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(200, contentType, fileData)
}
// getIntQuery 获取整数查询参数
func (h *ApiHandler) getIntQuery(c *gin.Context, key string, defaultValue int) int {
if value := c.Query(key); value != "" {
@@ -477,3 +548,116 @@ func (h *ApiHandler) getIntQuery(c *gin.Context, key string, defaultValue int) i
}
return defaultValue
}
// GetUserBalanceAlertSettings 获取用户余额预警设置
// @Summary 获取用户余额预警设置
// @Description 获取当前用户的余额预警配置
// @Tags 用户设置
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{} "获取成功"
// @Failure 401 {object} map[string]interface{} "未授权"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/user/balance-alert/settings [get]
func (h *ApiHandler) GetUserBalanceAlertSettings(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
settings, err := h.appService.GetUserBalanceAlertSettings(c.Request.Context(), userID)
if err != nil {
h.logger.Error("获取用户余额预警设置失败",
zap.String("user_id", userID),
zap.Error(err))
h.responseBuilder.InternalError(c, "获取预警设置失败")
return
}
h.responseBuilder.Success(c, settings, "获取成功")
}
// UpdateUserBalanceAlertSettings 更新用户余额预警设置
// @Summary 更新用户余额预警设置
// @Description 更新当前用户的余额预警配置
// @Tags 用户设置
// @Accept json
// @Produce json
// @Param request body map[string]interface{} true "预警设置"
// @Success 200 {object} map[string]interface{} "更新成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未授权"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/user/balance-alert/settings [put]
func (h *ApiHandler) UpdateUserBalanceAlertSettings(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
var request struct {
Enabled bool `json:"enabled" binding:"required"`
Threshold float64 `json:"threshold" binding:"required,min=0"`
AlertPhone string `json:"alert_phone" binding:"required"`
}
if err := c.ShouldBindJSON(&request); err != nil {
h.responseBuilder.BadRequest(c, "请求参数错误: "+err.Error())
return
}
err := h.appService.UpdateUserBalanceAlertSettings(c.Request.Context(), userID, request.Enabled, request.Threshold, request.AlertPhone)
if err != nil {
h.logger.Error("更新用户余额预警设置失败",
zap.String("user_id", userID),
zap.Error(err))
h.responseBuilder.InternalError(c, "更新预警设置失败")
return
}
h.responseBuilder.Success(c, gin.H{}, "更新成功")
}
// TestBalanceAlertSms 测试余额预警短信
// @Summary 测试余额预警短信
// @Description 发送测试预警短信到指定手机号
// @Tags 用户设置
// @Accept json
// @Produce json
// @Param request body map[string]interface{} true "测试参数"
// @Success 200 {object} map[string]interface{} "发送成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未授权"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/user/balance-alert/test-sms [post]
func (h *ApiHandler) TestBalanceAlertSms(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.responseBuilder.Unauthorized(c, "用户未登录")
return
}
var request struct {
Phone string `json:"phone" binding:"required,len=11"`
Balance float64 `json:"balance" binding:"required"`
AlertType string `json:"alert_type" binding:"required,oneof=low_balance arrears"`
}
if err := c.ShouldBindJSON(&request); err != nil {
h.responseBuilder.BadRequest(c, "请求参数错误: "+err.Error())
return
}
err := h.appService.TestBalanceAlertSms(c.Request.Context(), userID, request.Phone, request.Balance, request.AlertType)
if err != nil {
h.logger.Error("发送测试预警短信失败",
zap.String("user_id", userID),
zap.Error(err))
h.responseBuilder.InternalError(c, "发送测试短信失败")
return
}
h.responseBuilder.Success(c, gin.H{}, "测试短信发送成功")
}

View File

@@ -215,6 +215,86 @@ func (h *CertificationHandler) ApplyContract(c *gin.Context) {
h.response.Success(c, result, "合同申请成功")
}
// RecognizeBusinessLicense OCR识别营业执照
// @Summary OCR识别营业执照
// @Description 上传营业执照图片进行OCR识别自动填充企业信息
// @Tags 认证管理
// @Accept multipart/form-data
// @Produce json
// @Security Bearer
// @Param image formData file true "营业执照图片文件"
// @Success 200 {object} responses.BusinessLicenseResult "营业执照识别成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications/ocr/business-license [post]
func (h *CertificationHandler) RecognizeBusinessLicense(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
// 获取上传的文件
file, err := c.FormFile("image")
if err != nil {
h.logger.Error("获取上传文件失败", zap.Error(err), zap.String("user_id", userID))
h.response.BadRequest(c, "请选择要上传的营业执照图片")
return
}
// 验证文件类型
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/jpg": true,
"image/png": true,
"image/webp": true,
}
if !allowedTypes[file.Header.Get("Content-Type")] {
h.response.BadRequest(c, "只支持JPG、PNG、WEBP格式的图片")
return
}
// 验证文件大小限制为5MB
if file.Size > 5*1024*1024 {
h.response.BadRequest(c, "图片大小不能超过5MB")
return
}
// 打开文件
src, err := file.Open()
if err != nil {
h.logger.Error("打开上传文件失败", zap.Error(err), zap.String("user_id", userID))
h.response.BadRequest(c, "文件读取失败")
return
}
defer src.Close()
// 读取文件内容
imageBytes, err := io.ReadAll(src)
if err != nil {
h.logger.Error("读取文件内容失败", zap.Error(err), zap.String("user_id", userID))
h.response.BadRequest(c, "文件读取失败")
return
}
// 调用OCR服务识别营业执照
result, err := h.appService.RecognizeBusinessLicense(c.Request.Context(), imageBytes)
if err != nil {
h.logger.Error("营业执照OCR识别失败", zap.Error(err), zap.String("user_id", userID))
h.response.BadRequest(c, "营业执照识别失败:"+err.Error())
return
}
h.logger.Info("营业执照OCR识别成功",
zap.String("user_id", userID),
zap.String("company_name", result.CompanyName),
zap.Float64("confidence", result.Confidence),
)
h.response.Success(c, result, "营业执照识别成功")
}
// ListCertifications 获取认证列表(管理员)
// @Summary 获取认证列表
// @Description 管理员获取认证申请列表

View File

@@ -1199,6 +1199,102 @@ func (h *ProductAdminHandler) GetAdminWalletTransactions(c *gin.Context) {
h.responseBuilder.Success(c, result, "获取消费记录成功")
}
// ExportAdminWalletTransactions 导出管理端消费记录
// @Summary 导出管理端消费记录
// @Description 管理员导出消费记录支持Excel和CSV格式
// @Tags 财务管理
// @Accept json
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv
// @Security Bearer
// @Param user_ids query string false "用户ID列表逗号分隔"
// @Param user_id query string false "单个用户ID"
// @Param transaction_id query string false "交易ID"
// @Param product_name query string false "产品名称"
// @Param product_ids query string false "产品ID列表逗号分隔"
// @Param min_amount query string false "最小金额"
// @Param max_amount query string false "最大金额"
// @Param start_time query string false "开始时间" format(date-time)
// @Param end_time query string false "结束时间" format(date-time)
// @Param format query string false "导出格式" Enums(excel, csv) default(excel)
// @Success 200 {file} file "导出文件"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/admin/wallet-transactions/export [get]
func (h *ProductAdminHandler) ExportAdminWalletTransactions(c *gin.Context) {
// 构建筛选条件
filters := make(map[string]interface{})
// 用户ID筛选
if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds
} else if userId := c.Query("user_id"); userId != "" {
filters["user_id"] = userId
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 交易ID筛选
if transactionId := c.Query("transaction_id"); transactionId != "" {
filters["transaction_id"] = transactionId
}
// 产品名称筛选
if productName := c.Query("product_name"); productName != "" {
filters["product_name"] = productName
}
// 产品ID列表筛选
if productIds := c.Query("product_ids"); productIds != "" {
filters["product_ids"] = productIds
}
// 金额范围筛选
if minAmount := c.Query("min_amount"); minAmount != "" {
filters["min_amount"] = minAmount
}
if maxAmount := c.Query("max_amount"); maxAmount != "" {
filters["max_amount"] = maxAmount
}
// 获取导出格式
format := c.DefaultQuery("format", "excel")
if format != "excel" && format != "csv" {
h.responseBuilder.BadRequest(c, "不支持的导出格式")
return
}
// 调用导出服务
fileData, err := h.financeAppService.ExportAdminWalletTransactions(c.Request.Context(), filters, format)
if err != nil {
h.logger.Error("导出消费记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出消费记录失败")
return
}
// 设置响应头
contentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename := "消费记录.xlsx"
if format == "csv" {
contentType = "text/csv;charset=utf-8"
filename = "消费记录.csv"
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(200, contentType, fileData)
}
// GetAdminRechargeRecords 获取管理端充值记录
// @Summary 获取管理端充值记录
// @Description 管理员获取充值记录,支持筛选和分页
@@ -1282,3 +1378,184 @@ func (h *ProductAdminHandler) GetAdminRechargeRecords(c *gin.Context) {
h.responseBuilder.Success(c, result, "获取充值记录成功")
}
// ExportAdminRechargeRecords 导出管理端充值记录
// @Summary 导出管理端充值记录
// @Description 管理员导出充值记录支持Excel和CSV格式
// @Tags 财务管理
// @Accept json
// @Produce application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,text/csv
// @Security Bearer
// @Param user_ids query string false "用户ID列表逗号分隔"
// @Param recharge_type query string false "充值类型" Enums(alipay, transfer, gift)
// @Param status query string false "状态" Enums(pending, success, failed)
// @Param start_time query string false "开始时间" format(date-time)
// @Param end_time query string false "结束时间" format(date-time)
// @Param format query string false "导出格式" Enums(excel, csv) default(excel)
// @Success 200 {file} file "导出文件"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/admin/recharge-records/export [get]
func (h *ProductAdminHandler) ExportAdminRechargeRecords(c *gin.Context) {
// 解析查询参数
filters := make(map[string]interface{})
// 用户ID筛选
if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds
}
// 充值类型筛选
if rechargeType := c.Query("recharge_type"); rechargeType != "" {
filters["recharge_type"] = rechargeType
}
// 状态筛选
if status := c.Query("status"); status != "" {
filters["status"] = status
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 获取导出格式默认为excel
format := c.DefaultQuery("format", "excel")
if format != "excel" && format != "csv" {
h.responseBuilder.BadRequest(c, "不支持的导出格式")
return
}
// 调用应用服务导出数据
fileData, err := h.financeAppService.ExportAdminRechargeRecords(c.Request.Context(), filters, format)
if err != nil {
h.logger.Error("导出充值记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出充值记录失败")
return
}
// 设置响应头
contentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename := "充值记录.xlsx"
if format == "csv" {
contentType = "text/csv;charset=utf-8"
filename = "充值记录.csv"
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(200, contentType, fileData)
}
// GetAdminApiCalls 获取管理端API调用记录
func (h *ProductAdminHandler) GetAdminApiCalls(c *gin.Context) {
// 解析查询参数
page := h.getIntQuery(c, "page", 1)
pageSize := h.getIntQuery(c, "page_size", 10)
// 构建筛选条件
filters := make(map[string]interface{})
// 用户ID筛选
if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds
}
// 产品ID筛选
if productIds := c.Query("product_ids"); productIds != "" {
filters["product_ids"] = productIds
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 构建分页选项
options := interfaces.ListOptions{
Page: page,
PageSize: pageSize,
Sort: "created_at",
Order: "desc",
}
result, err := h.apiAppService.GetAdminApiCalls(c.Request.Context(), filters, options)
if err != nil {
h.logger.Error("获取管理端API调用记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "获取API调用记录失败")
return
}
h.responseBuilder.Success(c, result, "获取API调用记录成功")
}
// ExportAdminApiCalls 导出管理端API调用记录
func (h *ProductAdminHandler) ExportAdminApiCalls(c *gin.Context) {
// 解析查询参数
filters := make(map[string]interface{})
// 用户ID筛选
if userIds := c.Query("user_ids"); userIds != "" {
filters["user_ids"] = userIds
}
// 产品ID筛选
if productIds := c.Query("product_ids"); productIds != "" {
filters["product_ids"] = productIds
}
// 时间范围筛选
if startTime := c.Query("start_time"); startTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", startTime); err == nil {
filters["start_time"] = t
}
}
if endTime := c.Query("end_time"); endTime != "" {
if t, err := time.Parse("2006-01-02 15:04:05", endTime); err == nil {
filters["end_time"] = t
}
}
// 获取导出格式默认为excel
format := c.DefaultQuery("format", "excel")
if format != "excel" && format != "csv" {
h.responseBuilder.BadRequest(c, "不支持的导出格式")
return
}
// 调用应用服务导出数据
fileData, err := h.apiAppService.ExportAdminApiCalls(c.Request.Context(), filters, format)
if err != nil {
h.logger.Error("导出API调用记录失败", zap.Error(err))
h.responseBuilder.BadRequest(c, "导出API调用记录失败")
return
}
// 设置响应头
contentType := "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
filename := "API调用记录.xlsx"
if format == "csv" {
contentType = "text/csv;charset=utf-8"
filename = "API调用记录.csv"
}
c.Header("Content-Type", contentType)
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Data(200, contentType, fileData)
}

File diff suppressed because it is too large Load Diff

View File

@@ -274,7 +274,6 @@ func (h *UserHandler) ListUsers(c *gin.Context) {
return
}
// 构建查询参数
query := &queries.ListUsersQuery{
Page: 1,
@@ -289,7 +288,7 @@ func (h *UserHandler) ListUsers(c *gin.Context) {
}
if pageSize := c.Query("page_size"); pageSize != "" {
if size, err := strconv.Atoi(pageSize); err == nil && size > 0 && size <= 100 {
if size, err := strconv.Atoi(pageSize); err == nil && size > 0 && size <= 1000 {
query.PageSize = size
}
}