v0.1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# 第一阶段:构建阶段
|
||||
FROM golang:1.23-alpine AS builder
|
||||
FROM golang:1.23.4-alpine AS builder
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
|
||||
58
config.yaml
58
config.yaml
@@ -60,6 +60,9 @@ jwt:
|
||||
expires_in: 168h
|
||||
refresh_expires_in: 168h
|
||||
|
||||
api:
|
||||
domain: "https://apitest.tianyuancha.com"
|
||||
|
||||
sms:
|
||||
access_key_id: "LTAI5tKGB3TVJbMHSoZN3yr9"
|
||||
access_key_secret: "OCQ30GWp4yENMjmfOAaagksE18bp65"
|
||||
@@ -86,6 +89,11 @@ ocr:
|
||||
api_key: "your-baidu-api-key"
|
||||
secret_key: "your-baidu-secret-key"
|
||||
|
||||
# 充值配置
|
||||
recharge:
|
||||
min_amount: "100.00" # 生产环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
|
||||
ratelimit:
|
||||
requests: 5000
|
||||
window: 60s
|
||||
@@ -130,10 +138,7 @@ esign:
|
||||
app_id: "7439073138"
|
||||
app_secret: "d76e27fdd169b391e09262a0959dac5c"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "b3d8c665dd344f17bdb19940876e145f"
|
||||
rsa_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvQjSHd/MBiLpIswSMCnzaKJbhJMxCIzrmbFEVb33JhV6R8l/ADp1sgiXX8Jzbc5zvnCmtL6zU7q2BmtwiO0CUsagsmZwc6oxGlcx5pOGOn/GvzOau79YQFpp9W+Xqo33qxJwm9FjjTxhGHawJ3pGFjloyevjhtFtufUhqIovB4laChR4kOParJF0iWSyahH8guS/k/zXv/lvp5b4mwww34S8233jbDvm7qUDhqh+BJalkfF6lyQirhv4x/8qt5v1vBp6W69+K5U4sm1xpNVrM/5nnCXyYVg0OItBmrBaoiHagx9XgqhcT8GDQicQVL9bDRd3HlLcf6hqymklnqFufQIDAQAB"
|
||||
aes_secret: "3996443939925655558"
|
||||
aes_secret_key: "3996443939925655558"
|
||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -143,10 +148,49 @@ esign:
|
||||
default_auth_mode: "PSN_MOBILE3"
|
||||
psn_auth_modes: ["PSN_MOBILE3", "PSN_IDCARD"]
|
||||
willingness_auth_modes: ["CODE_SMS"]
|
||||
redirect_url: "https://consoletest.tianyuanapi.com/certification/callback/auth"
|
||||
sign:
|
||||
auto_finish: true
|
||||
sign_field_style: 1
|
||||
client_type: "ALL"
|
||||
notify:
|
||||
types: "1"
|
||||
redirect_url: "https://www.tianyuanapi.com/certification/complete"
|
||||
redirect_url: "https://consoletest.tianyuanapi.com/certification/callback/sign"
|
||||
|
||||
# ===========================================
|
||||
# 💰 钱包配置
|
||||
# ===========================================
|
||||
wallet:
|
||||
default_credit_limit: 50.00
|
||||
|
||||
# ===========================================
|
||||
# 🌍 西部数据配置
|
||||
# ===========================================
|
||||
westdex:
|
||||
url: "https://apimaster.westdex.com.cn/api/invoke"
|
||||
key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||
secret_id: "449159"
|
||||
secret_second_id: "296804"
|
||||
|
||||
# ===========================================
|
||||
# 🌍 羽山配置
|
||||
# ===========================================
|
||||
yushan:
|
||||
url: https://api.yushanshuju.com/credit-gw/service"
|
||||
api_key: "4c566c4a4b543164535455685655316c"
|
||||
acct_id: "YSSJ843926726"
|
||||
|
||||
# ===========================================
|
||||
# 💰 支付宝支付配置
|
||||
# ===========================================
|
||||
alipay:
|
||||
app_id: "2021004181633376"
|
||||
private_key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCC2GNEWrQUg6FVHBdlDcgL1SA1KmRI8IHgSJGvXEsgfo3g62aa5usFXHVz5bMzpIcDu0N+jGtZQIBuuq7TxGwhDvWBygEDXN17p00uwqik/3TsyFvJ4FfbkaS7pRIGfeO/cBTzjqznanPUdHJZ9L39QmTqTefIQQvGOCvgntKxPa/LdS24+ZLA2RNh3TsRzbSxOOJPmUrwvCX8U13F9jH250hvf+Tewz4hyG8CkiMM4d1UpGMndQNr8oTY0vwFbWAG0ZDGgkxjg0iRJ02fgxwShQS1TgY5NxPhpKBiN5C/WG15qCqEw0F3GlpfWZwzUhv1uMiy+xbZ2bGLo1YCtwUtAgMBAAECggEAQ8uk25T3u61cWYH9qTGT1nWug32ciqJ7WN+hBLCYiJSqJMEz380INzXp8Ywx5u83ubo8xYQyVwNxyG3YCge7UwGyOXaWQczLQbe06SaZRSzLw6gozxf7zdvP9B4akdyGtfl4EZ56fkmNDKbtXSjPjDrrmO+Wyg7R7/nI2lDQsF6dXTKD0YiHtTKz40amKgbIYX+qc3yVS0slkVjcfnRczr+PKM5RMsV3Jk2pr6IYeq3E24LnbuVtV76priTqJN3hVSy2Y6JqmAYkI0HCoCuaFGE8ud3J859jjMcUXTRFJyDsKKooa+FZCoEx2ToVMqnb4vjfr1gZifUrw4ZNd5cPoQKBgQC4v/fNTXuA21pb+l4fnqK0o3wFhiNJh920yIlF4Vd0Nsi2/TwqFK6cVhrUFAmKr88hTzY1vkOhd/HLlkWjNDR5OGx1K1BKUAZjWIfProv8lDSckADEI29lro9WzFGy0o4szlEJ2uuUfO/j9Qn2lmx5oFPsz0TI+HoSNFE0q/SlxQKBgQC1ToMLuh0OkucZm1SL6xcjudBX7U0ElZ/TIxRzfxQ/sN911/BRlxrSdCcDMXNuuFpV2ACjDNWWLJM1sRVsOWNA/oXzZf6VTvUDIAv8XrNUt/B87genBVuMTZ2RYmMWCrgW0PE1OrpKGuQCKVsn242B2Xpmee9OnHhBF2uTASDASQKBgBALvD38iMl8Q7DRYfNlF8SQnmjsaYwtXLgi4qlLFQlm6K/b9qnA+hlh8RqSUvHUqyy9cHvidoVDoaCJAKtYEWal2+WhSWvq32MpgUIsasQZKyid6TMf0MEIFDL5s+7QEsEZejhc5zESWNN3qNHd5rX5ktBygArkadXC7XqhpLHxAoGBAJ0dJEKNTZDLjKiMCoAVgT/cTcdkRFGst4tn4tkTTqDCzWJ5di++Geg173i86aMQ7ndlb2fcP1qb1hW5Fy9pq7Eu3zVFNZB9k6TZqIlSJ2VK4IPiYY9C/UpgGCNcdzEqqMxc1Cmkcrq1AtE8tVmc0Mutgnw7Pj2JKkx91yLU32TBAoGAKxssUdTLuf5Z5oFgzpoSES9qwc1h6jlMfsouDzHcZf0aYintD6Vby7SVul5540qYkDkNs0YZ3uZu74LHfoBaWJjYIIVAMSMX+3AtBpQUyYluex64V/g60t+0sFuDWqMvSPU7mZcv6+KIP6vW56GeYdhHf4JqttdIHm9SgkoJjjY="
|
||||
alipay_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2CqoCp95w/JV3RT/gzF4/8QmVT1HQNaeW7yUp+mA7x9AbjvlTW/+eRn6oGAL/XhZLjvHD0XjKLVKX0MJVS1aUQHEHEbOJN4Eu8II45OavD4iZISa7Kp9V6AM+i4qTyaeV2wNDnGxHQBaLVUGCfMR+56EK2YpORdE1H9uy72SSQseVb3bmpsV9EW/IJNmcVL/ut3uA1JWAoRmzlQ7ekxg7p8AYXzYPEHQr1tl7W+M4zv9wO9GKZCxIqMA8U3RP5npPfRaCfIRGzXzCqFEEUvWuidOB7frsvN4jiPD07qpL2Bi9LM1X/ee2kC/oM8Uhd7ERZhG8MbZfijZKxgrsDKBcwIDAQAB"
|
||||
is_production: true
|
||||
notify_url: "https://consoletest.tianyuanapi.com/api/v1/finance/alipay/callback"
|
||||
return_url: "https://consoletest.tianyuanapi.com/api/v1/finance/alipay/return"
|
||||
|
||||
# ===========================================
|
||||
# 🌐 域名配置
|
||||
# ===========================================
|
||||
domain:
|
||||
api: "" # 开发环境不限制域名,生产环境为 "api.tianyuancha.com"
|
||||
|
||||
@@ -12,6 +12,7 @@ app:
|
||||
# ===========================================
|
||||
database:
|
||||
password: Pg9mX4kL8nW2rT5y
|
||||
name: "tyapi_dev"
|
||||
|
||||
# ===========================================
|
||||
# 🔐 JWT配置
|
||||
@@ -39,10 +40,10 @@ ocr:
|
||||
# 📝 e签宝服务配置
|
||||
# ===========================================
|
||||
esign:
|
||||
app_id: "7439073431"
|
||||
app_secret: "08d1f5ef3c364acb25ea0e9916684ca0"
|
||||
app_id: "7439073713"
|
||||
app_secret: "c7d8cb0d701f7890601d221e9b6edfef"
|
||||
server_url: "https://smlopenapi.esign.cn"
|
||||
template_id: "b3d8c665dd344f17bdb19940876e145f"
|
||||
template_id: "1fd7ed9c6d134d1db7b5af9582633d76"
|
||||
contract:
|
||||
name: "天远数据API合作协议"
|
||||
expire_days: 7
|
||||
@@ -52,10 +53,34 @@ esign:
|
||||
default_auth_mode: "PSN_MOBILE3"
|
||||
psn_auth_modes: ["PSN_MOBILE3", "PSN_IDCARD"]
|
||||
willingness_auth_modes: ["CODE_SMS"]
|
||||
redirect_url: "http://localhost:5173/certification/callback/auth"
|
||||
sign:
|
||||
auto_finish: true
|
||||
sign_field_style: 1
|
||||
client_type: "ALL"
|
||||
notify:
|
||||
types: "1"
|
||||
redirect_url: "https://www.tianyuanapi.com/certification/complete"
|
||||
redirect_url: "http://localhost:5173/certification/callback/sign"
|
||||
# ===========================================
|
||||
# 🌍 西部数据配置
|
||||
# ===========================================
|
||||
westdex:
|
||||
url: "http://proxy.tianyuanapi.com/api/invoke"
|
||||
key: "121a1e41fc1690dd6b90afbcacd80cf4"
|
||||
secret_id: "449159"
|
||||
secret_second_id: "296804"
|
||||
# ===========================================
|
||||
# 💰 支付宝支付配置
|
||||
# ===========================================
|
||||
alipay:
|
||||
app_id: "2021004181633376"
|
||||
private_key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCC2GNEWrQUg6FVHBdlDcgL1SA1KmRI8IHgSJGvXEsgfo3g62aa5usFXHVz5bMzpIcDu0N+jGtZQIBuuq7TxGwhDvWBygEDXN17p00uwqik/3TsyFvJ4FfbkaS7pRIGfeO/cBTzjqznanPUdHJZ9L39QmTqTefIQQvGOCvgntKxPa/LdS24+ZLA2RNh3TsRzbSxOOJPmUrwvCX8U13F9jH250hvf+Tewz4hyG8CkiMM4d1UpGMndQNr8oTY0vwFbWAG0ZDGgkxjg0iRJ02fgxwShQS1TgY5NxPhpKBiN5C/WG15qCqEw0F3GlpfWZwzUhv1uMiy+xbZ2bGLo1YCtwUtAgMBAAECggEAQ8uk25T3u61cWYH9qTGT1nWug32ciqJ7WN+hBLCYiJSqJMEz380INzXp8Ywx5u83ubo8xYQyVwNxyG3YCge7UwGyOXaWQczLQbe06SaZRSzLw6gozxf7zdvP9B4akdyGtfl4EZ56fkmNDKbtXSjPjDrrmO+Wyg7R7/nI2lDQsF6dXTKD0YiHtTKz40amKgbIYX+qc3yVS0slkVjcfnRczr+PKM5RMsV3Jk2pr6IYeq3E24LnbuVtV76priTqJN3hVSy2Y6JqmAYkI0HCoCuaFGE8ud3J859jjMcUXTRFJyDsKKooa+FZCoEx2ToVMqnb4vjfr1gZifUrw4ZNd5cPoQKBgQC4v/fNTXuA21pb+l4fnqK0o3wFhiNJh920yIlF4Vd0Nsi2/TwqFK6cVhrUFAmKr88hTzY1vkOhd/HLlkWjNDR5OGx1K1BKUAZjWIfProv8lDSckADEI29lro9WzFGy0o4szlEJ2uuUfO/j9Qn2lmx5oFPsz0TI+HoSNFE0q/SlxQKBgQC1ToMLuh0OkucZm1SL6xcjudBX7U0ElZ/TIxRzfxQ/sN911/BRlxrSdCcDMXNuuFpV2ACjDNWWLJM1sRVsOWNA/oXzZf6VTvUDIAv8XrNUt/B87genBVuMTZ2RYmMWCrgW0PE1OrpKGuQCKVsn242B2Xpmee9OnHhBF2uTASDASQKBgBALvD38iMl8Q7DRYfNlF8SQnmjsaYwtXLgi4qlLFQlm6K/b9qnA+hlh8RqSUvHUqyy9cHvidoVDoaCJAKtYEWal2+WhSWvq32MpgUIsasQZKyid6TMf0MEIFDL5s+7QEsEZejhc5zESWNN3qNHd5rX5ktBygArkadXC7XqhpLHxAoGBAJ0dJEKNTZDLjKiMCoAVgT/cTcdkRFGst4tn4tkTTqDCzWJ5di++Geg173i86aMQ7ndlb2fcP1qb1hW5Fy9pq7Eu3zVFNZB9k6TZqIlSJ2VK4IPiYY9C/UpgGCNcdzEqqMxc1Cmkcrq1AtE8tVmc0Mutgnw7Pj2JKkx91yLU32TBAoGAKxssUdTLuf5Z5oFgzpoSES9qwc1h6jlMfsouDzHcZf0aYintD6Vby7SVul5540qYkDkNs0YZ3uZu74LHfoBaWJjYIIVAMSMX+3AtBpQUyYluex64V/g60t+0sFuDWqMvSPU7mZcv6+KIP6vW56GeYdhHf4JqttdIHm9SgkoJjjY="
|
||||
alipay_public_key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2CqoCp95w/JV3RT/gzF4/8QmVT1HQNaeW7yUp+mA7x9AbjvlTW/+eRn6oGAL/XhZLjvHD0XjKLVKX0MJVS1aUQHEHEbOJN4Eu8II45OavD4iZISa7Kp9V6AM+i4qTyaeV2wNDnGxHQBaLVUGCfMR+56EK2YpORdE1H9uy72SSQseVb3bmpsV9EW/IJNmcVL/ut3uA1JWAoRmzlQ7ekxg7p8AYXzYPEHQr1tl7W+M4zv9wO9GKZCxIqMA8U3RP5npPfRaCfIRGzXzCqFEEUvWuidOB7frsvN4jiPD07qpL2Bi9LM1X/ee2kC/oM8Uhd7ERZhG8MbZfijZKxgrsDKBcwIDAQAB"
|
||||
is_production: true
|
||||
notify_url: "https://6m4685017o.goho.co/api/v1/finance/alipay/callback"
|
||||
return_url: "http://127.0.0.1:8080/api/v1/finance/alipay/return"
|
||||
|
||||
# ===========================================
|
||||
# 💰 充值配置
|
||||
# ===========================================
|
||||
recharge:
|
||||
min_amount: "0.01" # 开发环境最低充值金额
|
||||
max_amount: "100000.00" # 单次最高充值金额
|
||||
@@ -19,6 +19,7 @@ server:
|
||||
# 敏感信息通过外部环境变量注入
|
||||
database:
|
||||
password: Pg9mX4kL8nW2rT5y
|
||||
name: "tyapi"
|
||||
|
||||
# ===========================================
|
||||
# 📝 日志配置
|
||||
@@ -77,3 +78,9 @@ esign:
|
||||
notify:
|
||||
types: "1"
|
||||
redirect_url: "https://www.tianyuanapi.com/certification/complete"
|
||||
|
||||
# ===========================================
|
||||
# 🌐 域名配置
|
||||
# ===========================================
|
||||
domain:
|
||||
api: "api.tianyuancha.com" # 生产环境API域名
|
||||
|
||||
@@ -34,9 +34,9 @@ services:
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: "0.5"
|
||||
# 生产环境不暴露端口到主机
|
||||
# ports:
|
||||
# - "5432:5432"
|
||||
# 生产环境暴露数据库端口到主机
|
||||
ports:
|
||||
- "${DB_PORT:-25010}:5432"
|
||||
|
||||
# Redis 缓存 (生产环境)
|
||||
redis:
|
||||
@@ -85,7 +85,13 @@ services:
|
||||
|
||||
# TYAPI 应用程序
|
||||
tyapi-app:
|
||||
image: docker-registry.tianyuanapi.com/tyapi-server:${APP_VERSION:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
VERSION: ${APP_VERSION:-1.0.0}
|
||||
COMMIT: ${GIT_COMMIT:-dev}
|
||||
BUILD_TIME: ${BUILD_TIME}
|
||||
container_name: tyapi-app-prod
|
||||
environment:
|
||||
# 时区配置
|
||||
@@ -115,9 +121,8 @@ services:
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
|
||||
# 监控配置
|
||||
TRACING_ENABLED: true
|
||||
TRACING_ENDPOINT: http://jaeger:4317
|
||||
METRICS_ENABLED: true
|
||||
TRACING_ENABLED: false
|
||||
METRICS_ENABLED: false
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
@@ -129,7 +134,7 @@ services:
|
||||
SMS_SIGN_NAME: ${SMS_SIGN_NAME}
|
||||
SMS_TEMPLATE_CODE: ${SMS_TEMPLATE_CODE}
|
||||
ports:
|
||||
- "${APP_PORT:-8080}:8080"
|
||||
- "${APP_PORT:-25000}:8080"
|
||||
volumes:
|
||||
- app_logs:/app/logs
|
||||
networks:
|
||||
@@ -155,279 +160,17 @@ services:
|
||||
memory: 256M
|
||||
cpus: "0.3"
|
||||
|
||||
# Jaeger 链路追踪 (生产环境配置)
|
||||
jaeger:
|
||||
image: jaegertracing/all-in-one:1.70.0
|
||||
container_name: tyapi-jaeger-prod
|
||||
ports:
|
||||
- "${JAEGER_UI_PORT:-16686}:16686" # Jaeger UI
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
environment:
|
||||
# 时区配置
|
||||
TZ: Asia/Shanghai
|
||||
# 启用OTLP接收器
|
||||
COLLECTOR_OTLP_ENABLED: true
|
||||
# 配置持久化存储 (生产环境建议使用Elasticsearch/Cassandra)
|
||||
SPAN_STORAGE_TYPE: memory
|
||||
# 设置日志级别
|
||||
LOG_LEVEL: warn
|
||||
# 配置采样策略
|
||||
SAMPLING_STRATEGIES_FILE: /etc/jaeger/sampling_strategies.json
|
||||
# 内存存储配置 (生产环境应增加)
|
||||
MEMORY_MAX_TRACES: 100000
|
||||
# 查询服务配置
|
||||
QUERY_MAX_CLOCK_SKEW_ADJUSTMENT: 0
|
||||
# 收集器配置 (生产环境优化)
|
||||
COLLECTOR_QUEUE_SIZE: 5000
|
||||
COLLECTOR_NUM_WORKERS: 100
|
||||
# gRPC服务器配置
|
||||
COLLECTOR_GRPC_SERVER_MAX_RECEIVE_MESSAGE_LENGTH: 8388608
|
||||
COLLECTOR_GRPC_SERVER_MAX_CONNECTION_AGE: 120s
|
||||
COLLECTOR_GRPC_SERVER_MAX_CONNECTION_IDLE: 60s
|
||||
# HTTP服务器配置
|
||||
COLLECTOR_HTTP_SERVER_HOST_PORT: :14268
|
||||
COLLECTOR_HTTP_SERVER_READ_TIMEOUT: 30s
|
||||
COLLECTOR_HTTP_SERVER_WRITE_TIMEOUT: 30s
|
||||
# UI配置
|
||||
QUERY_UI_CONFIG: /etc/jaeger/ui-config.json
|
||||
# 安全配置
|
||||
QUERY_BASE_PATH: /
|
||||
volumes:
|
||||
- ./deployments/docker/jaeger-sampling-prod.json:/etc/jaeger/sampling_strategies.json
|
||||
- ./deployments/docker/jaeger-ui-config.json:/etc/jaeger/ui-config.json
|
||||
networks:
|
||||
- tyapi-network
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--no-verbose",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:14269/health",
|
||||
]
|
||||
interval: 60s
|
||||
timeout: 30s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "0.5"
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: "0.2"
|
||||
|
||||
# Nginx 反向代理 (可选)
|
||||
nginx:
|
||||
image: nginx:1.27.3-alpine
|
||||
container_name: tyapi-nginx-prod
|
||||
ports:
|
||||
- "${NGINX_HTTP_PORT:-80}:80"
|
||||
- "${NGINX_HTTPS_PORT:-443}:443"
|
||||
volumes:
|
||||
- ./deployments/docker/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./deployments/docker/ssl:/etc/nginx/ssl
|
||||
- nginx_logs:/var/log/nginx
|
||||
networks:
|
||||
- tyapi-network
|
||||
depends_on:
|
||||
- tyapi-app
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--quiet",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost/health",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 256M
|
||||
cpus: "0.3"
|
||||
reservations:
|
||||
memory: 64M
|
||||
cpus: "0.1"
|
||||
|
||||
# Prometheus 监控 (生产环境)
|
||||
prometheus:
|
||||
image: prom/prometheus:v2.55.1
|
||||
container_name: tyapi-prometheus-prod
|
||||
ports:
|
||||
- "${PROMETHEUS_PORT:-9090}:9090"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- ./deployments/docker/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||
- "--storage.tsdb.path=/prometheus"
|
||||
- "--web.console.libraries=/etc/prometheus/console_libraries"
|
||||
- "--web.console.templates=/etc/prometheus/consoles"
|
||||
- "--web.enable-lifecycle"
|
||||
- "--storage.tsdb.retention.time=30d"
|
||||
- "--storage.tsdb.retention.size=10GB"
|
||||
- "--web.enable-admin-api"
|
||||
networks:
|
||||
- tyapi-network
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--quiet",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:9090/-/healthy",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2G
|
||||
cpus: "1.0"
|
||||
reservations:
|
||||
memory: 512M
|
||||
cpus: "0.3"
|
||||
|
||||
# Grafana 仪表盘 (生产环境)
|
||||
grafana:
|
||||
image: grafana/grafana:11.4.0
|
||||
container_name: tyapi-grafana-prod
|
||||
ports:
|
||||
- "${GRAFANA_PORT:-3000}:3000"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-Gf7nB3xM9cV6pQ2w}
|
||||
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
|
||||
GF_INSTALL_PLUGINS: "grafana-clock-panel,grafana-simple-json-datasource"
|
||||
GF_ANALYTICS_REPORTING_ENABLED: "false"
|
||||
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
|
||||
GF_USERS_ALLOW_SIGN_UP: "false"
|
||||
GF_SERVER_ROOT_URL: "http://localhost:3000"
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./deployments/docker/grafana/provisioning:/etc/grafana/provisioning
|
||||
networks:
|
||||
- tyapi-network
|
||||
depends_on:
|
||||
- prometheus
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--quiet",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost:3000/api/health",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "0.5"
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: "0.2"
|
||||
|
||||
# MinIO 对象存储 (生产环境)
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2024-12-18T13-15-44Z
|
||||
container_name: tyapi-minio-prod
|
||||
ports:
|
||||
- "${MINIO_API_PORT:-9000}:9000"
|
||||
- "${MINIO_CONSOLE_PORT:-9001}:9001"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-Mn5oH8yK3bR7vX1z}
|
||||
MINIO_BROWSER_REDIRECT_URL: "http://localhost:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
networks:
|
||||
- tyapi-network
|
||||
healthcheck:
|
||||
test:
|
||||
["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
cpus: "0.5"
|
||||
reservations:
|
||||
memory: 256M
|
||||
cpus: "0.2"
|
||||
|
||||
# pgAdmin 数据库管理 (生产环境)
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:8.15
|
||||
container_name: tyapi-pgadmin-prod
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@tyapi.com}
|
||||
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-Pa4dG9wF2sL6tN8u}
|
||||
PGADMIN_CONFIG_SERVER_MODE: "True"
|
||||
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
|
||||
PGADMIN_CONFIG_UPGRADE_CHECK_ENABLED: "False"
|
||||
PGADMIN_CONFIG_ENHANCED_COOKIE_PROTECTION: "False"
|
||||
ports:
|
||||
- "${PGADMIN_PORT:-5050}:80"
|
||||
volumes:
|
||||
- pgadmin_data:/var/lib/pgadmin
|
||||
- ./deployments/docker/pgadmin-servers.json:/pgadmin4/servers.json
|
||||
- ./deployments/docker/pgadmin-passfile:/var/lib/pgadmin/passfile
|
||||
networks:
|
||||
- tyapi-network
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"wget",
|
||||
"--quiet",
|
||||
"--tries=1",
|
||||
"--spider",
|
||||
"http://localhost/misc/ping",
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
cpus: "0.3"
|
||||
reservations:
|
||||
memory: 128M
|
||||
cpus: "0.1"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
@@ -436,16 +179,6 @@ volumes:
|
||||
driver: local
|
||||
app_logs:
|
||||
driver: local
|
||||
nginx_logs:
|
||||
driver: local
|
||||
prometheus_data:
|
||||
driver: local
|
||||
grafana_data:
|
||||
driver: local
|
||||
minio_data:
|
||||
driver: local
|
||||
pgadmin_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
tyapi-network:
|
||||
|
||||
319
docs/API调用记录和钱包交易记录功能说明.md
Normal file
319
docs/API调用记录和钱包交易记录功能说明.md
Normal file
@@ -0,0 +1,319 @@
|
||||
# API调用记录和钱包交易记录功能说明
|
||||
|
||||
## 概述
|
||||
|
||||
本文档描述了新增的API调用记录和钱包交易记录功能,包括后端API接口和前端页面。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. API调用记录功能
|
||||
|
||||
#### 后端API接口
|
||||
|
||||
**获取用户API调用记录**
|
||||
- **接口地址**: `GET /api/v1/my/api-calls`
|
||||
- **认证要求**: 需要JWT认证
|
||||
- **功能**: 获取当前用户的API调用历史记录,支持分页和筛选
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"page": 1, // 页码,默认1
|
||||
"page_size": 10, // 每页数量,默认10
|
||||
"start_time": "2024-01-01 00:00:00", // 开始时间(可选)
|
||||
"end_time": "2024-01-31 23:59:59", // 结束时间(可选)
|
||||
"transaction_id": "1234567890", // 交易ID(可选)
|
||||
"product_id": "product_123", // 产品ID(可选)
|
||||
"status": "success" // 状态筛选:success/failed/pending(可选)
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取API调用记录成功",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "api_call_123",
|
||||
"access_id": "access_456",
|
||||
"user_id": "user_789",
|
||||
"product_id": "product_123",
|
||||
"product_name": "身份证识别",
|
||||
"transaction_id": "1234567890",
|
||||
"client_ip": "192.168.1.1",
|
||||
"request_params": "{\"image\": \"base64...\"}",
|
||||
"response_data": "{\"result\": \"success\"}",
|
||||
"status": "success",
|
||||
"start_at": "2024-01-15 10:30:00",
|
||||
"end_at": "2024-01-15 10:30:05",
|
||||
"cost": "0.10",
|
||||
"error_type": null,
|
||||
"error_msg": null,
|
||||
"created_at": "2024-01-15 10:30:00",
|
||||
"updated_at": "2024-01-15 10:30:05"
|
||||
}
|
||||
],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 前端页面
|
||||
|
||||
**页面路径**: `/api/usage`
|
||||
**功能特性**:
|
||||
- 显示API调用统计信息(总调用次数、成功率)
|
||||
- 支持按时间范围、交易ID、产品名称、状态筛选
|
||||
- 分页显示调用记录列表
|
||||
- 查看调用详情(请求参数、响应数据、错误信息)
|
||||
- 响应式设计,支持移动端访问
|
||||
|
||||
### 2. 钱包交易记录功能
|
||||
|
||||
#### 后端API接口
|
||||
|
||||
**获取用户钱包交易记录**
|
||||
- **接口地址**: `GET /api/v1/finance/wallet/transactions`
|
||||
- **认证要求**: 需要JWT认证
|
||||
- **功能**: 获取当前用户的钱包消费记录,支持分页和筛选
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"page": 1, // 页码,默认1
|
||||
"page_size": 10, // 每页数量,默认10
|
||||
"start_time": "2024-01-01 00:00:00", // 开始时间(可选)
|
||||
"end_time": "2024-01-31 23:59:59", // 结束时间(可选)
|
||||
"api_call_id": "api_call_123", // API调用ID(可选)
|
||||
"min_amount": "0.01", // 最小金额(可选)
|
||||
"max_amount": "100.00" // 最大金额(可选)
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取钱包交易记录成功",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": "transaction_123",
|
||||
"user_id": "user_789",
|
||||
"api_call_id": "api_call_456",
|
||||
"amount": "0.10",
|
||||
"created_at": "2024-01-15 10:30:05",
|
||||
"updated_at": "2024-01-15 10:30:05"
|
||||
}
|
||||
],
|
||||
"total": 50,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 前端页面
|
||||
|
||||
**页面路径**: `/finance/transactions`
|
||||
**功能特性**:
|
||||
- 显示消费统计信息(总消费次数、总消费金额)
|
||||
- 支持按时间范围、API调用ID、金额范围筛选
|
||||
- 分页显示交易记录列表
|
||||
- 查看交易详情
|
||||
- 响应式设计,支持移动端访问
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端架构
|
||||
|
||||
#### 1. 数据层
|
||||
- **实体**: `ApiCall`、`WalletTransaction`
|
||||
- **仓储**: `ApiCallRepository`、`WalletTransactionRepository`
|
||||
- **GORM实现**: `GormApiCallRepository`、`GormWalletTransactionRepository`
|
||||
|
||||
#### 2. 领域层
|
||||
- **聚合服务**: `ApiCallAggregateService`、`WalletAggregateService`
|
||||
- **业务逻辑**: 分页查询、条件筛选、数据统计
|
||||
|
||||
#### 3. 应用层
|
||||
- **应用服务**: `ApiApplicationService`、`FinanceApplicationService`
|
||||
- **DTO转换**: 实体到响应DTO的映射
|
||||
- **业务编排**: 调用领域服务,处理应用级逻辑
|
||||
|
||||
#### 4. 接口层
|
||||
- **HTTP处理器**: `ApiHandler`、`FinanceHandler`
|
||||
- **路由配置**: API路由和财务路由
|
||||
- **中间件**: JWT认证、请求验证、响应构建
|
||||
|
||||
### 前端架构
|
||||
|
||||
#### 1. 页面组件
|
||||
- **API调用记录**: `Usage.vue`
|
||||
- **钱包交易记录**: `Transactions.vue`
|
||||
- **通用组件**: `ListPageLayout`、`FilterSection`、`FilterItem`
|
||||
|
||||
#### 2. API接口
|
||||
- **API模块**: `apiApi.getUserApiCalls()`
|
||||
- **财务模块**: `financeApi.getUserWalletTransactions()`
|
||||
|
||||
#### 3. 功能特性
|
||||
- **筛选功能**: 时间范围、关键词搜索、状态筛选
|
||||
- **分页功能**: 支持自定义每页数量
|
||||
- **详情弹窗**: 查看完整记录信息
|
||||
- **响应式设计**: 适配不同屏幕尺寸
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### API调用记录表 (api_calls)
|
||||
```sql
|
||||
CREATE TABLE api_calls (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
access_id VARCHAR(64) NOT NULL,
|
||||
user_id VARCHAR(36),
|
||||
product_id VARCHAR(64),
|
||||
transaction_id VARCHAR(64) NOT NULL,
|
||||
client_ip VARCHAR(64) NOT NULL,
|
||||
request_params TEXT,
|
||||
response_data TEXT,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
start_at TIMESTAMP NOT NULL,
|
||||
end_at TIMESTAMP,
|
||||
cost DECIMAL(20,8),
|
||||
error_type VARCHAR(32),
|
||||
error_msg VARCHAR(256),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_transaction_id (transaction_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at)
|
||||
);
|
||||
```
|
||||
|
||||
### 钱包交易记录表 (wallet_transactions)
|
||||
```sql
|
||||
CREATE TABLE wallet_transactions (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
api_call_id VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(20,8) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_api_call_id (api_call_id),
|
||||
INDEX idx_created_at (created_at)
|
||||
);
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 获取API调用记录
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/v1/my/api-calls?page=1&page_size=10&status=success" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取API调用记录成功",
|
||||
"data": {
|
||||
"items": [...],
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取钱包交易记录
|
||||
|
||||
**请求示例**:
|
||||
```bash
|
||||
curl -X GET "http://localhost:8080/api/v1/finance/wallet/transactions?page=1&page_size=10" \
|
||||
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "获取钱包交易记录成功",
|
||||
"data": {
|
||||
"items": [...],
|
||||
"total": 50,
|
||||
"page": 1,
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误码
|
||||
- `400`: 请求参数错误
|
||||
- `401`: 未认证或认证失败
|
||||
- `500`: 服务器内部错误
|
||||
|
||||
### 错误响应格式
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "请求参数错误",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 数据库优化
|
||||
- 合理设置索引
|
||||
- 使用分页查询避免大量数据查询
|
||||
- 缓存热点数据
|
||||
|
||||
### 2. 前端优化
|
||||
- 防抖搜索,避免频繁请求
|
||||
- 分页加载,减少数据传输
|
||||
- 响应式设计,提升用户体验
|
||||
|
||||
## 安全考虑
|
||||
|
||||
### 1. 认证授权
|
||||
- 所有接口都需要JWT认证
|
||||
- 用户只能查看自己的记录
|
||||
- 接口权限验证
|
||||
|
||||
### 2. 数据安全
|
||||
- 敏感信息脱敏处理
|
||||
- SQL注入防护
|
||||
- XSS攻击防护
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 统计报表
|
||||
- 调用量趋势分析
|
||||
- 费用统计报表
|
||||
- 成功率分析
|
||||
|
||||
### 2. 导出功能
|
||||
- 支持Excel导出
|
||||
- 支持PDF报表
|
||||
- 自定义导出格式
|
||||
|
||||
### 3. 实时监控
|
||||
- WebSocket实时推送
|
||||
- 调用状态实时更新
|
||||
- 异常告警通知
|
||||
|
||||
## 总结
|
||||
|
||||
API调用记录和钱包交易记录功能为用户提供了完整的API使用追踪和费用管理能力,通过合理的架构设计和功能实现,确保了系统的可扩展性和可维护性。
|
||||
177
docs/API调用错误处理规范.md
Normal file
177
docs/API调用错误处理规范.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# API调用错误处理规范
|
||||
|
||||
## 概述
|
||||
|
||||
本文档定义了API调用接口的统一错误处理规范,包括错误码定义、响应结构、错误处理流程等。
|
||||
|
||||
## 响应结构
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "业务成功",
|
||||
"transaction_id": "api_call_123456",
|
||||
"data": "加密的响应数据"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 1001,
|
||||
"message": "接口异常",
|
||||
"transaction_id": "api_call_123456"
|
||||
}
|
||||
```
|
||||
|
||||
## 错误码定义
|
||||
|
||||
| 错误码 | 错误类型 | 说明 |
|
||||
|--------|----------|------|
|
||||
| 0 | 业务成功 | 调用成功 |
|
||||
| 1000 | 查询为空 | 查询结果为空 |
|
||||
| 1001 | 接口异常 | 系统异常、数据源异常等 |
|
||||
| 1002 | 参数解密失败 | 请求参数解密失败 |
|
||||
| 1003 | 基础参数校验不正确 | 请求参数格式错误 |
|
||||
| 1004 | 未经授权的IP | IP不在白名单内 |
|
||||
| 1005 | 缺少Access-Id | 请求头中缺少Access-Id |
|
||||
| 1006 | 未经授权的AccessId | AccessId无效或账户已冻结 |
|
||||
| 1007 | 账户余额不足,无法请求 | 钱包余额不足 |
|
||||
| 1008 | 未开通此产品 | 产品不存在、已停用或未订阅 |
|
||||
| 2001 | 业务失败 | 第三方API业务逻辑失败 |
|
||||
|
||||
## 实现架构
|
||||
|
||||
### 1. 错误类型定义
|
||||
- 位置:`internal/application/api/errors.go`
|
||||
- 功能:定义具体的错误类型常量和错误码映射
|
||||
|
||||
### 2. 响应结构定义
|
||||
- 位置:`internal/application/api/dto/api_response.go`
|
||||
- 功能:定义统一的响应结构体和工厂方法
|
||||
|
||||
### 3. 应用层错误处理
|
||||
- 位置:`internal/application/api/api_application_service.go`
|
||||
- 功能:在业务逻辑中返回具体的错误类型
|
||||
|
||||
### 4. Handler层统一处理
|
||||
- 位置:`internal/infrastructure/http/handlers/api_handler.go`
|
||||
- 功能:根据错误类型返回对应的错误码和响应
|
||||
|
||||
## 错误处理流程
|
||||
|
||||
### 1. 基础参数校验(Handler层)
|
||||
```go
|
||||
// 检查Access-Id
|
||||
accessId := c.GetHeader("Access-Id")
|
||||
if accessId == "" {
|
||||
response := dto.NewErrorResponse(1005, "缺少Access-Id", "")
|
||||
c.JSON(400, response)
|
||||
return
|
||||
}
|
||||
|
||||
// 参数绑定和校验
|
||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||
response := dto.NewErrorResponse(1003, "基础参数校验不正确", "")
|
||||
c.JSON(400, response)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 业务逻辑错误处理(应用层)
|
||||
```go
|
||||
// 用户不存在
|
||||
if err != nil {
|
||||
return ErrInvalidAccessId
|
||||
}
|
||||
|
||||
// 账户已冻结
|
||||
if apiUser.IsFrozen() {
|
||||
return ErrFrozenAccount
|
||||
}
|
||||
|
||||
// IP不在白名单
|
||||
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
||||
return ErrInvalidIP
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 统一错误响应(Handler层)
|
||||
```go
|
||||
// 调用应用服务
|
||||
transactionId, encryptedResp, err := h.appService.CallApi(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
// 根据错误类型返回对应的错误码
|
||||
errorCode := api.GetErrorCode(err)
|
||||
response := dto.NewErrorResponse(errorCode, err.Error(), transactionId)
|
||||
c.JSON(200, response) // API调用接口统一返回200状态码
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
## 中间件简化
|
||||
|
||||
原来的API认证中间件已经简化,只保留IP获取功能:
|
||||
- 认证逻辑移到Handler层处理
|
||||
- 保持中间件职责单一
|
||||
- 便于错误码的统一管理
|
||||
|
||||
## 测试
|
||||
|
||||
错误处理逻辑包含完整的单元测试:
|
||||
```bash
|
||||
go test ./internal/application/api -v
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 成功调用
|
||||
```bash
|
||||
curl -X POST "http://localhost:8080/api/v1/test_api" \
|
||||
-H "Access-Id: your_access_id" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "encrypted_request_data"}'
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "业务成功",
|
||||
"transaction_id": "api_call_123456",
|
||||
"data": "encrypted_response_data"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误调用示例
|
||||
```bash
|
||||
# 缺少Access-Id
|
||||
curl -X POST "http://localhost:8080/api/v1/test_api" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "encrypted_request_data"}'
|
||||
```
|
||||
|
||||
响应:
|
||||
```json
|
||||
{
|
||||
"code": 1005,
|
||||
"message": "缺少Access-Id",
|
||||
"transaction_id": ""
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展说明
|
||||
|
||||
1. **新增错误类型**:在`errors.go`中添加新的错误常量和错误码映射
|
||||
2. **错误码调整**:修改`ErrorCodeMap`中的映射关系
|
||||
3. **响应结构扩展**:在`api_response.go`中添加新的响应字段或方法
|
||||
4. **国际化支持**:可以在错误消息中添加多语言支持
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. API调用接口统一返回HTTP 200状态码,错误通过响应体中的code字段区分
|
||||
2. 所有错误响应都包含transaction_id,便于问题追踪
|
||||
3. 错误消息使用中文,符合项目规范
|
||||
4. 错误处理遵循DDD分层架构,错误类型定义在应用层
|
||||
5. 错误码严格按照规范定义,不能多不能少
|
||||
1039
docs/swagger/docs.go
1039
docs/swagger/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
192
docs/充值功能说明.md
Normal file
192
docs/充值功能说明.md
Normal file
@@ -0,0 +1,192 @@
|
||||
# 充值功能说明
|
||||
|
||||
## 概述
|
||||
|
||||
本系统支持三种充值方式:
|
||||
1. **支付宝充值** - 通过支付宝进行在线充值(待实现)
|
||||
2. **对公转账** - 通过银行转账进行充值
|
||||
3. **赠送充值** - 管理员为用户进行赠送充值
|
||||
|
||||
## 功能架构
|
||||
|
||||
### 实体层 (Entities)
|
||||
- `RechargeRecord` - 充值记录实体
|
||||
- `Wallet` - 钱包实体
|
||||
- `WalletTransaction` - 钱包交易记录实体
|
||||
|
||||
### 领域服务层 (Domain Services)
|
||||
- `WalletAggregateService` - 钱包聚合服务,处理充值业务逻辑
|
||||
|
||||
### 应用服务层 (Application Services)
|
||||
- `FinanceApplicationService` - 财务应用服务,协调充值操作
|
||||
|
||||
### HTTP层 (HTTP Handlers)
|
||||
- `FinanceHandler` - 财务HTTP处理器,提供REST API接口
|
||||
|
||||
## API接口
|
||||
|
||||
### 1. 对公转账充值
|
||||
|
||||
**接口地址:** `POST /api/v1/finance/wallet/transfer-recharge`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"amount": "100.00",
|
||||
"transfer_order_id": "TR202412010001",
|
||||
"bank_account": "6222021234567890123",
|
||||
"bank_name": "中国工商银行",
|
||||
"notes": "转账备注"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "对公转账充值成功",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"user_id": "user_uuid",
|
||||
"amount": "100.00",
|
||||
"recharge_type": "transfer",
|
||||
"status": "success",
|
||||
"transfer_order_id": "TR202412010001",
|
||||
"bank_account": "6222021234567890123",
|
||||
"bank_name": "中国工商银行",
|
||||
"notes": "转账备注",
|
||||
"created_at": "2024-12-01T10:00:00Z",
|
||||
"updated_at": "2024-12-01T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 赠送充值
|
||||
|
||||
**接口地址:** `POST /api/v1/finance/wallet/gift-recharge`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"amount": "50.00",
|
||||
"gift_reason": "新用户注册奖励",
|
||||
"notes": "系统自动赠送"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "赠送充值成功",
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"user_id": "user_uuid",
|
||||
"amount": "50.00",
|
||||
"recharge_type": "gift",
|
||||
"status": "success",
|
||||
"gift_reason": "新用户注册奖励",
|
||||
"operator_id": "system",
|
||||
"notes": "系统自动赠送",
|
||||
"created_at": "2024-12-01T10:00:00Z",
|
||||
"updated_at": "2024-12-01T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 通用充值(保留接口)
|
||||
|
||||
**接口地址:** `POST /api/v1/finance/wallet/recharge`
|
||||
|
||||
**请求参数:**
|
||||
```json
|
||||
{
|
||||
"amount": "200.00"
|
||||
}
|
||||
```
|
||||
|
||||
## 业务规则
|
||||
|
||||
### 对公转账充值
|
||||
1. 转账订单号必须唯一,不能重复使用
|
||||
2. 充值成功后立即更新钱包余额
|
||||
3. 充值记录状态标记为成功
|
||||
|
||||
### 赠送充值
|
||||
1. 赠送充值直接标记为成功状态
|
||||
2. 立即更新钱包余额
|
||||
3. 记录操作员ID(当前为"system")
|
||||
|
||||
### 通用规则
|
||||
1. 充值金额必须大于0
|
||||
2. 用户必须存在钱包
|
||||
3. 使用乐观锁防止并发问题
|
||||
4. 所有操作都有详细的日志记录
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### recharge_records 表
|
||||
```sql
|
||||
CREATE TABLE recharge_records (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
amount DECIMAL(20,8) NOT NULL,
|
||||
recharge_type VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
alipay_order_id VARCHAR(64),
|
||||
transfer_order_id VARCHAR(64),
|
||||
bank_account VARCHAR(100),
|
||||
bank_name VARCHAR(100),
|
||||
gift_reason VARCHAR(200),
|
||||
notes TEXT,
|
||||
operator_id VARCHAR(36),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP NULL,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_recharge_type (recharge_type),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_alipay_order_id (alipay_order_id),
|
||||
INDEX idx_transfer_order_id (transfer_order_id)
|
||||
);
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 常见错误码
|
||||
- `400` - 请求参数错误
|
||||
- `401` - 用户未登录
|
||||
- `409` - 转账订单号已存在
|
||||
- `500` - 服务器内部错误
|
||||
|
||||
### 错误消息示例
|
||||
```json
|
||||
{
|
||||
"code": 409,
|
||||
"message": "转账订单号已存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 后续开发计划
|
||||
|
||||
1. **支付宝充值功能**
|
||||
- 集成支付宝支付接口
|
||||
- 实现支付回调处理
|
||||
- 添加支付状态查询接口
|
||||
|
||||
2. **充值记录查询**
|
||||
- 添加充值记录列表查询接口
|
||||
- 支持按用户、类型、状态等条件筛选
|
||||
|
||||
3. **充值统计功能**
|
||||
- 添加充值金额统计
|
||||
- 支持按时间段统计充值情况
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有金额计算使用 `decimal.Decimal` 类型,确保精度
|
||||
2. 使用乐观锁机制防止并发充值问题
|
||||
3. 充值操作都有完整的日志记录
|
||||
4. 对公转账充值需要人工审核确认
|
||||
5. 赠送充值需要管理员权限
|
||||
302
docs/充值记录服务优化说明.md
Normal file
302
docs/充值记录服务优化说明.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 充值记录服务优化说明
|
||||
|
||||
## 优化概述
|
||||
|
||||
本次优化主要针对充值记录服务进行了两个重要改进:
|
||||
|
||||
1. **统一钱包余额更新逻辑** - 使用钱包聚合服务的充值方法
|
||||
2. **添加事务支持** - 为所有充值相关方法添加事务保护
|
||||
|
||||
## 优化内容
|
||||
|
||||
### 1. 统一钱包余额更新逻辑
|
||||
|
||||
#### 问题分析
|
||||
之前的充值记录服务直接操作钱包仓储来更新余额,存在以下问题:
|
||||
- 代码重复:钱包余额更新逻辑在多个地方重复实现
|
||||
- 维护困难:余额更新逻辑变更需要在多个地方修改
|
||||
- 一致性风险:不同服务可能使用不同的余额更新逻辑
|
||||
|
||||
#### 解决方案
|
||||
修改充值记录服务,使用钱包聚合服务的 `Recharge` 方法来更新钱包余额:
|
||||
|
||||
**修改的方法:**
|
||||
- `TransferRecharge` - 对公转账充值
|
||||
- `GiftRecharge` - 赠送充值
|
||||
- `HandleAlipayPaymentSuccess` - 支付宝支付成功回调处理
|
||||
|
||||
**修改前:**
|
||||
```go
|
||||
// 直接操作钱包仓储
|
||||
w.AddBalance(amount)
|
||||
ok, err := s.walletRepo.UpdateBalanceWithVersion(ctx, w.ID, w.Balance.String(), w.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("高并发下充值失败,请重试")
|
||||
}
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```go
|
||||
// 使用钱包聚合服务
|
||||
err = s.walletService.Recharge(ctx, userID, amount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
```
|
||||
|
||||
#### 优势
|
||||
- **代码复用**:统一使用钱包聚合服务的余额更新逻辑
|
||||
- **维护性**:余额更新逻辑变更只需在一个地方修改
|
||||
- **一致性**:确保所有充值方式使用相同的余额更新逻辑
|
||||
- **可测试性**:可以独立测试钱包聚合服务的余额更新逻辑
|
||||
|
||||
### 2. 添加事务支持
|
||||
|
||||
#### 问题分析
|
||||
充值相关方法执行多个数据库操作:
|
||||
|
||||
**TransferRecharge 方法:**
|
||||
1. 创建充值记录
|
||||
2. 更新钱包余额
|
||||
3. 更新充值记录状态为成功
|
||||
|
||||
**GiftRecharge 方法:**
|
||||
1. 创建充值记录
|
||||
2. 更新钱包余额
|
||||
|
||||
**HandleAlipayPaymentSuccess 方法:**
|
||||
1. 更新支付宝订单状态
|
||||
2. 更新充值记录状态
|
||||
3. 更新钱包余额
|
||||
|
||||
如果任何一步失败,可能导致数据不一致:
|
||||
- 充值记录已创建,但钱包余额未更新
|
||||
- 支付宝订单已更新,但充值记录未更新
|
||||
- 充值记录已更新,但钱包余额未更新
|
||||
- 重复处理导致数据异常
|
||||
|
||||
#### 解决方案
|
||||
为所有充值相关方法添加事务支持:
|
||||
|
||||
**添加事务的方法:**
|
||||
- `TransferRecharge` - 对公转账充值
|
||||
- `GiftRecharge` - 赠送充值
|
||||
- `HandleAlipayPaymentSuccess` - 支付宝支付成功回调处理
|
||||
|
||||
**修改前:**
|
||||
```go
|
||||
// 逐个执行更新操作,无事务保护
|
||||
// TransferRecharge 示例
|
||||
createdRecord, err := s.rechargeRecordRepo.Create(ctx, *rechargeRecord)
|
||||
err = s.walletService.Recharge(ctx, userID, amount)
|
||||
err = s.rechargeRecordRepo.Update(ctx, createdRecord)
|
||||
|
||||
// GiftRecharge 示例
|
||||
createdRecord, err := s.rechargeRecordRepo.Create(ctx, *rechargeRecord)
|
||||
err = s.walletService.Recharge(ctx, userID, amount)
|
||||
|
||||
// HandleAlipayPaymentSuccess 示例
|
||||
alipayOrder.MarkSuccess(tradeNo, "", "", amount, amount)
|
||||
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
||||
// ... 其他更新操作
|
||||
```
|
||||
|
||||
**修改后:**
|
||||
```go
|
||||
// 在事务中执行所有更新操作
|
||||
// TransferRecharge 示例
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createdRecord = record
|
||||
|
||||
err = s.walletService.Recharge(txCtx, userID, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createdRecord.MarkSuccess()
|
||||
err = s.rechargeRecordRepo.Update(txCtx, createdRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// GiftRecharge 示例
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createdRecord = record
|
||||
|
||||
err = s.walletService.Recharge(txCtx, userID, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// HandleAlipayPaymentSuccess 示例
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
// 更新支付宝订单状态为成功
|
||||
alipayOrder.MarkSuccess(tradeNo, "", "", amount, amount)
|
||||
err := s.alipayOrderRepo.Update(txCtx, *alipayOrder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新充值记录状态为成功
|
||||
rechargeRecord.MarkSuccess()
|
||||
err = s.rechargeRecordRepo.Update(txCtx, rechargeRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 使用钱包聚合服务更新钱包余额
|
||||
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, amount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
#### 优势
|
||||
- **数据一致性**:确保所有操作要么全部成功,要么全部回滚
|
||||
- **幂等性**:防止重复处理导致的数据不一致
|
||||
- **错误处理**:统一的错误处理和回滚机制
|
||||
- **业务完整性**:保证充值记录和钱包余额状态一致
|
||||
- **原子性**:所有相关操作作为一个原子单元执行
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 依赖注入更新
|
||||
|
||||
**充值记录服务构造函数:**
|
||||
```go
|
||||
func NewRechargeRecordService(
|
||||
rechargeRecordRepo repositories.RechargeRecordRepository,
|
||||
alipayOrderRepo repositories.AlipayOrderRepository,
|
||||
walletRepo repositories.WalletRepository,
|
||||
walletService WalletAggregateService, // 新增
|
||||
txManager *database.TransactionManager, // 新增
|
||||
logger *zap.Logger,
|
||||
) RechargeRecordService
|
||||
```
|
||||
|
||||
**服务结构体:**
|
||||
```go
|
||||
type RechargeRecordServiceImpl struct {
|
||||
rechargeRecordRepo repositories.RechargeRecordRepository
|
||||
alipayOrderRepo repositories.AlipayOrderRepository
|
||||
walletRepo repositories.WalletRepository
|
||||
walletService WalletAggregateService // 新增
|
||||
txManager *database.TransactionManager // 新增
|
||||
logger *zap.Logger
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 事务管理
|
||||
|
||||
使用 `shared/database.TransactionManager` 进行事务管理:
|
||||
- 自动事务开始和提交
|
||||
- 异常时自动回滚
|
||||
- 支持事务超时和重试
|
||||
- 提供事务统计和监控
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
- 保持原有的错误处理机制
|
||||
- 事务失败时自动回滚所有更改
|
||||
- 详细的错误日志记录
|
||||
- 幂等性检查防止重复处理
|
||||
|
||||
## 影响评估
|
||||
|
||||
### 1. 向后兼容性
|
||||
- ✅ 应用服务层接口保持不变
|
||||
- ✅ 对调用方完全透明
|
||||
- ✅ 保持原有的错误处理机制
|
||||
|
||||
### 2. 性能影响
|
||||
- ✅ 事务开销可控,仅在支付宝回调时使用
|
||||
- ✅ 减少代码重复,提高执行效率
|
||||
- ✅ 统一余额更新逻辑,减少数据库操作
|
||||
|
||||
### 3. 数据一致性
|
||||
- ✅ 显著提高数据一致性
|
||||
- ✅ 防止部分更新导致的数据不一致
|
||||
- ✅ 支持幂等性处理
|
||||
|
||||
### 4. 维护性
|
||||
- ✅ 代码更加清晰和易于维护
|
||||
- ✅ 减少重复代码
|
||||
- ✅ 统一的错误处理机制
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 单元测试
|
||||
- 测试钱包聚合服务的 `Recharge` 方法
|
||||
- 测试充值记录服务的各个方法
|
||||
- 测试事务回滚机制
|
||||
|
||||
### 2. 集成测试
|
||||
- 测试完整的支付宝充值流程
|
||||
- 测试事务失败时的回滚机制
|
||||
- 测试重复回调的处理
|
||||
|
||||
### 3. 压力测试
|
||||
- 测试高并发下的充值处理
|
||||
- 测试事务超时和重试机制
|
||||
- 测试数据库连接池的使用
|
||||
|
||||
## 监控建议
|
||||
|
||||
### 1. 业务监控
|
||||
- 监控充值成功率
|
||||
- 监控事务执行时间
|
||||
- 监控事务回滚次数
|
||||
|
||||
### 2. 技术监控
|
||||
- 监控数据库连接池使用情况
|
||||
- 监控事务超时情况
|
||||
- 监控错误率和异常情况
|
||||
|
||||
## 后续优化
|
||||
|
||||
### 1. 事件驱动
|
||||
考虑使用事件驱动架构来处理充值成功后的业务逻辑:
|
||||
- 充值成功事件
|
||||
- 余额变更事件
|
||||
- 通知事件
|
||||
|
||||
### 2. 缓存优化
|
||||
- 对频繁查询的充值记录添加缓存
|
||||
- 对钱包余额添加缓存
|
||||
- 实现缓存一致性机制
|
||||
|
||||
### 3. 异步处理
|
||||
- 将非关键操作异步化
|
||||
- 使用消息队列处理充值回调
|
||||
- 实现重试机制
|
||||
|
||||
## 总结
|
||||
|
||||
本次优化通过统一钱包余额更新逻辑和添加事务支持,显著提高了充值记录服务的:
|
||||
|
||||
1. **代码质量** - 减少重复代码,提高可维护性
|
||||
2. **数据一致性** - 确保业务操作的原子性
|
||||
3. **错误处理** - 统一的错误处理和回滚机制
|
||||
4. **可测试性** - 更好的单元测试和集成测试支持
|
||||
|
||||
这些改进为后续的功能扩展和维护奠定了良好的基础。
|
||||
210
docs/智能缓存机制说明.md
Normal file
210
docs/智能缓存机制说明.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# 智能缓存机制说明
|
||||
|
||||
## 概述
|
||||
|
||||
为了解决 `EnabledTables` 配置问题,我们实现了一个智能缓存决策机制。该机制允许您:
|
||||
|
||||
1. **精确控制缓存表**:通过 `EnabledTables` 和 `DisabledTables` 精确控制哪些表使用缓存
|
||||
2. **智能决策**:`CachedBaseRepositoryImpl` 会自动根据配置决定是否使用缓存
|
||||
3. **无需修改代码**:钱包Repository等代码无需修改,自动适应配置
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. 全局缓存配置管理器 (`CacheConfigManager`)
|
||||
|
||||
```go
|
||||
// 全局实例
|
||||
var GlobalCacheConfigManager *CacheConfigManager
|
||||
|
||||
// 主要方法
|
||||
func (m *CacheConfigManager) IsTableCacheEnabled(tableName string) bool
|
||||
func (m *CacheConfigManager) IsTableCacheDisabled(tableName string) bool
|
||||
func (m *CacheConfigManager) AddEnabledTable(tableName string)
|
||||
func (m *CacheConfigManager) AddDisabledTable(tableName string)
|
||||
```
|
||||
|
||||
### 2. 智能缓存Repository (`CachedBaseRepositoryImpl`)
|
||||
|
||||
```go
|
||||
// 智能决策方法
|
||||
func (r *CachedBaseRepositoryImpl) shouldUseCacheForTable() bool
|
||||
func (r *CachedBaseRepositoryImpl) isTableCacheEnabled() bool
|
||||
|
||||
// 智能缓存方法
|
||||
func (r *CachedBaseRepositoryImpl) GetWithCache(ctx context.Context, dest interface{}, ttl time.Duration, where string, args ...interface{}) error
|
||||
func (r *CachedBaseRepositoryImpl) FindWithCache(ctx context.Context, dest interface{}, ttl time.Duration, where string, args ...interface{}) error
|
||||
```
|
||||
|
||||
### 3. 智能GORM插件 (`GormCachePlugin`)
|
||||
|
||||
```go
|
||||
// 智能缓存决策
|
||||
func (p *GormCachePlugin) shouldCache(db *gorm.DB) bool
|
||||
func (p *GormCachePlugin) shouldInvalidateTable(table string) bool
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
### 1. 配置初始化
|
||||
|
||||
```go
|
||||
// 在 cache_setup.go 中
|
||||
cacheConfig := cache.CacheConfig{
|
||||
EnabledTables: []string{
|
||||
"users",
|
||||
"product",
|
||||
"product_category",
|
||||
// 注意:这里没有 "wallets"
|
||||
},
|
||||
DisabledTables: []string{
|
||||
"audit_logs",
|
||||
"system_logs",
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化全局管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
```
|
||||
|
||||
### 2. 智能决策流程
|
||||
|
||||
当钱包Repository调用缓存方法时:
|
||||
|
||||
```go
|
||||
// 钱包Repository代码(无需修改)
|
||||
func (r *GormWalletRepository) GetByUserID(ctx context.Context, userID string) (*Wallet, error) {
|
||||
var wallet Wallet
|
||||
err := r.CachedBaseRepositoryImpl.GetWithCache(ctx, &wallet, 5*time.Minute, "user_id = ?", userID)
|
||||
return &wallet, err
|
||||
}
|
||||
```
|
||||
|
||||
**决策流程:**
|
||||
|
||||
1. **Repository层**:调用 `GetWithCache` 方法
|
||||
2. **智能决策**:`shouldUseCacheForTable()` 检查 `wallets` 表是否启用缓存
|
||||
3. **配置检查**:通过 `GlobalCacheConfigManager.IsTableCacheEnabled("wallets")` 检查
|
||||
4. **结果处理**:
|
||||
- 如果启用缓存:设置 `cache:enabled=true`,正常使用缓存
|
||||
- 如果禁用缓存:设置 `cache:disabled=true`,跳过缓存,直接查询数据库
|
||||
|
||||
### 3. 缓存失效智能控制
|
||||
|
||||
```go
|
||||
// GORM回调中的智能失效
|
||||
func (p *GormCachePlugin) afterCreate(db *gorm.DB) {
|
||||
if !p.config.AutoInvalidate || db.Error != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 只对启用缓存的表执行失效操作
|
||||
if p.shouldInvalidateTable(db.Statement.Table) {
|
||||
p.invalidateTableCache(db.Statement.Context, db.Statement.Table)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景1:钱包表不启用缓存
|
||||
|
||||
```go
|
||||
// 配置中不包含 wallets
|
||||
EnabledTables: []string{
|
||||
"users",
|
||||
"product",
|
||||
// 没有 "wallets"
|
||||
}
|
||||
|
||||
// 结果:钱包查询不使用缓存,直接查询数据库
|
||||
// 不会触发缓存失效操作,避免 "context canceled" 错误
|
||||
```
|
||||
|
||||
### 场景2:钱包表启用缓存
|
||||
|
||||
```go
|
||||
// 配置中包含 wallets
|
||||
EnabledTables: []string{
|
||||
"users",
|
||||
"product",
|
||||
"wallets", // 添加钱包表
|
||||
}
|
||||
|
||||
// 结果:钱包查询正常使用缓存
|
||||
// 缓存失效操作正常执行
|
||||
```
|
||||
|
||||
### 场景3:动态调整
|
||||
|
||||
```go
|
||||
// 运行时动态启用钱包缓存
|
||||
cache.GlobalCacheConfigManager.AddEnabledTable("wallets")
|
||||
|
||||
// 运行时动态禁用钱包缓存
|
||||
cache.GlobalCacheConfigManager.AddDisabledTable("wallets")
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
### 1. **精确控制**
|
||||
- 通过配置精确控制哪些表使用缓存
|
||||
- 避免对不需要缓存的表执行无效操作
|
||||
|
||||
### 2. **代码无需修改**
|
||||
- Repository层代码无需修改
|
||||
- 自动适应配置变化
|
||||
|
||||
### 3. **性能优化**
|
||||
- 避免对未启用缓存的表执行缓存失效操作
|
||||
- 减少Redis无效操作,提高性能
|
||||
|
||||
### 4. **错误消除**
|
||||
- 彻底解决 `"context canceled"` 错误
|
||||
- 避免对不存在缓存数据的表执行失效操作
|
||||
|
||||
### 5. **灵活配置**
|
||||
- 支持运行时动态调整
|
||||
- 支持环境级别的配置差异
|
||||
|
||||
## 监控和调试
|
||||
|
||||
### 1. 查看表缓存状态
|
||||
|
||||
```go
|
||||
// 获取单个表状态
|
||||
status := cache.GlobalCacheConfigManager.GetTableCacheStatus("wallets")
|
||||
// 返回:{"table_name": "wallets", "enabled": false, "disabled": true, ...}
|
||||
|
||||
// 获取所有表状态
|
||||
allStatus := cache.GlobalCacheConfigManager.GetAllTableStatus()
|
||||
```
|
||||
|
||||
### 2. 日志监控
|
||||
|
||||
```go
|
||||
// 启用缓存的查询
|
||||
"执行带缓存查询" {"table": "users", "ttl": "5m0s", "where": "id = ?"}
|
||||
|
||||
// 禁用缓存的查询
|
||||
"执行无缓存查询" {"table": "wallets", "where": "user_id = ?"}
|
||||
|
||||
// 表未启用缓存的提示
|
||||
"表未启用缓存,跳过缓存操作" {"table": "wallets"}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. **配置原则**
|
||||
- 只对查询频繁、数据变化不频繁的表启用缓存
|
||||
- 对实时性要求高的表(如钱包余额)谨慎使用缓存
|
||||
- 对日志表等大数据量表禁用缓存
|
||||
|
||||
### 2. **性能考虑**
|
||||
- 合理设置TTL,平衡缓存命中率和数据一致性
|
||||
- 监控缓存命中率和失效频率
|
||||
- 根据业务特点调整缓存策略
|
||||
|
||||
### 3. **运维建议**
|
||||
- 定期检查缓存配置的合理性
|
||||
- 监控Redis内存使用情况
|
||||
- 根据业务增长调整缓存容量
|
||||
138
docs/服务重构说明.md
Normal file
138
docs/服务重构说明.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# 钱包聚合服务重构说明
|
||||
|
||||
## 重构概述
|
||||
|
||||
本次重构将钱包聚合服务中的充值记录相关功能提取到独立的充值记录服务中,实现了更好的职责分离和代码组织。
|
||||
|
||||
## 重构内容
|
||||
|
||||
### 1. 新增服务
|
||||
|
||||
#### RechargeRecordService (充值记录服务)
|
||||
- **文件位置**: `internal/domains/finance/services/recharge_record_service.go`
|
||||
- **职责**: 专门处理所有充值记录相关的业务逻辑
|
||||
|
||||
**主要功能**:
|
||||
- 对公转账充值 (`TransferRecharge`)
|
||||
- 赠送充值 (`GiftRecharge`)
|
||||
- 支付宝充值记录创建 (`CreateAlipayRecharge`)
|
||||
- 支付宝订单管理 (`CreateAlipayOrder`)
|
||||
- 支付宝支付成功回调处理 (`HandleAlipayPaymentSuccess`)
|
||||
- 充值记录查询 (`GetByID`, `GetByUserID`, `GetByTransferOrderID`)
|
||||
|
||||
### 2. 重构的服务
|
||||
|
||||
#### WalletAggregateService (钱包聚合服务)
|
||||
- **文件位置**: `internal/domains/finance/services/wallet_aggregate_service.go`
|
||||
- **重构后职责**: 专注于钱包核心功能
|
||||
|
||||
**保留功能**:
|
||||
- 钱包创建 (`CreateWallet`)
|
||||
- 通用充值 (`Recharge`)
|
||||
- 扣款 (`Deduct`)
|
||||
- 余额查询 (`GetBalance`)
|
||||
- 钱包加载 (`LoadWalletByUserId`)
|
||||
|
||||
**移除功能**:
|
||||
- 对公转账充值
|
||||
- 赠送充值
|
||||
- 支付宝充值记录管理
|
||||
- 支付宝订单管理
|
||||
- 支付宝回调处理
|
||||
|
||||
### 3. 应用服务层更新
|
||||
|
||||
#### FinanceApplicationService
|
||||
- **文件位置**: `internal/application/finance/finance_application_service_impl.go`
|
||||
- **更新内容**: 添加对 `RechargeRecordService` 的依赖
|
||||
|
||||
**修改的方法**:
|
||||
- `TransferRecharge`: 改为调用 `rechargeRecordService.TransferRecharge`
|
||||
- `GiftRecharge`: 改为调用 `rechargeRecordService.GiftRecharge`
|
||||
- `CreateAlipayRechargeOrder`: 改为调用 `rechargeRecordService.CreateAlipayRecharge`
|
||||
- `CreateAlipayOrderRecord`: 改为调用 `rechargeRecordService.CreateAlipayOrder`
|
||||
- `GetRechargeRecordByAlipayOrderID`: 改为调用 `rechargeRecordService.GetRechargeRecordByAlipayOrderID`
|
||||
- `HandleAlipayPaymentSuccess`: 改为调用 `rechargeRecordService.HandleAlipayPaymentSuccess`
|
||||
|
||||
### 4. 依赖注入更新
|
||||
|
||||
#### Container
|
||||
- **文件位置**: `internal/container/container.go`
|
||||
- **更新内容**: 添加 `finance_service.NewRechargeRecordService` 的依赖注入
|
||||
|
||||
## 重构优势
|
||||
|
||||
### 1. 职责分离
|
||||
- **钱包聚合服务**: 专注于钱包核心功能(创建、充值、扣款、查询)
|
||||
- **充值记录服务**: 专注于充值记录管理(各种充值方式、订单管理)
|
||||
|
||||
### 2. 代码组织
|
||||
- 相关功能聚合在一起,提高代码可读性
|
||||
- 减少单个服务的复杂度
|
||||
- 便于维护和测试
|
||||
|
||||
### 3. 扩展性
|
||||
- 新增充值方式时,只需要在充值记录服务中添加
|
||||
- 钱包核心功能与充值方式解耦
|
||||
|
||||
### 4. 测试友好
|
||||
- 可以独立测试钱包功能和充值记录功能
|
||||
- 减少测试的复杂度
|
||||
|
||||
## 接口变更
|
||||
|
||||
### 钱包聚合服务接口变更
|
||||
```go
|
||||
// 移除的方法
|
||||
TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error)
|
||||
GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, giftReason, operatorID, notes string) (*entities.RechargeRecord, error)
|
||||
CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error)
|
||||
CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error
|
||||
GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error)
|
||||
HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo string) error
|
||||
|
||||
// 保留的方法
|
||||
CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error)
|
||||
Recharge(ctx context.Context, userID string, amount decimal.Decimal) error
|
||||
Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error
|
||||
GetBalance(ctx context.Context, userID string) (decimal.Decimal, error)
|
||||
LoadWalletByUserId(ctx context.Context, userID string) (*entities.Wallet, error)
|
||||
```
|
||||
|
||||
### 新增充值记录服务接口
|
||||
```go
|
||||
type RechargeRecordService interface {
|
||||
// 对公转账充值
|
||||
TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error)
|
||||
|
||||
// 赠送充值
|
||||
GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, giftReason, operatorID, notes string) (*entities.RechargeRecord, error)
|
||||
|
||||
// 支付宝充值
|
||||
CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error)
|
||||
GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error)
|
||||
|
||||
// 支付宝订单管理
|
||||
CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error
|
||||
HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo string) error
|
||||
|
||||
// 通用查询
|
||||
GetByID(ctx context.Context, id string) (*entities.RechargeRecord, error)
|
||||
GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error)
|
||||
GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error)
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **向后兼容性**: 应用服务层的公共接口保持不变,对调用方透明
|
||||
2. **事务处理**: 充值记录服务中的方法仍然保持原有的事务处理逻辑
|
||||
3. **错误处理**: 保持原有的错误处理机制和日志记录
|
||||
4. **依赖关系**: 充值记录服务依赖钱包仓储来更新钱包余额
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **实体扩展**: 考虑在 `RechargeRecord` 实体中添加 `GiftReason` 和 `OperatorID` 字段
|
||||
2. **事件驱动**: 考虑使用事件驱动架构来处理充值成功后的业务逻辑
|
||||
3. **缓存优化**: 对频繁查询的充值记录添加缓存机制
|
||||
4. **监控指标**: 添加充值相关的业务监控指标
|
||||
117
docs/钱包交易记录功能说明.md
Normal file
117
docs/钱包交易记录功能说明.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 钱包扣款记录功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
为钱包系统添加了简单的扣款记录功能,记录API调用产生的扣款操作,包含扣款金额、用户ID和关联的ApiCallID。
|
||||
|
||||
## 新增功能
|
||||
|
||||
### 1. 钱包扣款记录实体 (WalletTransaction)
|
||||
|
||||
**位置**: `internal/domains/finance/entities/wallet_transaction.go`
|
||||
|
||||
**主要字段**:
|
||||
- `ID`: 扣款记录唯一标识
|
||||
- `UserID`: 扣款用户ID
|
||||
- `ApiCallID`: 关联API调用ID
|
||||
- `Amount`: 扣款金额
|
||||
- `CreatedAt`: 创建时间
|
||||
|
||||
### 2. 钱包扣款记录仓储
|
||||
|
||||
**接口**: `internal/domains/finance/repositories/wallet_transaction_repository_interface.go`
|
||||
**实现**: `internal/infrastructure/database/repositories/finance/gorm_wallet_transaction_repository.go`
|
||||
|
||||
**主要方法**:
|
||||
- `Create`: 创建扣款记录
|
||||
- `GetByUserID`: 根据用户ID获取扣款记录
|
||||
- `GetByApiCallID`: 根据API调用ID获取扣款记录
|
||||
|
||||
### 3. 钱包聚合服务更新
|
||||
|
||||
**位置**: `internal/domains/finance/services/wallet_aggregate_service.go`
|
||||
|
||||
**更新内容**:
|
||||
- 添加了交易记录仓储依赖
|
||||
- 更新了`Deduct`方法,支持记录扣款并关联ApiCall
|
||||
|
||||
**方法签名**:
|
||||
```go
|
||||
// 扣款方法
|
||||
Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID string) error
|
||||
```
|
||||
|
||||
### 4. API应用服务更新
|
||||
|
||||
**位置**: `internal/application/api/api_application_service.go`
|
||||
|
||||
**更新内容**:
|
||||
- 更新了API调用中的扣款逻辑,现在会记录扣款并关联ApiCall
|
||||
|
||||
## 数据库变更
|
||||
|
||||
### 新增表: wallet_transactions
|
||||
|
||||
```sql
|
||||
CREATE TABLE wallet_transactions (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
user_id VARCHAR(36) NOT NULL,
|
||||
api_call_id VARCHAR(64) NOT NULL,
|
||||
amount DECIMAL(20,8) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL,
|
||||
deleted_at TIMESTAMP,
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_api_call_id (api_call_id),
|
||||
INDEX idx_created_at (created_at)
|
||||
);
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### API调用扣款
|
||||
|
||||
当用户调用API时,系统会自动:
|
||||
1. 验证用户钱包余额
|
||||
2. 扣减相应费用
|
||||
3. 创建扣款记录,关联ApiCall
|
||||
|
||||
```go
|
||||
// 在API应用服务中
|
||||
err = s.walletService.Deduct(ctx, apiUser.UserId, subscription.Price, apiCall.ID)
|
||||
```
|
||||
|
||||
### 查询扣款记录
|
||||
|
||||
```go
|
||||
// 查询用户扣款记录
|
||||
transactions, err := transactionRepo.GetByUserID(ctx, userID, 10, 0)
|
||||
|
||||
// 根据API调用ID查询扣款记录
|
||||
transaction, err := transactionRepo.GetByApiCallID(ctx, apiCallID)
|
||||
```
|
||||
|
||||
## 依赖注入配置
|
||||
|
||||
在容器配置中已添加钱包扣款记录仓储的依赖注入:
|
||||
|
||||
```go
|
||||
// 钱包扣款记录仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormWalletTransactionRepository,
|
||||
fx.As(new(domain_finance_repo.WalletTransactionRepository)),
|
||||
),
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **简单实用**: 只记录必要的信息
|
||||
2. **API调用关联**: 可以追踪每个API调用产生的费用
|
||||
3. **用户追踪**: 可以查看用户的扣款历史
|
||||
4. **轻量级**: 不增加系统复杂度
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 扣款记录创建失败不会影响钱包余额更新,确保核心业务不受影响
|
||||
2. 所有金额计算使用decimal类型,确保精度
|
||||
3. 支持软删除,便于数据恢复
|
||||
@@ -1,456 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/domains/user/repositories/queries"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ModernCachedUserRepository 现代化的用户仓储(使用新缓存方案)
|
||||
type ModernCachedUserRepository struct {
|
||||
*database.CachedBaseRepositoryImpl
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.UserRepository = (*ModernCachedUserRepository)(nil)
|
||||
|
||||
// NewModernCachedUserRepository 创建现代化缓存用户仓储
|
||||
func NewModernCachedUserRepository(db *gorm.DB, logger *zap.Logger) repositories.UserRepository {
|
||||
return &ModernCachedUserRepository{
|
||||
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, "users"),
|
||||
}
|
||||
}
|
||||
|
||||
// ================ Repository[T] 接口实现 ================
|
||||
|
||||
// Create 创建用户(自动失效相关缓存)
|
||||
func (r *ModernCachedUserRepository) Create(ctx context.Context, user entities.User) (entities.User, error) {
|
||||
r.GetLogger().Info("创建用户", zap.String("phone", user.Phone))
|
||||
|
||||
// 使用基础创建方法,GORM插件会自动处理缓存失效
|
||||
err := r.CreateEntity(ctx, &user)
|
||||
return user, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户(自动缓存30分钟)
|
||||
func (r *ModernCachedUserRepository) GetByID(ctx context.Context, id string) (entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
// 使用智能缓存查询,自动缓存30分钟
|
||||
err := r.SmartGetByID(ctx, id, &user)
|
||||
return user, err
|
||||
}
|
||||
|
||||
// Update 更新用户(自动失效相关缓存)
|
||||
func (r *ModernCachedUserRepository) Update(ctx context.Context, user entities.User) error {
|
||||
r.GetLogger().Info("更新用户", zap.String("user_id", user.ID))
|
||||
|
||||
// 使用基础更新方法,GORM插件会自动处理缓存失效
|
||||
return r.UpdateEntity(ctx, &user)
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建用户
|
||||
func (r *ModernCachedUserRepository) CreateBatch(ctx context.Context, users []entities.User) error {
|
||||
r.GetLogger().Info("批量创建用户", zap.Int("count", len(users)))
|
||||
return r.CreateBatchEntity(ctx, &users)
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取用户(带缓存)
|
||||
func (r *ModernCachedUserRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 使用批量缓存查询,缓存15分钟
|
||||
err := r.BatchGetWithCache(ctx, ids, &users, 15*time.Minute)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新用户
|
||||
func (r *ModernCachedUserRepository) UpdateBatch(ctx context.Context, users []entities.User) error {
|
||||
r.GetLogger().Info("批量更新用户", zap.Int("count", len(users)))
|
||||
return r.UpdateBatchEntity(ctx, &users)
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除用户
|
||||
func (r *ModernCachedUserRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.GetLogger().Info("批量删除用户", zap.Strings("ids", ids))
|
||||
return r.DeleteBatchEntity(ctx, ids, &entities.User{})
|
||||
}
|
||||
|
||||
// List 获取用户列表(智能缓存)
|
||||
func (r *ModernCachedUserRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 使用智能列表查询,根据查询复杂度自动选择缓存策略
|
||||
err := r.SmartList(ctx, &users, options)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// ================ BaseRepository 接口实现 ================
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *ModernCachedUserRepository) Delete(ctx context.Context, id string) error {
|
||||
return r.DeleteEntity(ctx, id, &entities.User{})
|
||||
}
|
||||
|
||||
// Exists 检查用户是否存在(带缓存)
|
||||
func (r *ModernCachedUserRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
return r.ExistsEntity(ctx, id, &entities.User{})
|
||||
}
|
||||
|
||||
// Count 统计用户数量(智能缓存)
|
||||
func (r *ModernCachedUserRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
|
||||
// 计算缓存TTL
|
||||
cacheTTL := 10 * time.Minute
|
||||
if options.Search != "" {
|
||||
cacheTTL = 2 * time.Minute // 搜索查询缓存时间更短
|
||||
}
|
||||
|
||||
err := r.CountWithCache(ctx, &count, cacheTTL, &entities.User{}, "", nil)
|
||||
return count, err
|
||||
}
|
||||
|
||||
// SoftDelete 软删除用户
|
||||
func (r *ModernCachedUserRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.SoftDeleteEntity(ctx, id, &entities.User{})
|
||||
}
|
||||
|
||||
// Restore 恢复用户
|
||||
func (r *ModernCachedUserRepository) Restore(ctx context.Context, id string) error {
|
||||
return r.RestoreEntity(ctx, id, &entities.User{})
|
||||
}
|
||||
|
||||
// ================ 业务专用方法(使用新缓存API) ================
|
||||
|
||||
// GetByPhone 根据手机号获取用户(缓存15分钟)
|
||||
func (r *ModernCachedUserRepository) GetByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
// 使用智能字段查询,自动缓存15分钟
|
||||
err := r.SmartGetByField(ctx, &user, "phone", phone, 15*time.Minute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取用户(缓存15分钟)
|
||||
func (r *ModernCachedUserRepository) GetByUsername(ctx context.Context, username string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
err := r.SmartGetByField(ctx, &user, "username", username, 15*time.Minute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetByUserType 根据用户类型获取用户列表
|
||||
func (r *ModernCachedUserRepository) GetByUserType(ctx context.Context, userType string) ([]*entities.User, error) {
|
||||
var users []*entities.User
|
||||
|
||||
err := r.FindWithCache(ctx, &users, 30*time.Minute, "user_type = ?", userType)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表(带分页和筛选)
|
||||
func (r *ModernCachedUserRepository) ListUsers(ctx context.Context, query *queries.ListUsersQuery) ([]*entities.User, int64, error) {
|
||||
var users []*entities.User
|
||||
var total int64
|
||||
|
||||
// 构建查询条件
|
||||
db := r.GetDB(ctx).Set("cache:enabled", true).Set("cache:ttl", 15*time.Minute)
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Phone != "" {
|
||||
db = db.Where("phone LIKE ?", "%"+query.Phone+"%")
|
||||
}
|
||||
if query.StartDate != "" {
|
||||
db = db.Where("created_at >= ?", query.StartDate)
|
||||
}
|
||||
if query.EndDate != "" {
|
||||
db = db.Where("created_at <= ?", query.EndDate)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := db.Model(&entities.User{}).Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
if err := db.Offset(offset).Limit(query.PageSize).Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户登录
|
||||
func (r *ModernCachedUserRepository) ValidateUser(ctx context.Context, phone, password string) (*entities.User, error) {
|
||||
// 登录验证不使用缓存,确保安全性
|
||||
var user entities.User
|
||||
db := r.WithoutCache().GetDB(ctx)
|
||||
|
||||
err := db.Where("phone = ? AND password = ?", phone, password).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpdateLastLogin 更新最后登录时间
|
||||
func (r *ModernCachedUserRepository) UpdateLastLogin(ctx context.Context, userID string) error {
|
||||
now := time.Now()
|
||||
return r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Updates(map[string]interface{}{
|
||||
"last_login_at": &now,
|
||||
"updated_at": now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// UpdatePassword 更新密码
|
||||
func (r *ModernCachedUserRepository) UpdatePassword(ctx context.Context, userID string, newPassword string) error {
|
||||
return r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("password", newPassword).Error
|
||||
}
|
||||
|
||||
// CheckPassword 检查密码
|
||||
func (r *ModernCachedUserRepository) CheckPassword(ctx context.Context, userID string, password string) (bool, error) {
|
||||
var count int64
|
||||
err := r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ? AND password = ?", userID, password).
|
||||
Count(&count).Error
|
||||
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// ActivateUser 激活用户
|
||||
func (r *ModernCachedUserRepository) ActivateUser(ctx context.Context, userID string) error {
|
||||
return r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("active", true).Error
|
||||
}
|
||||
|
||||
// DeactivateUser 停用用户
|
||||
func (r *ModernCachedUserRepository) DeactivateUser(ctx context.Context, userID string) error {
|
||||
return r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("active", false).Error
|
||||
}
|
||||
|
||||
// UpdateLoginStats 更新登录统计
|
||||
func (r *ModernCachedUserRepository) UpdateLoginStats(ctx context.Context, userID string) error {
|
||||
return r.GetDB(ctx).Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Updates(map[string]interface{}{
|
||||
"login_count": gorm.Expr("login_count + 1"),
|
||||
"last_login_at": time.Now(),
|
||||
}).Error
|
||||
}
|
||||
|
||||
// GetStats 获取用户统计信息
|
||||
func (r *ModernCachedUserRepository) GetStats(ctx context.Context) (*repositories.UserStats, error) {
|
||||
var stats repositories.UserStats
|
||||
|
||||
// 使用短期缓存获取统计信息
|
||||
db := r.GetDB(ctx).Set("cache:enabled", true).Set("cache:ttl", 5*time.Minute)
|
||||
|
||||
// 总用户数
|
||||
if err := db.Model(&entities.User{}).Count(&stats.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 活跃用户数
|
||||
if err := db.Model(&entities.User{}).Where("active = ?", true).Count(&stats.ActiveUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日注册数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := db.Model(&entities.User{}).Where("created_at >= ?", today).Count(&stats.TodayRegistrations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日登录数
|
||||
if err := db.Model(&entities.User{}).Where("last_login_at >= ?", today).Count(&stats.TodayLogins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetStatsByDateRange 获取指定日期范围的用户统计
|
||||
func (r *ModernCachedUserRepository) GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*repositories.UserStats, error) {
|
||||
var stats repositories.UserStats
|
||||
|
||||
db := r.GetDB(ctx).Set("cache:enabled", true).Set("cache:ttl", 10*time.Minute)
|
||||
|
||||
// 指定时间范围内的注册数
|
||||
if err := db.Model(&entities.User{}).
|
||||
Where("created_at >= ? AND created_at <= ?", startDate, endDate).
|
||||
Count(&stats.TodayRegistrations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 指定时间范围内的登录数
|
||||
if err := db.Model(&entities.User{}).
|
||||
Where("last_login_at >= ? AND last_login_at <= ?", startDate, endDate).
|
||||
Count(&stats.TodayLogins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetActiveUsers 获取活跃用户(使用短期缓存)
|
||||
func (r *ModernCachedUserRepository) GetActiveUsers(ctx context.Context) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 活跃用户查询使用短期缓存(5分钟)
|
||||
err := r.WithShortCache().FindWithCache(ctx, &users, 5*time.Minute, "active = ?", true)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// GetUsersByType 根据用户类型获取用户(使用中期缓存)
|
||||
func (r *ModernCachedUserRepository) GetUsersByType(ctx context.Context, userType string) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 用户类型查询使用中期缓存(30分钟)
|
||||
err := r.WithMediumCache().FindWithCache(ctx, &users, 30*time.Minute, "user_type = ?", userType)
|
||||
return users, err
|
||||
}
|
||||
|
||||
// GetRecentUsers 获取最近注册用户(禁用缓存,实时数据)
|
||||
func (r *ModernCachedUserRepository) GetRecentUsers(ctx context.Context, limit int) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 最近用户查询禁用缓存,保证数据实时性
|
||||
db := r.WithoutCache().GetDB(ctx)
|
||||
err := db.Order("created_at DESC").Limit(limit).Find(&users).Error
|
||||
|
||||
return users, err
|
||||
}
|
||||
|
||||
// GetPopularUsers 获取热门用户(使用长期缓存)
|
||||
func (r *ModernCachedUserRepository) GetPopularUsers(ctx context.Context, limit int) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 热门用户查询使用长期缓存(2小时)
|
||||
err := r.WithLongCache().GetDB(ctx).
|
||||
Order("login_count DESC").
|
||||
Limit(limit).
|
||||
Find(&users).Error
|
||||
|
||||
return users, err
|
||||
}
|
||||
|
||||
// SearchUsers 搜索用户(智能缓存策略)
|
||||
func (r *ModernCachedUserRepository) SearchUsers(ctx context.Context, keyword string, limit int) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
// 搜索查询使用短期缓存,避免频繁的数据库查询
|
||||
db := r.GetDB(ctx).
|
||||
Set("cache:enabled", true).
|
||||
Set("cache:ttl", 2*time.Minute). // 搜索结果缓存2分钟
|
||||
Where("username LIKE ? OR phone LIKE ?", "%"+keyword+"%", "%"+keyword+"%").
|
||||
Limit(limit)
|
||||
|
||||
err := db.Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
// ================ 缓存管理方法 ================
|
||||
|
||||
// WarmupUserCache 预热用户缓存
|
||||
func (r *ModernCachedUserRepository) WarmupUserCache(ctx context.Context) error {
|
||||
r.GetLogger().Info("开始预热用户缓存")
|
||||
|
||||
// 定义预热查询
|
||||
queries := []database.WarmupQuery{
|
||||
{
|
||||
Name: "active_users",
|
||||
TTL: 30 * time.Minute,
|
||||
Dest: &[]entities.User{},
|
||||
},
|
||||
{
|
||||
Name: "user_types",
|
||||
TTL: 60 * time.Minute,
|
||||
Dest: &[]entities.User{},
|
||||
},
|
||||
{
|
||||
Name: "recent_users",
|
||||
TTL: 10 * time.Minute,
|
||||
Dest: &[]entities.User{},
|
||||
},
|
||||
}
|
||||
|
||||
return r.WarmupCommonQueries(ctx, queries)
|
||||
}
|
||||
|
||||
// RefreshUserCache 刷新用户缓存
|
||||
func (r *ModernCachedUserRepository) RefreshUserCache(ctx context.Context) error {
|
||||
r.GetLogger().Info("刷新用户缓存")
|
||||
|
||||
// 刷新用户相关的所有缓存
|
||||
return r.RefreshCache(ctx, "users:*")
|
||||
}
|
||||
|
||||
// GetUserCacheStats 获取用户缓存统计
|
||||
func (r *ModernCachedUserRepository) GetUserCacheStats() map[string]interface{} {
|
||||
stats := r.GetCacheInfo()
|
||||
stats["specific_patterns"] = []string{
|
||||
"gorm_cache:users:*",
|
||||
"user:id:*",
|
||||
"user:phone:*",
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// ================ 使用示例 ================
|
||||
|
||||
// ExampleUsage 使用示例
|
||||
func (r *ModernCachedUserRepository) ExampleUsage(ctx context.Context) {
|
||||
// 1. 基础查询(自动缓存)
|
||||
user, _ := r.GetByID(ctx, "user-123")
|
||||
r.GetLogger().Info("获取用户", zap.String("username", user.Username))
|
||||
|
||||
// 2. 手动控制缓存
|
||||
// 使用短期缓存查询
|
||||
var activeUsers []entities.User
|
||||
_ = r.WithShortCache().FindWithCache(ctx, &activeUsers, 5*time.Minute, "active = ?", true)
|
||||
r.GetLogger().Info("活跃用户数", zap.Int("count", len(activeUsers)))
|
||||
|
||||
// 禁用缓存查询
|
||||
var recentUsers []entities.User
|
||||
_ = r.WithoutCache().FindWhere(ctx, &recentUsers, "created_at > ?", time.Now().AddDate(0, 0, -7))
|
||||
r.GetLogger().Info("最近用户数", zap.Int("count", len(recentUsers)))
|
||||
|
||||
// 3. 智能缓存查询
|
||||
options := interfaces.ListOptions{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
Filters: map[string]interface{}{"active": true},
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
}
|
||||
users, _ := r.List(ctx, options) // 自动根据查询复杂度选择缓存策略
|
||||
r.GetLogger().Info("用户列表", zap.Int("count", len(users)))
|
||||
|
||||
// 4. 缓存预热
|
||||
r.WarmupUserCache(ctx)
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"tyapi-server/internal/shared/validator"
|
||||
)
|
||||
|
||||
// UserRegistrationData 用户注册数据示例
|
||||
type UserRegistrationData struct {
|
||||
Phone string `validate:"required,phone"`
|
||||
Password string `validate:"required,strong_password"`
|
||||
ConfirmPassword string `validate:"required,eqfield=Password"`
|
||||
Email string `validate:"omitempty,email"`
|
||||
Username string `validate:"required,username"`
|
||||
}
|
||||
|
||||
// ProductData 产品数据示例
|
||||
type ProductData struct {
|
||||
Name string `validate:"required,min=2,max=100"`
|
||||
Code string `validate:"required,product_code"`
|
||||
Price float64 `validate:"price,min=0"`
|
||||
CategoryID string `validate:"required,uuid"`
|
||||
Description string `validate:"omitempty,max=500"`
|
||||
}
|
||||
|
||||
// ValidatorUsageExample 验证器使用示例
|
||||
func ValidatorUsageExample() {
|
||||
// 创建业务验证器实例
|
||||
bv := validator.NewBusinessValidator()
|
||||
|
||||
fmt.Println("=== 验证器使用示例 ===")
|
||||
|
||||
// 1. 使用结构体验证
|
||||
fmt.Println("\n1. 结构体验证示例:")
|
||||
userData := UserRegistrationData{
|
||||
Phone: "13800138000",
|
||||
Password: "Password123",
|
||||
ConfirmPassword: "Password123",
|
||||
Email: "user@example.com",
|
||||
Username: "testuser",
|
||||
}
|
||||
|
||||
if err := bv.ValidateStruct(userData); err != nil {
|
||||
fmt.Printf("验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("用户数据验证通过")
|
||||
}
|
||||
|
||||
// 2. 使用单个字段验证
|
||||
fmt.Println("\n2. 单个字段验证示例:")
|
||||
|
||||
// 验证手机号
|
||||
if err := bv.ValidatePhone("13800138000"); err != nil {
|
||||
fmt.Printf("手机号验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("手机号验证通过")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if err := bv.ValidatePassword("Password123"); err != nil {
|
||||
fmt.Printf("密码验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("密码验证通过")
|
||||
}
|
||||
|
||||
// 验证统一社会信用代码
|
||||
if err := bv.ValidateSocialCreditCode("91110000123456789X"); err != nil {
|
||||
fmt.Printf("统一社会信用代码验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("统一社会信用代码验证通过")
|
||||
}
|
||||
|
||||
// 验证身份证号
|
||||
if err := bv.ValidateIDCard("110101199001011234"); err != nil {
|
||||
fmt.Printf("身份证号验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("身份证号验证通过")
|
||||
}
|
||||
|
||||
// 验证URL
|
||||
if err := bv.ValidateURL("https://www.example.com"); err != nil {
|
||||
fmt.Printf("URL验证失败: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("URL验证通过")
|
||||
}
|
||||
|
||||
// 3. 验证失败示例
|
||||
fmt.Println("\n3. 验证失败示例:")
|
||||
|
||||
invalidUserData := UserRegistrationData{
|
||||
Phone: "invalid_phone",
|
||||
Password: "weak",
|
||||
ConfirmPassword: "different",
|
||||
Email: "invalid_email",
|
||||
Username: "123invalid", // 数字开头
|
||||
}
|
||||
|
||||
if err := bv.ValidateStruct(invalidUserData); err != nil {
|
||||
fmt.Printf("预期的验证失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 4. 业务逻辑中的验证示例
|
||||
fmt.Println("\n4. 业务逻辑验证示例:")
|
||||
businessValidationExample(bv)
|
||||
}
|
||||
|
||||
// businessValidationExample 业务逻辑验证示例
|
||||
func businessValidationExample(bv *validator.BusinessValidator) {
|
||||
// 模拟用户注册业务逻辑
|
||||
registerUser := func(phone, password, confirmPassword string) error {
|
||||
// 验证手机号
|
||||
if err := bv.ValidatePhone(phone); err != nil {
|
||||
return fmt.Errorf("手机号验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证密码强度
|
||||
if err := bv.ValidatePassword(password); err != nil {
|
||||
return fmt.Errorf("密码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证密码确认
|
||||
if password != confirmPassword {
|
||||
return fmt.Errorf("两次输入的密码不一致")
|
||||
}
|
||||
|
||||
// 验证字符串长度
|
||||
if err := bv.ValidateStringLength(phone, "手机号", 11, 11); err != nil {
|
||||
return fmt.Errorf("手机号长度验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 这里可以添加更多业务逻辑验证...
|
||||
|
||||
fmt.Println("用户注册验证通过,可以继续业务逻辑")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试用户注册
|
||||
if err := registerUser("13800138000", "Password123", "Password123"); err != nil {
|
||||
fmt.Printf("用户注册失败: %v\n", err)
|
||||
}
|
||||
|
||||
// 模拟产品创建业务逻辑
|
||||
createProduct := func(name, code string, price float64) error {
|
||||
// 验证必填字段
|
||||
if err := bv.ValidateRequired(name, "产品名称"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证产品代码
|
||||
if err := bv.ValidateProductCode(code); err != nil {
|
||||
return fmt.Errorf("产品代码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证价格
|
||||
if err := bv.ValidatePrice(price); err != nil {
|
||||
return fmt.Errorf("价格验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证字符串长度
|
||||
if err := bv.ValidateStringLength(name, "产品名称", 2, 100); err != nil {
|
||||
return fmt.Errorf("产品名称长度验证失败: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("产品创建验证通过,可以继续业务逻辑")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 测试产品创建
|
||||
if err := createProduct("测试产品", "TEST_PRODUCT_001", 99.99); err != nil {
|
||||
fmt.Printf("产品创建失败: %v\n", err)
|
||||
}
|
||||
}
|
||||
7
go.mod
7
go.mod
@@ -83,11 +83,18 @@ require (
|
||||
github.com/prometheus/common v0.62.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/smartwalle/alipay/v3 v3.2.25 // indirect
|
||||
github.com/smartwalle/ncrypto v1.0.4 // indirect
|
||||
github.com/smartwalle/ngx v1.0.9 // indirect
|
||||
github.com/smartwalle/nsign v1.0.9 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -197,6 +197,14 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/smartwalle/alipay/v3 v3.2.25 h1:cRDN+fpDWTVHnuHIF/vsJETskRXS/S+fDOdAkzXmV/Q=
|
||||
github.com/smartwalle/alipay/v3 v3.2.25/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
|
||||
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
|
||||
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
|
||||
github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
|
||||
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
|
||||
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
|
||||
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
|
||||
@@ -227,6 +235,12 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
// 产品域实体
|
||||
productEntities "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
apiEntities "tyapi-server/internal/domains/api/entities"
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
)
|
||||
|
||||
@@ -206,6 +207,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
&entities.User{},
|
||||
&entities.SMSCode{},
|
||||
&entities.EnterpriseInfo{},
|
||||
&entities.ContractInfo{},
|
||||
|
||||
// 认证域
|
||||
&certEntities.Certification{},
|
||||
@@ -215,13 +217,20 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
|
||||
// 财务域
|
||||
&financeEntities.Wallet{},
|
||||
&financeEntities.UserSecrets{},
|
||||
&financeEntities.WalletTransaction{},
|
||||
&financeEntities.RechargeRecord{},
|
||||
&financeEntities.AlipayOrder{},
|
||||
|
||||
// 产品域
|
||||
&productEntities.Product{},
|
||||
&productEntities.ProductPackageItem{},
|
||||
&productEntities.ProductCategory{},
|
||||
&productEntities.Subscription{},
|
||||
&productEntities.ProductDocumentation{},
|
||||
&productEntities.ProductApiConfig{},
|
||||
// api
|
||||
&apiEntities.ApiUser{},
|
||||
&apiEntities.ApiCall{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
406
internal/application/api/api_application_service.go
Normal file
406
internal/application/api/api_application_service.go
Normal file
@@ -0,0 +1,406 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/application/api/dto"
|
||||
"tyapi-server/internal/config"
|
||||
entities "tyapi-server/internal/domains/api/entities"
|
||||
"tyapi-server/internal/domains/api/repositories"
|
||||
"tyapi-server/internal/domains/api/services"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
product_services "tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/shared/crypto"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ApiApplicationService interface {
|
||||
CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error)
|
||||
|
||||
// 获取用户API密钥
|
||||
GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error)
|
||||
|
||||
// 用户白名单管理
|
||||
GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error)
|
||||
AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||
DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error
|
||||
|
||||
// 获取用户API调用记录
|
||||
GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error)
|
||||
}
|
||||
|
||||
type ApiApplicationServiceImpl struct {
|
||||
apiCallService services.ApiCallAggregateService
|
||||
apiUserService services.ApiUserAggregateService
|
||||
apiRequestService *services.ApiRequestService
|
||||
apiCallRepository repositories.ApiCallRepository
|
||||
walletService finance_services.WalletAggregateService
|
||||
productManagementService *product_services.ProductManagementService
|
||||
productSubscriptionService *product_services.ProductSubscriptionService
|
||||
txManager *database.TransactionManager
|
||||
config *config.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewApiApplicationService(apiCallService services.ApiCallAggregateService, apiUserService services.ApiUserAggregateService, apiRequestService *services.ApiRequestService, apiCallRepository repositories.ApiCallRepository, walletService finance_services.WalletAggregateService, productManagementService *product_services.ProductManagementService, productSubscriptionService *product_services.ProductSubscriptionService, txManager *database.TransactionManager, config *config.Config, logger *zap.Logger) ApiApplicationService {
|
||||
return &ApiApplicationServiceImpl{apiCallService: apiCallService, apiUserService: apiUserService, apiRequestService: apiRequestService, apiCallRepository: apiCallRepository, walletService: walletService, productManagementService: productManagementService, productSubscriptionService: productSubscriptionService, txManager: txManager, config: config, logger: logger}
|
||||
}
|
||||
|
||||
// CallApi 应用服务层统一入口
|
||||
func (s *ApiApplicationServiceImpl) CallApi(ctx context.Context, cmd *commands.ApiCallCommand) (string, string, error) {
|
||||
// 在事务外创建ApiCall
|
||||
apiCall, err := s.apiCallService.CreateApiCall(cmd.AccessId, cmd.Data, cmd.ClientIP)
|
||||
if err != nil {
|
||||
s.logger.Error("创建ApiCall失败", zap.Error(err))
|
||||
return "", "", ErrSystem
|
||||
}
|
||||
transactionId := apiCall.TransactionId
|
||||
|
||||
// 先保存初始状态
|
||||
err = s.apiCallService.SaveApiCall(ctx, apiCall)
|
||||
if err != nil {
|
||||
s.logger.Error("保存ApiCall初始状态失败", zap.Error(err))
|
||||
return "", "", ErrSystem
|
||||
}
|
||||
|
||||
var encryptedResponse string
|
||||
var businessError error
|
||||
|
||||
// 在事务中执行业务逻辑
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
// 1. 查ApiUser
|
||||
apiUser, err := s.apiUserService.LoadApiUserByAccessId(txCtx, cmd.AccessId)
|
||||
if err != nil {
|
||||
s.logger.Error("查ApiUser失败", zap.Error(err))
|
||||
businessError = ErrInvalidAccessId
|
||||
return ErrInvalidAccessId
|
||||
}
|
||||
// 加入UserId
|
||||
apiCall.UserId = &apiUser.UserId
|
||||
if apiUser.IsFrozen() {
|
||||
s.logger.Error("账户已冻结", zap.String("userId", apiUser.UserId))
|
||||
businessError = ErrFrozenAccount
|
||||
return ErrFrozenAccount
|
||||
}
|
||||
// 在开发环境下跳过IP白名单校验
|
||||
if s.config.App.IsDevelopment() {
|
||||
s.logger.Info("开发环境跳过IP白名单校验", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
||||
} else {
|
||||
if !apiUser.IsWhiteListed(cmd.ClientIP) {
|
||||
s.logger.Error("IP不在白名单内", zap.String("userId", apiUser.UserId), zap.String("ip", cmd.ClientIP))
|
||||
businessError = ErrInvalidIP
|
||||
return ErrInvalidIP
|
||||
}
|
||||
}
|
||||
// 2. 查钱包
|
||||
wallet, err := s.walletService.LoadWalletByUserId(txCtx, apiUser.UserId)
|
||||
if err != nil {
|
||||
s.logger.Error("查钱包失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
if wallet.IsArrears() {
|
||||
s.logger.Error("账户已欠费", zap.String("userId", apiUser.UserId))
|
||||
businessError = ErrArrears
|
||||
return ErrArrears
|
||||
}
|
||||
|
||||
// 3. 查产品
|
||||
product, err := s.productManagementService.GetProductByCode(txCtx, cmd.ApiName)
|
||||
if err != nil {
|
||||
s.logger.Error("查产品失败", zap.Error(err))
|
||||
businessError = ErrProductNotFound
|
||||
return ErrProductNotFound
|
||||
}
|
||||
// 4. 查订阅
|
||||
subscription, err := s.productSubscriptionService.GetUserSubscribedProduct(txCtx, apiUser.UserId, product.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("查订阅失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
if subscription == nil {
|
||||
s.logger.Error("用户未订阅该产品", zap.String("userId", apiUser.UserId), zap.String("productId", product.ID))
|
||||
businessError = ErrNotSubscribed
|
||||
return ErrNotSubscribed
|
||||
}
|
||||
apiCall.ProductId = &product.ID
|
||||
if !product.IsValid() {
|
||||
s.logger.Error("产品已停用", zap.String("productId", product.ID))
|
||||
businessError = ErrProductDisabled
|
||||
return ErrProductDisabled
|
||||
}
|
||||
// 5. 解密参数
|
||||
requestParams, err := crypto.AesDecrypt(cmd.Data, apiUser.SecretKey)
|
||||
if err != nil {
|
||||
s.logger.Error("解密参数失败", zap.Error(err))
|
||||
businessError = ErrDecryptFail
|
||||
return ErrDecryptFail
|
||||
}
|
||||
|
||||
// 6. 调用API
|
||||
response, err := s.apiRequestService.PreprocessRequestApi(txCtx, cmd.ApiName, requestParams, &cmd.Options)
|
||||
if err != nil {
|
||||
if errors.Is(err, processors.ErrDatasource) {
|
||||
s.logger.Error("调用API失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
} else if errors.Is(err, processors.ErrInvalidParam) {
|
||||
s.logger.Error("调用API失败", zap.Error(err))
|
||||
businessError = ErrInvalidParam
|
||||
return ErrInvalidParam
|
||||
} else if errors.Is(err, processors.ErrNotFound) {
|
||||
s.logger.Error("调用API失败", zap.Error(err))
|
||||
businessError = ErrQueryEmpty
|
||||
return ErrQueryEmpty
|
||||
} else {
|
||||
s.logger.Error("调用API失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 加密响应
|
||||
encryptedResponse, err = crypto.AesEncrypt(response, apiUser.SecretKey)
|
||||
if err != nil {
|
||||
s.logger.Error("加密响应失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
apiCall.ResponseData = &encryptedResponse
|
||||
|
||||
// 8. 更新订阅使用次数
|
||||
subscription.IncrementAPIUsage(1)
|
||||
err = s.productSubscriptionService.SaveSubscription(txCtx, subscription)
|
||||
if err != nil {
|
||||
s.logger.Error("保存订阅失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
|
||||
// 9. 扣钱
|
||||
err = s.walletService.Deduct(txCtx, apiUser.UserId, subscription.Price, apiCall.ID, transactionId, product.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("扣钱失败", zap.Error(err))
|
||||
businessError = ErrSystem
|
||||
return ErrSystem
|
||||
}
|
||||
apiCall.Cost = &subscription.Price
|
||||
|
||||
// 10. 标记成功
|
||||
apiCall.MarkSuccess(encryptedResponse, subscription.Price)
|
||||
return nil
|
||||
})
|
||||
|
||||
// 根据事务结果更新ApiCall状态
|
||||
if err != nil {
|
||||
// 事务失败,根据错误类型标记ApiCall
|
||||
if businessError != nil {
|
||||
// 使用业务错误类型
|
||||
switch businessError {
|
||||
case ErrInvalidAccessId:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorInvalidAccess, err.Error())
|
||||
case ErrFrozenAccount:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorFrozenAccount, "")
|
||||
case ErrInvalidIP:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorInvalidIP, "")
|
||||
case ErrArrears:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorArrears, "")
|
||||
case ErrProductNotFound:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorProductNotFound, err.Error())
|
||||
case ErrProductDisabled:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorProductDisabled, "")
|
||||
case ErrNotSubscribed:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorNotSubscribed, "")
|
||||
case ErrDecryptFail:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorDecryptFail, err.Error())
|
||||
case ErrInvalidParam:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorInvalidParam, err.Error())
|
||||
case ErrQueryEmpty:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorQueryEmpty, "")
|
||||
default:
|
||||
apiCall.MarkFailed(entities.ApiCallErrorSystem, err.Error())
|
||||
}
|
||||
} else {
|
||||
// 系统错误
|
||||
apiCall.MarkFailed(entities.ApiCallErrorSystem, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 保存最终状态
|
||||
err = s.apiCallService.SaveApiCall(ctx, apiCall)
|
||||
if err != nil {
|
||||
s.logger.Error("保存ApiCall最终状态失败", zap.Error(err))
|
||||
// 即使保存失败,也返回业务结果
|
||||
}
|
||||
|
||||
if businessError != nil {
|
||||
return transactionId, "", businessError
|
||||
}
|
||||
|
||||
return transactionId, encryptedResponse, nil
|
||||
}
|
||||
|
||||
// GetUserApiKeys 获取用户API密钥
|
||||
func (s *ApiApplicationServiceImpl) GetUserApiKeys(ctx context.Context, userID string) (*dto.ApiKeysResponse, error) {
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dto.ApiKeysResponse{
|
||||
ID: apiUser.ID,
|
||||
UserID: apiUser.UserId,
|
||||
AccessID: apiUser.AccessId,
|
||||
SecretKey: apiUser.SecretKey,
|
||||
Status: apiUser.Status,
|
||||
CreatedAt: apiUser.CreatedAt,
|
||||
UpdatedAt: apiUser.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserWhiteList 获取用户白名单列表
|
||||
func (s *ApiApplicationServiceImpl) GetUserWhiteList(ctx context.Context, userID string) (*dto.WhiteListListResponse, error) {
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
}
|
||||
|
||||
// 将白名单字符串数组转换为响应格式
|
||||
var items []dto.WhiteListResponse
|
||||
for _, ip := range apiUser.WhiteList {
|
||||
items = append(items, dto.WhiteListResponse{
|
||||
ID: apiUser.ID, // 使用API用户ID作为标识
|
||||
UserID: apiUser.UserId,
|
||||
IPAddress: ip,
|
||||
CreatedAt: apiUser.CreatedAt, // 使用API用户创建时间
|
||||
})
|
||||
}
|
||||
|
||||
return &dto.WhiteListListResponse{
|
||||
Items: items,
|
||||
Total: len(items),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddWhiteListIP 添加白名单IP
|
||||
func (s *ApiApplicationServiceImpl) AddWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
}
|
||||
|
||||
// 使用实体的领域方法添加IP到白名单
|
||||
err = apiUser.AddToWhiteList(ipAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
err = s.apiUserService.SaveApiUser(ctx, apiUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteWhiteListIP 删除白名单IP
|
||||
func (s *ApiApplicationServiceImpl) DeleteWhiteListIP(ctx context.Context, userID string, ipAddress string) error {
|
||||
apiUser, err := s.apiUserService.LoadApiUserByUserId(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
}
|
||||
|
||||
// 使用实体的领域方法删除IP
|
||||
err = apiUser.RemoveFromWhiteList(ipAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
err = s.apiUserService.SaveApiUser(ctx, apiUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserApiCalls 获取用户API调用记录
|
||||
func (s *ApiApplicationServiceImpl) GetUserApiCalls(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*dto.ApiCallListResponse, error) {
|
||||
// 查询API调用记录(包含产品名称)
|
||||
productNameMap, calls, total, err := s.apiCallRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询API调用记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []dto.ApiCallRecordResponse
|
||||
for _, call := range calls {
|
||||
item := dto.ApiCallRecordResponse{
|
||||
ID: call.ID,
|
||||
AccessId: call.AccessId,
|
||||
UserId: *call.UserId,
|
||||
TransactionId: call.TransactionId,
|
||||
ClientIp: call.ClientIp,
|
||||
Status: call.Status,
|
||||
StartAt: call.StartAt.Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: call.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
UpdatedAt: call.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
// 处理可选字段
|
||||
if call.ProductId != nil {
|
||||
item.ProductId = call.ProductId
|
||||
}
|
||||
// 从映射中获取产品名称
|
||||
if productName, exists := productNameMap[call.ID]; exists {
|
||||
item.ProductName = &productName
|
||||
}
|
||||
if call.EndAt != nil {
|
||||
endAt := call.EndAt.Format("2006-01-02 15:04:05")
|
||||
item.EndAt = &endAt
|
||||
}
|
||||
if call.Cost != nil {
|
||||
cost := call.Cost.String()
|
||||
item.Cost = &cost
|
||||
}
|
||||
if call.ErrorType != nil {
|
||||
item.ErrorType = call.ErrorType
|
||||
}
|
||||
if call.ErrorMsg != nil {
|
||||
item.ErrorMsg = call.ErrorMsg
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &dto.ApiCallListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
18
internal/application/api/commands/api_call_commands.go
Normal file
18
internal/application/api/commands/api_call_commands.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
type ApiCallCommand struct {
|
||||
ClientIP string `json:"-"`
|
||||
AccessId string `json:"-"`
|
||||
ApiName string `json:"-"`
|
||||
Data string `json:"data" binding:"required"`
|
||||
Options ApiCallOptions `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
type ApiCallOptions struct {
|
||||
Json bool `json:"json,omitempty"` // 是否返回JSON格式
|
||||
}
|
||||
|
||||
// EncryptCommand 加密命令
|
||||
type EncryptCommand struct {
|
||||
Data map[string]interface{} `json:"data" binding:"required"`
|
||||
}
|
||||
90
internal/application/api/dto/api_response.go
Normal file
90
internal/application/api/dto/api_response.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
// ApiCallResponse API调用响应结构
|
||||
type ApiCallResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
Data string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ApiKeysResponse API密钥响应结构
|
||||
type ApiKeysResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
AccessID string `json:"access_id"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// 白名单相关DTO
|
||||
type WhiteListResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type WhiteListRequest struct {
|
||||
IPAddress string `json:"ip_address" binding:"required,ip"`
|
||||
}
|
||||
|
||||
type WhiteListListResponse struct {
|
||||
Items []WhiteListResponse `json:"items"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// API调用记录相关DTO
|
||||
type ApiCallRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
AccessId string `json:"access_id"`
|
||||
UserId string `json:"user_id"`
|
||||
ProductId *string `json:"product_id,omitempty"`
|
||||
ProductName *string `json:"product_name,omitempty"`
|
||||
TransactionId string `json:"transaction_id"`
|
||||
ClientIp string `json:"client_ip"`
|
||||
Status string `json:"status"`
|
||||
StartAt string `json:"start_at"`
|
||||
EndAt *string `json:"end_at,omitempty"`
|
||||
Cost *string `json:"cost,omitempty"`
|
||||
ErrorType *string `json:"error_type,omitempty"`
|
||||
ErrorMsg *string `json:"error_msg,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
type ApiCallListResponse struct {
|
||||
Items []ApiCallRecordResponse `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// EncryptResponse 加密响应
|
||||
type EncryptResponse struct {
|
||||
EncryptedData string `json:"encrypted_data"`
|
||||
}
|
||||
|
||||
// NewSuccessResponse 创建成功响应
|
||||
func NewSuccessResponse(transactionId, data string) *ApiCallResponse {
|
||||
return &ApiCallResponse{
|
||||
Code: 0,
|
||||
Message: "业务成功",
|
||||
TransactionId: transactionId,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorResponse 创建错误响应
|
||||
func NewErrorResponse(code int, message, transactionId string) *ApiCallResponse {
|
||||
return &ApiCallResponse{
|
||||
Code: code,
|
||||
Message: message,
|
||||
TransactionId: transactionId,
|
||||
Data: "",
|
||||
}
|
||||
}
|
||||
47
internal/application/api/errors.go
Normal file
47
internal/application/api/errors.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import "errors"
|
||||
|
||||
// API调用相关错误类型
|
||||
var (
|
||||
ErrQueryEmpty = errors.New("查询为空")
|
||||
ErrSystem = errors.New("接口异常")
|
||||
ErrDecryptFail = errors.New("解密失败")
|
||||
ErrRequestParam = errors.New("请求参数结构不正确")
|
||||
ErrInvalidParam = errors.New("参数校验不正确")
|
||||
ErrInvalidIP = errors.New("未经授权的IP")
|
||||
ErrMissingAccessId = errors.New("缺少Access-Id")
|
||||
ErrInvalidAccessId = errors.New("未经授权的AccessId")
|
||||
ErrFrozenAccount = errors.New("账户已冻结")
|
||||
ErrArrears = errors.New("账户余额不足,无法请求")
|
||||
ErrProductNotFound = errors.New("产品不存在")
|
||||
ErrProductDisabled = errors.New("产品已停用")
|
||||
ErrNotSubscribed = errors.New("未订阅此产品")
|
||||
ErrBusiness = errors.New("业务失败")
|
||||
)
|
||||
|
||||
// 错误码映射 - 严格按照用户要求
|
||||
var ErrorCodeMap = map[error]int{
|
||||
ErrQueryEmpty: 1000,
|
||||
ErrSystem: 1001,
|
||||
ErrDecryptFail: 1002,
|
||||
ErrRequestParam: 1003,
|
||||
ErrInvalidParam: 1003,
|
||||
ErrInvalidIP: 1004,
|
||||
ErrMissingAccessId: 1005,
|
||||
ErrInvalidAccessId: 1006,
|
||||
ErrFrozenAccount: 1007,
|
||||
ErrArrears: 1007,
|
||||
ErrProductNotFound: 1008,
|
||||
ErrProductDisabled: 1008,
|
||||
ErrNotSubscribed: 1008,
|
||||
ErrBusiness: 2001,
|
||||
}
|
||||
|
||||
// GetErrorCode 获取错误对应的错误码
|
||||
func GetErrorCode(err error) int {
|
||||
if code, exists := ErrorCodeMap[err]; exists {
|
||||
return code
|
||||
}
|
||||
return 1001 // 默认返回接口异常
|
||||
}
|
||||
@@ -12,46 +12,25 @@ import (
|
||||
// 负责用例协调,提供精简的应用层接口
|
||||
type CertificationApplicationService interface {
|
||||
// ================ 用户操作用例 ================
|
||||
|
||||
// 创建认证申请
|
||||
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// 提交企业信息
|
||||
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// 确认状态
|
||||
ConfirmAuth(ctx context.Context, cmd *queries.ConfirmAuthCommand) (*responses.ConfirmAuthResponse, error)
|
||||
// 确认签署
|
||||
ConfirmSign(ctx context.Context, cmd *queries.ConfirmSignCommand) (*responses.ConfirmSignResponse, error)
|
||||
// 申请合同签署
|
||||
ApplyContract(ctx context.Context, cmd *commands.ApplyContractCommand) (*responses.ContractSignUrlResponse, error)
|
||||
|
||||
// 重试失败操作
|
||||
RetryOperation(ctx context.Context, cmd *commands.RetryOperationCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// ================ 查询用例 ================
|
||||
|
||||
// 获取认证详情
|
||||
GetCertification(ctx context.Context, query *queries.GetCertificationQuery) (*responses.CertificationResponse, error)
|
||||
|
||||
// 获取用户认证列表
|
||||
GetUserCertifications(ctx context.Context, query *queries.GetUserCertificationsQuery) (*responses.CertificationListResponse, error)
|
||||
|
||||
// 获取认证列表(管理员)
|
||||
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
|
||||
|
||||
// 搜索认证
|
||||
SearchCertifications(ctx context.Context, query *queries.SearchCertificationsQuery) (*responses.CertificationListResponse, error)
|
||||
|
||||
// 获取认证统计
|
||||
GetCertificationStatistics(ctx context.Context, query *queries.GetCertificationStatisticsQuery) (*responses.CertificationStatisticsResponse, error)
|
||||
|
||||
// ================ e签宝回调处理 ================
|
||||
|
||||
// 处理e签宝回调
|
||||
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) (*responses.CallbackResponse, error)
|
||||
|
||||
// ================ 管理员操作 ================
|
||||
|
||||
// 手动状态转换(管理员)
|
||||
ForceTransitionStatus(ctx context.Context, cmd *commands.ForceTransitionStatusCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// 获取系统监控数据
|
||||
GetSystemMonitoring(ctx context.Context, query *queries.GetSystemMonitoringQuery) (*responses.SystemMonitoringResponse, error)
|
||||
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) error
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"tyapi-server/internal/domains/certification/entities/value_objects"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
)
|
||||
|
||||
@@ -12,7 +11,6 @@ type CreateCertificationCommand struct {
|
||||
|
||||
// ApplyContractCommand 申请合同命令
|
||||
type ApplyContractCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
}
|
||||
|
||||
@@ -26,13 +24,54 @@ type RetryOperationCommand struct {
|
||||
|
||||
// EsignCallbackCommand e签宝回调命令
|
||||
type EsignCallbackCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
CallbackType string `json:"callback_type" validate:"required,oneof=auth_result sign_result flow_status"`
|
||||
RawData string `json:"raw_data" validate:"required"`
|
||||
Headers map[string]string `json:"headers,omitempty"`
|
||||
QueryParams map[string]string `json:"query_params,omitempty"`
|
||||
Data *EsignCallbackData `json:"data"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
QueryParams map[string]string `json:"query_params"`
|
||||
}
|
||||
|
||||
// EsignCallbackData e签宝回调数据结构
|
||||
type EsignCallbackData struct {
|
||||
Action string `json:"action"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
AuthFlowId string `json:"authFlowId,omitempty"`
|
||||
SignFlowId string `json:"signFlowId,omitempty"`
|
||||
CustomBizNum string `json:"customBizNum,omitempty"`
|
||||
SignOrder int `json:"signOrder,omitempty"`
|
||||
OperateTime int64 `json:"operateTime,omitempty"`
|
||||
SignResult int `json:"signResult,omitempty"`
|
||||
ResultDescription string `json:"resultDescription,omitempty"`
|
||||
AuthType string `json:"authType,omitempty"`
|
||||
SignFlowStatus string `json:"signFlowStatus,omitempty"`
|
||||
Operator *EsignOperator `json:"operator,omitempty"`
|
||||
PsnInfo *EsignPsnInfo `json:"psnInfo,omitempty"`
|
||||
Organization *EsignOrganization `json:"organization,omitempty"`
|
||||
}
|
||||
|
||||
// EsignOperator 签署人信息
|
||||
type EsignOperator struct {
|
||||
PsnId string `json:"psnId"`
|
||||
PsnAccount *EsignPsnAccount `json:"psnAccount"`
|
||||
}
|
||||
|
||||
// EsignPsnInfo 个人认证信息
|
||||
type EsignPsnInfo struct {
|
||||
PsnId string `json:"psnId"`
|
||||
PsnAccount *EsignPsnAccount `json:"psnAccount"`
|
||||
}
|
||||
|
||||
// EsignPsnAccount 个人账户信息
|
||||
type EsignPsnAccount struct {
|
||||
AccountMobile string `json:"accountMobile"`
|
||||
AccountEmail string `json:"accountEmail"`
|
||||
}
|
||||
|
||||
// EsignOrganization 企业信息
|
||||
type EsignOrganization struct {
|
||||
OrgName string `json:"orgName"`
|
||||
// 可以根据需要添加更多企业信息字段
|
||||
}
|
||||
|
||||
|
||||
// ForceTransitionStatusCommand 强制状态转换命令(管理员)
|
||||
type ForceTransitionStatusCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
@@ -44,7 +83,13 @@ type ForceTransitionStatusCommand struct {
|
||||
|
||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
||||
type SubmitEnterpriseInfoCommand struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
UserID string `json:"-" validate:"required"`
|
||||
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info" validate:"required"`
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CompanyName string `json:"company_name" binding:"required,min=2,max=100" comment:"企业名称,如:北京科技有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required,id_card" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号,11位,如:13800138000"`
|
||||
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
|
||||
EnterpriseEmail string `json:"enterprise_email" binding:"required,enterprise_email" comment:"企业邮箱,如:info@example.com"`
|
||||
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
|
||||
}
|
||||
|
||||
@@ -9,18 +9,27 @@ import (
|
||||
|
||||
// GetCertificationQuery 获取认证详情查询
|
||||
type GetCertificationQuery struct {
|
||||
CertificationID string `json:"certification_id" validate:"required"`
|
||||
UserID string `json:"user_id,omitempty"` // 用于权限验证
|
||||
UserID string `json:"user_id,omitempty"` // 用于权限验证
|
||||
}
|
||||
|
||||
// ConfirmAuthCommand 确认认证状态命令
|
||||
type ConfirmAuthCommand struct {
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
// ConfirmSignCommand 确认签署状态命令
|
||||
type ConfirmSignCommand struct {
|
||||
UserID string `json:"-"`
|
||||
}
|
||||
|
||||
// GetUserCertificationsQuery 获取用户认证列表查询
|
||||
type GetUserCertificationsQuery struct {
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
Status enums.CertificationStatus `json:"status,omitempty"`
|
||||
IncludeCompleted bool `json:"include_completed,omitempty"`
|
||||
IncludeFailed bool `json:"include_failed,omitempty"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id" validate:"required"`
|
||||
Status enums.CertificationStatus `json:"status,omitempty"`
|
||||
IncludeCompleted bool `json:"include_completed,omitempty"`
|
||||
IncludeFailed bool `json:"include_failed,omitempty"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
@@ -39,19 +48,19 @@ func (q *GetUserCertificationsQuery) ToDomainQuery() *domainQueries.UserCertific
|
||||
|
||||
// ListCertificationsQuery 认证列表查询(管理员)
|
||||
type ListCertificationsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Status enums.CertificationStatus `json:"status,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
|
||||
CreatedAfter *time.Time `json:"created_after,omitempty"`
|
||||
CreatedBefore *time.Time `json:"created_before,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
LegalPersonName string `json:"legal_person_name,omitempty"`
|
||||
SearchKeyword string `json:"search_keyword,omitempty"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Status enums.CertificationStatus `json:"status,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
|
||||
CreatedAfter *time.Time `json:"created_after,omitempty"`
|
||||
CreatedBefore *time.Time `json:"created_before,omitempty"`
|
||||
CompanyName string `json:"company_name,omitempty"`
|
||||
LegalPersonName string `json:"legal_person_name,omitempty"`
|
||||
SearchKeyword string `json:"search_keyword,omitempty"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
@@ -77,15 +86,15 @@ func (q *ListCertificationsQuery) ToDomainQuery() *domainQueries.ListCertificati
|
||||
|
||||
// SearchCertificationsQuery 搜索认证查询
|
||||
type SearchCertificationsQuery struct {
|
||||
Keyword string `json:"keyword" validate:"required,min=2"`
|
||||
SearchFields []string `json:"search_fields,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
ExactMatch bool `json:"exact_match,omitempty"`
|
||||
Keyword string `json:"keyword" validate:"required,min=2"`
|
||||
SearchFields []string `json:"search_fields,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
UserID string `json:"user_id,omitempty"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
ExactMatch bool `json:"exact_match,omitempty"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
@@ -107,15 +116,15 @@ func (q *SearchCertificationsQuery) ToDomainQuery() *domainQueries.SearchCertifi
|
||||
|
||||
// GetCertificationStatisticsQuery 认证统计查询
|
||||
type GetCertificationStatisticsQuery struct {
|
||||
StartDate time.Time `json:"start_date" validate:"required"`
|
||||
EndDate time.Time `json:"end_date" validate:"required"`
|
||||
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
UserIDs []string `json:"user_ids,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
|
||||
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
|
||||
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
|
||||
StartDate time.Time `json:"start_date" validate:"required"`
|
||||
EndDate time.Time `json:"end_date" validate:"required"`
|
||||
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
UserIDs []string `json:"user_ids,omitempty"`
|
||||
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
|
||||
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
|
||||
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
|
||||
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
@@ -135,7 +144,7 @@ func (q *GetCertificationStatisticsQuery) ToDomainQuery() *domainQueries.Certifi
|
||||
|
||||
// GetSystemMonitoringQuery 系统监控查询
|
||||
type GetSystemMonitoringQuery struct {
|
||||
TimeRange string `json:"time_range" validate:"oneof=1h 6h 24h 7d 30d"`
|
||||
TimeRange string `json:"time_range" validate:"oneof=1h 6h 24h 7d 30d"`
|
||||
Metrics []string `json:"metrics,omitempty"` // 指定要获取的指标类型
|
||||
}
|
||||
|
||||
@@ -175,7 +184,7 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
|
||||
if len(q.Metrics) == 0 {
|
||||
return true // 如果没有指定,包含所有指标
|
||||
}
|
||||
|
||||
|
||||
for _, m := range q.Metrics {
|
||||
if m == metric {
|
||||
return true
|
||||
|
||||
@@ -1,55 +1,65 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities/value_objects"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
"tyapi-server/internal/domains/certification/repositories"
|
||||
"tyapi-server/internal/domains/certification/services/state_machine"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
|
||||
// 企业信息
|
||||
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
|
||||
|
||||
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
|
||||
|
||||
// 合同信息
|
||||
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
|
||||
|
||||
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
|
||||
|
||||
// 时间戳
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
|
||||
// 业务状态
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
IsFailed bool `json:"is_failed"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
|
||||
IsCompleted bool `json:"is_completed"`
|
||||
IsFailed bool `json:"is_failed"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
|
||||
// 失败信息
|
||||
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
|
||||
FailureReasonName string `json:"failure_reason_name,omitempty"`
|
||||
FailureMessage string `json:"failure_message,omitempty"`
|
||||
CanRetry bool `json:"can_retry,omitempty"`
|
||||
RetryCount int `json:"retry_count,omitempty"`
|
||||
|
||||
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
|
||||
FailureReasonName string `json:"failure_reason_name,omitempty"`
|
||||
FailureMessage string `json:"failure_message,omitempty"`
|
||||
CanRetry bool `json:"can_retry,omitempty"`
|
||||
RetryCount int `json:"retry_count,omitempty"`
|
||||
|
||||
// 用户操作提示
|
||||
NextAction string `json:"next_action,omitempty"`
|
||||
AvailableActions []string `json:"available_actions,omitempty"`
|
||||
|
||||
NextAction string `json:"next_action,omitempty"`
|
||||
AvailableActions []string `json:"available_actions,omitempty"`
|
||||
|
||||
// 元数据
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// ConfirmAuthResponse 确认认证状态响应
|
||||
type ConfirmAuthResponse struct {
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// ConfirmSignResponse 确认签署状态响应
|
||||
type ConfirmSignResponse struct {
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
// CertificationListResponse 认证列表响应
|
||||
@@ -63,63 +73,42 @@ type CertificationListResponse struct {
|
||||
|
||||
// ContractSignUrlResponse 合同签署URL响应
|
||||
type ContractSignUrlResponse struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
ContractSignURL string `json:"contract_sign_url"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
ExpireAt *time.Time `json:"expire_at,omitempty"`
|
||||
NextAction string `json:"next_action"`
|
||||
Message string `json:"message"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
ContractSignURL string `json:"contract_sign_url"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
ExpireAt *time.Time `json:"expire_at,omitempty"`
|
||||
NextAction string `json:"next_action"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// CallbackResponse 回调响应
|
||||
type CallbackResponse struct {
|
||||
Success bool `json:"success"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
CallbackType string `json:"callback_type"`
|
||||
ProcessedAt time.Time `json:"processed_at"`
|
||||
OldStatus enums.CertificationStatus `json:"old_status,omitempty"`
|
||||
NewStatus enums.CertificationStatus `json:"new_status,omitempty"`
|
||||
Message string `json:"message"`
|
||||
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
|
||||
}
|
||||
|
||||
// CertificationStatisticsResponse 认证统计响应
|
||||
type CertificationStatisticsResponse struct {
|
||||
Period string `json:"period"`
|
||||
TimeRange string `json:"time_range"`
|
||||
Statistics *repositories.CertificationStatistics `json:"statistics"`
|
||||
ProgressStats *repositories.CertificationProgressStats `json:"progress_stats,omitempty"`
|
||||
Charts map[string]interface{} `json:"charts,omitempty"`
|
||||
GeneratedAt time.Time `json:"generated_at"`
|
||||
}
|
||||
|
||||
// SystemMonitoringResponse 系统监控响应
|
||||
type SystemMonitoringResponse struct {
|
||||
TimeRange string `json:"time_range"`
|
||||
Metrics map[string]interface{} `json:"metrics"`
|
||||
Alerts []SystemAlert `json:"alerts,omitempty"`
|
||||
SystemHealth SystemHealthStatus `json:"system_health"`
|
||||
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||
TimeRange string `json:"time_range"`
|
||||
Metrics map[string]interface{} `json:"metrics"`
|
||||
Alerts []SystemAlert `json:"alerts,omitempty"`
|
||||
SystemHealth SystemHealthStatus `json:"system_health"`
|
||||
LastUpdatedAt time.Time `json:"last_updated_at"`
|
||||
}
|
||||
|
||||
// SystemAlert 系统警告
|
||||
type SystemAlert struct {
|
||||
Level string `json:"level"` // info, warning, error, critical
|
||||
Type string `json:"type"` // 警告类型
|
||||
Message string `json:"message"` // 警告消息
|
||||
Metric string `json:"metric"` // 相关指标
|
||||
Value interface{} `json:"value"` // 当前值
|
||||
Threshold interface{} `json:"threshold"` // 阈值
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Level string `json:"level"` // info, warning, error, critical
|
||||
Type string `json:"type"` // 警告类型
|
||||
Message string `json:"message"` // 警告消息
|
||||
Metric string `json:"metric"` // 相关指标
|
||||
Value interface{} `json:"value"` // 当前值
|
||||
Threshold interface{} `json:"threshold"` // 阈值
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// SystemHealthStatus 系统健康状态
|
||||
type SystemHealthStatus struct {
|
||||
Overall string `json:"overall"` // healthy, warning, critical
|
||||
Components map[string]string `json:"components"` // 各组件状态
|
||||
LastCheck time.Time `json:"last_check"`
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
Overall string `json:"overall"` // healthy, warning, critical
|
||||
Components map[string]string `json:"components"` // 各组件状态
|
||||
LastCheck time.Time `json:"last_check"`
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// ================ 响应构建辅助方法 ================
|
||||
@@ -130,7 +119,7 @@ func NewCertificationListResponse(items []*CertificationResponse, total int64, p
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
|
||||
return &CertificationListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
@@ -149,24 +138,14 @@ func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextActio
|
||||
NextAction: nextAction,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
|
||||
// 设置过期时间(默认24小时)
|
||||
expireAt := time.Now().Add(24 * time.Hour)
|
||||
response.ExpireAt = &expireAt
|
||||
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// NewCallbackResponse 创建回调响应
|
||||
func NewCallbackResponse(success bool, certificationID, callbackType, message string) *CallbackResponse {
|
||||
return &CallbackResponse{
|
||||
Success: success,
|
||||
CertificationID: certificationID,
|
||||
CallbackType: callbackType,
|
||||
ProcessedAt: time.Now(),
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSystemAlert 创建系统警告
|
||||
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
|
||||
@@ -182,30 +161,6 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
|
||||
}
|
||||
}
|
||||
|
||||
// IsSuccess 检查响应是否成功
|
||||
func (r *CallbackResponse) IsSuccess() bool {
|
||||
return r.Success
|
||||
}
|
||||
|
||||
// HasStateTransition 检查是否有状态转换
|
||||
func (r *CallbackResponse) HasStateTransition() bool {
|
||||
return r.StateTransition != nil
|
||||
}
|
||||
|
||||
// GetStatusChange 获取状态变更描述
|
||||
func (r *CallbackResponse) GetStatusChange() string {
|
||||
if !r.HasStateTransition() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if r.OldStatus == r.NewStatus {
|
||||
return "状态无变化"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("从 %s 转换为 %s",
|
||||
enums.GetStatusName(r.OldStatus),
|
||||
enums.GetStatusName(r.NewStatus))
|
||||
}
|
||||
|
||||
// IsHealthy 检查系统是否健康
|
||||
func (r *SystemMonitoringResponse) IsHealthy() bool {
|
||||
|
||||
@@ -1,69 +1,31 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// CreateWalletCommand 创建钱包命令
|
||||
type CreateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
}
|
||||
|
||||
// UpdateWalletCommand 更新钱包命令
|
||||
type UpdateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Balance decimal.Decimal `json:"balance" binding:"omitempty"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
|
||||
// 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:"备注信息"`
|
||||
}
|
||||
|
||||
// RechargeWalletCommand 充值钱包命令
|
||||
type RechargeWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
// 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:"备注信息"`
|
||||
}
|
||||
|
||||
// RechargeCommand 充值命令
|
||||
type RechargeCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// WithdrawWalletCommand 提现钱包命令
|
||||
type WithdrawWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// WithdrawCommand 提现命令
|
||||
type WithdrawCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// CreateUserSecretsCommand 创建用户密钥命令
|
||||
type CreateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// RegenerateAccessKeyCommand 重新生成访问密钥命令
|
||||
type RegenerateAccessKeyCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// DeactivateUserSecretsCommand 停用用户密钥命令
|
||||
type DeactivateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
}
|
||||
|
||||
// WalletTransactionCommand 钱包交易命令
|
||||
type WalletTransactionCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
FromUserID string `json:"from_user_id" binding:"required,uuid"`
|
||||
ToUserID string `json:"to_user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
Notes string `json:"notes" binding:"omitempty,max=200"`
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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"` // 是否可以重试
|
||||
}
|
||||
@@ -8,24 +8,21 @@ import (
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
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 用户密钥响应
|
||||
@@ -49,3 +46,62 @@ type WalletStatsResponse struct {
|
||||
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"`
|
||||
TransferOrderID string `json:"transfer_order_id,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
OperatorID string `json:"operator_id,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"`
|
||||
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"` // 最高充值金额
|
||||
}
|
||||
|
||||
@@ -2,23 +2,36 @@ package finance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
"tyapi-server/internal/application/finance/dto/responses"
|
||||
"tyapi-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)
|
||||
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)
|
||||
|
||||
CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, 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)
|
||||
|
||||
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)
|
||||
|
||||
// 获取用户充值记录
|
||||
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)
|
||||
|
||||
// 获取充值配置
|
||||
GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error)
|
||||
}
|
||||
|
||||
@@ -2,88 +2,649 @@ package finance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"net/http"
|
||||
"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"
|
||||
"tyapi-server/internal/config"
|
||||
finance_entities "tyapi-server/internal/domains/finance/entities"
|
||||
finance_repositories "tyapi-server/internal/domains/finance/repositories"
|
||||
finance_services "tyapi-server/internal/domains/finance/services"
|
||||
"tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// FinanceApplicationServiceImpl 财务应用服务实现
|
||||
type FinanceApplicationServiceImpl struct {
|
||||
walletRepo repositories.WalletRepository
|
||||
userSecretsRepo repositories.UserSecretsRepository
|
||||
logger *zap.Logger
|
||||
aliPayClient *payment.AliPayService
|
||||
walletService finance_services.WalletAggregateService
|
||||
rechargeRecordService finance_services.RechargeRecordService
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository
|
||||
txManager *database.TransactionManager
|
||||
logger *zap.Logger
|
||||
config *config.Config
|
||||
}
|
||||
|
||||
// NewFinanceApplicationService 创建财务应用服务
|
||||
func NewFinanceApplicationService(
|
||||
walletRepo repositories.WalletRepository,
|
||||
userSecretsRepo repositories.UserSecretsRepository,
|
||||
aliPayClient *payment.AliPayService,
|
||||
walletService finance_services.WalletAggregateService,
|
||||
rechargeRecordService finance_services.RechargeRecordService,
|
||||
walletTransactionRepository finance_repositories.WalletTransactionRepository,
|
||||
alipayOrderRepo finance_repositories.AlipayOrderRepository,
|
||||
txManager *database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
config *config.Config,
|
||||
) FinanceApplicationService {
|
||||
return &FinanceApplicationServiceImpl{
|
||||
walletRepo: walletRepo,
|
||||
userSecretsRepo: userSecretsRepo,
|
||||
logger: logger,
|
||||
aliPayClient: aliPayClient,
|
||||
walletService: walletService,
|
||||
rechargeRecordService: rechargeRecordService,
|
||||
walletTransactionRepository: walletTransactionRepository,
|
||||
alipayOrderRepo: alipayOrderRepo,
|
||||
txManager: txManager,
|
||||
logger: logger,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// 调用钱包聚合服务创建钱包
|
||||
wallet, err := s.walletService.CreateWallet(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("创建钱包失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.WalletResponse{
|
||||
ID: wallet.ID,
|
||||
UserID: wallet.UserID,
|
||||
IsActive: wallet.IsActive,
|
||||
Balance: wallet.Balance,
|
||||
BalanceStatus: wallet.GetBalanceStatus(),
|
||||
IsArrears: wallet.IsArrears(),
|
||||
IsLowBalance: wallet.IsLowBalance(),
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// 调用钱包聚合服务获取钱包信息
|
||||
wallet, err := s.walletService.LoadWalletByUserId(ctx, query.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取钱包信息失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.WalletResponse{
|
||||
ID: wallet.ID,
|
||||
UserID: wallet.UserID,
|
||||
IsActive: wallet.IsActive,
|
||||
Balance: wallet.Balance,
|
||||
BalanceStatus: wallet.GetBalanceStatus(),
|
||||
IsArrears: wallet.IsArrears(),
|
||||
IsLowBalance: wallet.IsLowBalance(),
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error {
|
||||
// ... implementation from old service
|
||||
return fmt.Errorf("not implemented")
|
||||
// CreateAlipayRechargeOrder 创建支付宝充值订单(完整流程编排)
|
||||
func (s *FinanceApplicationServiceImpl) CreateAlipayRechargeOrder(ctx context.Context, cmd *commands.CreateAlipayRechargeCommand) (*responses.AlipayRechargeOrderResponse, error) {
|
||||
cmd.Subject = "天远数据API充值"
|
||||
// 将字符串金额转换为 decimal.Decimal
|
||||
amount, err := decimal.NewFromString(cmd.Amount)
|
||||
if err != nil {
|
||||
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
||||
return nil, fmt.Errorf("金额格式错误: %w", err)
|
||||
}
|
||||
|
||||
// 验证金额是否大于0
|
||||
if amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("充值金额必须大于0")
|
||||
}
|
||||
|
||||
// 从配置中获取充值限制
|
||||
minAmount, err := decimal.NewFromString(s.config.Recharge.MinAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("配置中的最低充值金额格式错误", zap.String("min_amount", s.config.Recharge.MinAmount), zap.Error(err))
|
||||
return nil, fmt.Errorf("系统配置错误: %w", err)
|
||||
}
|
||||
|
||||
maxAmount, err := decimal.NewFromString(s.config.Recharge.MaxAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("配置中的最高充值金额格式错误", zap.String("max_amount", s.config.Recharge.MaxAmount), zap.Error(err))
|
||||
return nil, fmt.Errorf("系统配置错误: %w", err)
|
||||
}
|
||||
|
||||
// 验证充值金额范围
|
||||
if amount.LessThan(minAmount) {
|
||||
return nil, fmt.Errorf("充值金额不能少于%s元", minAmount.String())
|
||||
}
|
||||
|
||||
if amount.GreaterThan(maxAmount) {
|
||||
return nil, fmt.Errorf("单次充值金额不能超过%s元", maxAmount.String())
|
||||
}
|
||||
|
||||
// 1. 生成订单号
|
||||
outTradeNo := s.aliPayClient.GenerateOutTradeNo()
|
||||
var payUrl string
|
||||
// 2. 进入事务,创建充值记录和支付宝订单本地记录
|
||||
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
|
||||
var err error
|
||||
// 创建充值记录
|
||||
rechargeRecord, err := s.rechargeRecordService.CreateAlipayRecharge(txCtx, cmd.UserID, amount, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建支付宝充值记录失败", zap.Error(err))
|
||||
return fmt.Errorf("创建支付宝充值记录失败: %w", err)
|
||||
}
|
||||
// 创建支付宝订单本地记录
|
||||
err = s.rechargeRecordService.CreateAlipayOrder(txCtx, rechargeRecord.ID, outTradeNo, cmd.Subject, amount, cmd.Platform)
|
||||
if err != nil {
|
||||
s.logger.Error("创建支付宝订单记录失败", zap.Error(err))
|
||||
return fmt.Errorf("创建支付宝订单记录失败: %w", err)
|
||||
}
|
||||
// 3. 创建支付宝订单(调用支付宝API,非事务内)
|
||||
payUrl, err = s.aliPayClient.CreateAlipayOrder(ctx, cmd.Platform, amount, cmd.Subject, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建支付宝订单失败", zap.Error(err))
|
||||
return fmt.Errorf("创建支付宝订单失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("支付宝充值订单创建成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("amount", amount.String()),
|
||||
zap.String("platform", cmd.Platform),
|
||||
)
|
||||
|
||||
return &responses.AlipayRechargeOrderResponse{
|
||||
PayURL: payUrl,
|
||||
OutTradeNo: outTradeNo,
|
||||
Amount: amount,
|
||||
Platform: cmd.Platform,
|
||||
Subject: cmd.Subject,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// TransferRecharge 对公转账充值
|
||||
func (s *FinanceApplicationServiceImpl) TransferRecharge(ctx context.Context, cmd *commands.TransferRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
||||
// 将字符串金额转换为 decimal.Decimal
|
||||
amount, err := decimal.NewFromString(cmd.Amount)
|
||||
if err != nil {
|
||||
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
||||
return nil, fmt.Errorf("金额格式错误: %w", err)
|
||||
}
|
||||
|
||||
// 验证金额是否大于0
|
||||
if amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("充值金额必须大于0")
|
||||
}
|
||||
|
||||
// 调用充值记录服务进行对公转账充值
|
||||
rechargeRecord, err := s.rechargeRecordService.TransferRecharge(ctx, cmd.UserID, amount, cmd.TransferOrderID, cmd.Notes)
|
||||
if err != nil {
|
||||
s.logger.Error("对公转账充值失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transferOrderID := ""
|
||||
if rechargeRecord.TransferOrderID != nil {
|
||||
transferOrderID = *rechargeRecord.TransferOrderID
|
||||
}
|
||||
|
||||
return &responses.RechargeRecordResponse{
|
||||
ID: rechargeRecord.ID,
|
||||
UserID: rechargeRecord.UserID,
|
||||
Amount: rechargeRecord.Amount,
|
||||
RechargeType: string(rechargeRecord.RechargeType),
|
||||
Status: string(rechargeRecord.Status),
|
||||
TransferOrderID: transferOrderID,
|
||||
Notes: rechargeRecord.Notes,
|
||||
CreatedAt: rechargeRecord.CreatedAt,
|
||||
UpdatedAt: rechargeRecord.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// GiftRecharge 赠送充值
|
||||
func (s *FinanceApplicationServiceImpl) GiftRecharge(ctx context.Context, cmd *commands.GiftRechargeCommand) (*responses.RechargeRecordResponse, error) {
|
||||
// 将字符串金额转换为 decimal.Decimal
|
||||
amount, err := decimal.NewFromString(cmd.Amount)
|
||||
if err != nil {
|
||||
s.logger.Error("金额格式错误", zap.String("amount", cmd.Amount), zap.Error(err))
|
||||
return nil, fmt.Errorf("金额格式错误: %w", err)
|
||||
}
|
||||
|
||||
// 验证金额是否大于0
|
||||
if amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("充值金额必须大于0")
|
||||
}
|
||||
|
||||
// 获取当前操作员ID(这里假设从上下文中获取,实际可能需要从认证中间件获取)
|
||||
operatorID := "system" // 临时使用,实际应该从认证上下文获取
|
||||
|
||||
// 调用充值记录服务进行赠送充值
|
||||
rechargeRecord, err := s.rechargeRecordService.GiftRecharge(ctx, cmd.UserID, amount, operatorID, cmd.Notes)
|
||||
if err != nil {
|
||||
s.logger.Error("赠送充值失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.RechargeRecordResponse{
|
||||
ID: rechargeRecord.ID,
|
||||
UserID: rechargeRecord.UserID,
|
||||
Amount: rechargeRecord.Amount,
|
||||
RechargeType: string(rechargeRecord.RechargeType),
|
||||
Status: string(rechargeRecord.Status),
|
||||
OperatorID: "system", // 临时使用,实际应该从认证上下文获取
|
||||
Notes: rechargeRecord.Notes,
|
||||
CreatedAt: rechargeRecord.CreatedAt,
|
||||
UpdatedAt: rechargeRecord.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// GetUserWalletTransactions 获取用户钱包交易记录
|
||||
func (s *FinanceApplicationServiceImpl) GetUserWalletTransactions(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.WalletTransactionListResponse, error) {
|
||||
// 查询钱包交易记录(包含产品名称)
|
||||
productNameMap, transactions, total, err := s.walletTransactionRepository.ListByUserIdWithFiltersAndProductName(ctx, userID, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询钱包交易记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.WalletTransactionResponse
|
||||
for _, transaction := range transactions {
|
||||
item := responses.WalletTransactionResponse{
|
||||
ID: transaction.ID,
|
||||
UserID: transaction.UserID,
|
||||
ApiCallID: transaction.ApiCallID,
|
||||
TransactionID: transaction.TransactionID,
|
||||
ProductID: transaction.ProductID,
|
||||
ProductName: productNameMap[transaction.ProductID], // 从映射中获取产品名称
|
||||
Amount: transaction.Amount,
|
||||
CreatedAt: transaction.CreatedAt,
|
||||
UpdatedAt: transaction.UpdatedAt,
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.WalletTransactionListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
|
||||
|
||||
// HandleAlipayCallback 处理支付宝回调
|
||||
func (s *FinanceApplicationServiceImpl) HandleAlipayCallback(ctx context.Context, r *http.Request) error {
|
||||
// 解析并验证支付宝回调通知
|
||||
notification, err := s.aliPayClient.HandleAliPaymentNotification(r)
|
||||
if err != nil {
|
||||
s.logger.Error("支付宝回调验证失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 记录回调数据
|
||||
s.logger.Info("支付宝回调数据",
|
||||
zap.String("out_trade_no", notification.OutTradeNo),
|
||||
zap.String("trade_no", notification.TradeNo),
|
||||
zap.String("trade_status", string(notification.TradeStatus)),
|
||||
zap.String("total_amount", notification.TotalAmount),
|
||||
zap.String("buyer_id", notification.BuyerId),
|
||||
zap.String("seller_id", notification.SellerId),
|
||||
)
|
||||
|
||||
// 检查交易状态
|
||||
if !s.aliPayClient.IsAlipayPaymentSuccess(notification) {
|
||||
s.logger.Warn("支付宝交易未成功",
|
||||
zap.String("out_trade_no", notification.OutTradeNo),
|
||||
zap.String("trade_status", string(notification.TradeStatus)),
|
||||
)
|
||||
return nil // 不返回错误,因为这是正常的业务状态
|
||||
}
|
||||
|
||||
// 使用公共方法处理支付成功逻辑
|
||||
err = s.processAlipayPaymentSuccess(ctx, notification.OutTradeNo, notification.TradeNo, notification.TotalAmount, notification.BuyerId, notification.SellerId)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
zap.String("out_trade_no", notification.OutTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// processAlipayPaymentSuccess 处理支付宝支付成功的公共逻辑
|
||||
func (s *FinanceApplicationServiceImpl) processAlipayPaymentSuccess(ctx context.Context, outTradeNo, tradeNo, totalAmount, buyerID, sellerID string) error {
|
||||
// 解析金额
|
||||
amount, err := decimal.NewFromString(totalAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("解析支付宝金额失败",
|
||||
zap.String("total_amount", totalAmount),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// 直接调用充值记录服务处理支付成功逻辑
|
||||
// 该服务内部会处理所有必要的检查、事务和更新操作
|
||||
err = s.rechargeRecordService.HandleAlipayPaymentSuccess(ctx, outTradeNo, amount, tradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("处理支付宝支付成功失败",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("支付宝支付成功处理完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("trade_no", tradeNo),
|
||||
zap.String("amount", amount.String()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error {
|
||||
// ... implementation from old service
|
||||
return fmt.Errorf("not implemented")
|
||||
// updateAlipayOrderStatus 根据支付宝状态更新本地订单状态
|
||||
func (s *FinanceApplicationServiceImpl) updateAlipayOrderStatus(ctx context.Context, outTradeNo string, alipayStatus alipay.TradeStatus, tradeNo, totalAmount string) error {
|
||||
// 查找支付宝订单
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return fmt.Errorf("查找支付宝订单失败: %w", err)
|
||||
}
|
||||
|
||||
if alipayOrder == nil {
|
||||
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||
return fmt.Errorf("支付宝订单不存在")
|
||||
}
|
||||
|
||||
switch alipayStatus {
|
||||
case alipay.TradeStatusSuccess:
|
||||
// 支付成功,调用公共处理逻辑
|
||||
return s.processAlipayPaymentSuccess(ctx, outTradeNo, tradeNo, totalAmount, "", "")
|
||||
case alipay.TradeStatusClosed:
|
||||
// 交易关闭
|
||||
s.logger.Info("支付宝订单已关闭", zap.String("out_trade_no", outTradeNo))
|
||||
alipayOrder.MarkClosed()
|
||||
err = s.alipayOrderRepo.Update(ctx, *alipayOrder)
|
||||
if err != nil {
|
||||
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
case alipay.TradeStatusWaitBuyerPay:
|
||||
// 等待买家付款,保持pending状态
|
||||
s.logger.Info("支付宝订单等待买家付款", zap.String("out_trade_no", outTradeNo))
|
||||
default:
|
||||
// 其他状态,记录日志
|
||||
s.logger.Info("支付宝订单其他状态", zap.String("out_trade_no", outTradeNo), zap.String("status", string(alipayStatus)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// HandleAlipayReturn 处理支付宝同步回调
|
||||
func (s *FinanceApplicationServiceImpl) HandleAlipayReturn(ctx context.Context, outTradeNo string) (string, error) {
|
||||
if outTradeNo == "" {
|
||||
return "", fmt.Errorf("缺少商户订单号")
|
||||
}
|
||||
|
||||
// 查找支付宝订单
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return "", fmt.Errorf("查找支付宝订单失败: %w", err)
|
||||
}
|
||||
|
||||
if alipayOrder == nil {
|
||||
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||
return "", fmt.Errorf("支付宝订单不存在")
|
||||
}
|
||||
|
||||
// 记录同步回调查询
|
||||
s.logger.Info("支付宝同步回调查询订单状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("order_status", string(alipayOrder.Status)),
|
||||
zap.String("trade_no", func() string {
|
||||
if alipayOrder.TradeNo != nil {
|
||||
return *alipayOrder.TradeNo
|
||||
}
|
||||
return ""
|
||||
}()),
|
||||
)
|
||||
|
||||
// 返回订单状态
|
||||
switch alipayOrder.Status {
|
||||
case finance_entities.AlipayOrderStatusSuccess:
|
||||
return "TRADE_SUCCESS", nil
|
||||
case finance_entities.AlipayOrderStatusPending:
|
||||
// 对于pending状态,需要特殊处理
|
||||
// 可能是用户支付了但支付宝异步回调还没到,或者用户还没支付
|
||||
// 这里可以尝试主动查询支付宝订单状态,但为了简化处理,先返回WAIT_BUYER_PAY
|
||||
// 让前端显示"支付处理中"的状态,用户可以通过刷新页面或等待异步回调来更新状态
|
||||
s.logger.Info("支付宝订单状态为pending,建议用户等待异步回调或刷新页面",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
)
|
||||
return "WAIT_BUYER_PAY", nil
|
||||
case finance_entities.AlipayOrderStatusFailed:
|
||||
return "TRADE_FAILED", nil
|
||||
case finance_entities.AlipayOrderStatusClosed:
|
||||
return "TRADE_CLOSED", nil
|
||||
default:
|
||||
return "UNKNOWN", nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
// GetAlipayOrderStatus 获取支付宝订单状态
|
||||
func (s *FinanceApplicationServiceImpl) GetAlipayOrderStatus(ctx context.Context, outTradeNo string) (*responses.AlipayOrderStatusResponse, error) {
|
||||
if outTradeNo == "" {
|
||||
return nil, fmt.Errorf("缺少商户订单号")
|
||||
}
|
||||
|
||||
// 查找支付宝订单
|
||||
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
return nil, fmt.Errorf("查找支付宝订单失败: %w", err)
|
||||
}
|
||||
|
||||
if alipayOrder == nil {
|
||||
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
|
||||
return nil, fmt.Errorf("支付宝订单不存在")
|
||||
}
|
||||
|
||||
// 如果订单状态为pending,主动查询支付宝订单状态
|
||||
if alipayOrder.Status == finance_entities.AlipayOrderStatusPending {
|
||||
s.logger.Info("订单状态为pending,主动查询支付宝订单状态", zap.String("out_trade_no", outTradeNo))
|
||||
|
||||
// 调用支付宝查询接口
|
||||
alipayResp, err := s.aliPayClient.QueryOrderStatus(ctx, outTradeNo)
|
||||
if err != nil {
|
||||
s.logger.Error("查询支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
// 查询失败不影响返回,继续使用数据库中的状态
|
||||
} else {
|
||||
// 解析支付宝返回的状态
|
||||
alipayStatus := alipayResp.TradeStatus
|
||||
s.logger.Info("支付宝返回订单状态",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("alipay_status", string(alipayStatus)),
|
||||
zap.String("trade_no", alipayResp.TradeNo),
|
||||
)
|
||||
|
||||
// 使用公共方法更新订单状态
|
||||
err = s.updateAlipayOrderStatus(ctx, outTradeNo, alipayStatus, alipayResp.TradeNo, alipayResp.TotalAmount)
|
||||
if err != nil {
|
||||
s.logger.Error("更新支付宝订单状态失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
|
||||
}
|
||||
|
||||
// 重新获取更新后的订单信息
|
||||
updatedOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
|
||||
if err == nil && updatedOrder != nil {
|
||||
alipayOrder = updatedOrder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否处理中
|
||||
isProcessing := alipayOrder.Status == finance_entities.AlipayOrderStatusPending
|
||||
|
||||
// 判断是否可以重试(失败状态可以重试)
|
||||
canRetry := alipayOrder.Status == finance_entities.AlipayOrderStatusFailed
|
||||
|
||||
// 转换为响应DTO
|
||||
response := &responses.AlipayOrderStatusResponse{
|
||||
OutTradeNo: alipayOrder.OutTradeNo,
|
||||
TradeNo: alipayOrder.TradeNo,
|
||||
Status: string(alipayOrder.Status),
|
||||
Amount: alipayOrder.Amount,
|
||||
Subject: alipayOrder.Subject,
|
||||
Platform: alipayOrder.Platform,
|
||||
CreatedAt: alipayOrder.CreatedAt,
|
||||
UpdatedAt: alipayOrder.UpdatedAt,
|
||||
NotifyTime: alipayOrder.NotifyTime,
|
||||
ReturnTime: alipayOrder.ReturnTime,
|
||||
ErrorCode: &alipayOrder.ErrorCode,
|
||||
ErrorMessage: &alipayOrder.ErrorMessage,
|
||||
IsProcessing: isProcessing,
|
||||
CanRetry: canRetry,
|
||||
}
|
||||
|
||||
// 如果错误码为空,设置为nil
|
||||
if alipayOrder.ErrorCode == "" {
|
||||
response.ErrorCode = nil
|
||||
}
|
||||
if alipayOrder.ErrorMessage == "" {
|
||||
response.ErrorMessage = nil
|
||||
}
|
||||
|
||||
s.logger.Info("查询支付宝订单状态完成",
|
||||
zap.String("out_trade_no", outTradeNo),
|
||||
zap.String("status", string(alipayOrder.Status)),
|
||||
zap.Bool("is_processing", isProcessing),
|
||||
zap.Bool("can_retry", canRetry),
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetUserRechargeRecords 获取用户充值记录
|
||||
func (s *FinanceApplicationServiceImpl) GetUserRechargeRecords(ctx context.Context, userID string, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
||||
// 查询用户充值记录
|
||||
records, err := s.rechargeRecordService.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Error("查询用户充值记录失败", zap.Error(err), zap.String("userID", userID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
total := int64(len(records))
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.RechargeRecordResponse
|
||||
for _, record := range records {
|
||||
item := responses.RechargeRecordResponse{
|
||||
ID: record.ID,
|
||||
UserID: record.UserID,
|
||||
Amount: record.Amount,
|
||||
RechargeType: string(record.RechargeType),
|
||||
Status: string(record.Status),
|
||||
Notes: record.Notes,
|
||||
CreatedAt: record.CreatedAt,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
}
|
||||
|
||||
// 根据充值类型设置相应的订单号
|
||||
if record.AlipayOrderID != nil {
|
||||
item.AlipayOrderID = *record.AlipayOrderID
|
||||
}
|
||||
if record.TransferOrderID != nil {
|
||||
item.TransferOrderID = *record.TransferOrderID
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.RechargeRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAdminRechargeRecords 管理员获取充值记录
|
||||
func (s *FinanceApplicationServiceImpl) GetAdminRechargeRecords(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.RechargeRecordListResponse, error) {
|
||||
// 查询所有充值记录(管理员可以查看所有用户的充值记录)
|
||||
records, err := s.rechargeRecordService.GetAll(ctx, filters, options)
|
||||
if err != nil {
|
||||
s.logger.Error("查询管理员充值记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
total, err := s.rechargeRecordService.Count(ctx, filters)
|
||||
if err != nil {
|
||||
s.logger.Error("统计管理员充值记录失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应DTO
|
||||
var items []responses.RechargeRecordResponse
|
||||
for _, record := range records {
|
||||
item := responses.RechargeRecordResponse{
|
||||
ID: record.ID,
|
||||
UserID: record.UserID,
|
||||
Amount: record.Amount,
|
||||
RechargeType: string(record.RechargeType),
|
||||
Status: string(record.Status),
|
||||
Notes: record.Notes,
|
||||
CreatedAt: record.CreatedAt,
|
||||
UpdatedAt: record.UpdatedAt,
|
||||
}
|
||||
|
||||
// 根据充值类型设置相应的订单号
|
||||
if record.AlipayOrderID != nil {
|
||||
item.AlipayOrderID = *record.AlipayOrderID
|
||||
}
|
||||
if record.TransferOrderID != nil {
|
||||
item.TransferOrderID = *record.TransferOrderID
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.RechargeRecordListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetRechargeConfig 获取充值配置
|
||||
func (s *FinanceApplicationServiceImpl) GetRechargeConfig(ctx context.Context) (*responses.RechargeConfigResponse, error) {
|
||||
return &responses.RechargeConfigResponse{
|
||||
MinAmount: s.config.Recharge.MinAmount,
|
||||
MaxAmount: s.config.Recharge.MaxAmount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd
|
||||
if err := s.validateCreateCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 2. 验证分类编号唯一性
|
||||
if err := s.validateCategoryCode(cmd.Code, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 3. 创建分类实体
|
||||
category := &entities.ProductCategory{
|
||||
Name: cmd.Name,
|
||||
@@ -50,14 +50,14 @@ func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
}
|
||||
|
||||
|
||||
// 4. 保存到仓储
|
||||
createdCategory, err := s.categoryRepo.Create(ctx, *category)
|
||||
if err != nil {
|
||||
s.logger.Error("创建分类失败", zap.Error(err), zap.String("code", cmd.Code))
|
||||
return fmt.Errorf("创建分类失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
s.logger.Info("创建分类成功", zap.String("id", createdCategory.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
@@ -68,18 +68,18 @@ func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd
|
||||
if err := s.validateUpdateCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 2. 获取现有分类
|
||||
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 3. 验证分类编号唯一性(排除当前分类)
|
||||
if err := s.validateCategoryCode(cmd.Code, cmd.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 4. 更新分类信息
|
||||
existingCategory.Name = cmd.Name
|
||||
existingCategory.Code = cmd.Code
|
||||
@@ -87,13 +87,13 @@ func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd
|
||||
existingCategory.Sort = cmd.Sort
|
||||
existingCategory.IsEnabled = cmd.IsEnabled
|
||||
existingCategory.IsVisible = cmd.IsVisible
|
||||
|
||||
|
||||
// 5. 保存到仓储
|
||||
if err := s.categoryRepo.Update(ctx, existingCategory); err != nil {
|
||||
s.logger.Error("更新分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("更新分类失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
s.logger.Info("更新分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
@@ -105,16 +105,16 @@ func (s *CategoryApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
|
||||
// 2. 检查是否有产品(可选,根据业务需求决定)
|
||||
// 这里可以添加检查逻辑,如果有产品则不允许删除
|
||||
|
||||
|
||||
// 3. 删除分类
|
||||
if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("删除分类失败: %w", err)
|
||||
}
|
||||
|
||||
|
||||
s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("code", existingCategory.Code))
|
||||
return nil
|
||||
}
|
||||
@@ -226,11 +226,11 @@ func (s *CategoryApplicationServiceImpl) validateCategoryCode(code, excludeID st
|
||||
if code == "" {
|
||||
return errors.New("分类编号不能为空")
|
||||
}
|
||||
|
||||
|
||||
existingCategory, err := s.categoryRepo.FindByCode(context.Background(), code)
|
||||
if err == nil && existingCategory != nil && existingCategory.ID != excludeID {
|
||||
return errors.New("分类编号已存在")
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package commands
|
||||
|
||||
// AddPackageItemCommand 添加组合包子产品命令
|
||||
type AddPackageItemCommand struct {
|
||||
ProductID string `json:"product_id" binding:"required,uuid" comment:"子产品ID"`
|
||||
}
|
||||
|
||||
// UpdatePackageItemCommand 更新组合包子产品命令
|
||||
type UpdatePackageItemCommand struct {
|
||||
SortOrder int `json:"sort_order" binding:"required,min=0" comment:"排序"`
|
||||
}
|
||||
|
||||
// ReorderPackageItemsCommand 重新排序组合包子产品命令
|
||||
type ReorderPackageItemsCommand struct {
|
||||
ItemIDs []string `json:"item_ids" binding:"required,dive,uuid" comment:"子产品ID列表"`
|
||||
}
|
||||
|
||||
// UpdatePackageItemsCommand 批量更新组合包子产品命令
|
||||
type UpdatePackageItemsCommand struct {
|
||||
Items []PackageItemData `json:"items" binding:"required,dive" comment:"子产品列表"`
|
||||
}
|
||||
|
||||
// PackageItemData 组合包子产品数据
|
||||
type PackageItemData struct {
|
||||
ProductID string `json:"product_id" binding:"required,uuid" comment:"子产品ID"`
|
||||
SortOrder int `json:"sort_order" binding:"required,min=0" comment:"排序"`
|
||||
}
|
||||
@@ -11,7 +11,7 @@ type CreateProductCommand struct {
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
@@ -30,7 +30,7 @@ type UpdateProductCommand struct {
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
@@ -40,4 +40,4 @@ type UpdateProductCommand struct {
|
||||
// DeleteProductCommand 删除产品命令
|
||||
type DeleteProductCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
}
|
||||
}
|
||||
|
||||
10
internal/application/product/dto/queries/package_queries.go
Normal file
10
internal/application/product/dto/queries/package_queries.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package queries
|
||||
|
||||
// GetAvailableProductsQuery 获取可选子产品查询
|
||||
type GetAvailableProductsQuery struct {
|
||||
ExcludePackageID string `form:"exclude_package_id" binding:"omitempty,uuid" comment:"排除的组合包ID"`
|
||||
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// ProductApiConfigResponse 产品API配置响应
|
||||
type ProductApiConfigResponse struct {
|
||||
ID string `json:"id" comment:"配置ID"`
|
||||
ProductID string `json:"product_id" comment:"产品ID"`
|
||||
RequestParams []RequestParamResponse `json:"request_params" comment:"请求参数配置"`
|
||||
ResponseFields []ResponseFieldResponse `json:"response_fields" comment:"响应字段配置"`
|
||||
ResponseExample map[string]interface{} `json:"response_example" comment:"响应示例"`
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// RequestParamResponse 请求参数响应
|
||||
type RequestParamResponse struct {
|
||||
Name string `json:"name" comment:"参数名称"`
|
||||
Field string `json:"field" comment:"参数字段名"`
|
||||
Type string `json:"type" comment:"参数类型"`
|
||||
Required bool `json:"required" comment:"是否必填"`
|
||||
Description string `json:"description" comment:"参数描述"`
|
||||
Example string `json:"example" comment:"参数示例"`
|
||||
Validation string `json:"validation" comment:"验证规则"`
|
||||
}
|
||||
|
||||
// ResponseFieldResponse 响应字段响应
|
||||
type ResponseFieldResponse struct {
|
||||
Name string `json:"name" comment:"字段名称"`
|
||||
Path string `json:"path" comment:"字段路径"`
|
||||
Type string `json:"type" comment:"字段类型"`
|
||||
Description string `json:"description" comment:"字段描述"`
|
||||
Required bool `json:"required" comment:"是否必填"`
|
||||
Example string `json:"example" comment:"字段示例"`
|
||||
}
|
||||
|
||||
// ProductApiConfigListResponse 产品API配置列表响应
|
||||
type ProductApiConfigListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []ProductApiConfigResponse `json:"items" comment:"配置列表"`
|
||||
}
|
||||
@@ -2,6 +2,16 @@ package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// PackageItemResponse 组合包项目响应
|
||||
type PackageItemResponse struct {
|
||||
ID string `json:"id" comment:"项目ID"`
|
||||
ProductID string `json:"product_id" comment:"子产品ID"`
|
||||
ProductCode string `json:"product_code" comment:"子产品编号"`
|
||||
ProductName string `json:"product_name" comment:"子产品名称"`
|
||||
SortOrder int `json:"sort_order" comment:"排序"`
|
||||
Price float64 `json:"price" comment:"子产品价格"`
|
||||
}
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
type ProductInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
@@ -23,6 +33,9 @@ type ProductInfoResponse struct {
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
|
||||
// 组合包信息
|
||||
PackageItems []*PackageItemResponse `json:"package_items,omitempty" comment:"组合包项目列表"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProductApiConfigApplicationService 产品API配置应用服务接口
|
||||
type ProductApiConfigApplicationService interface {
|
||||
// 获取产品API配置
|
||||
GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error)
|
||||
|
||||
// 根据产品代码获取API配置
|
||||
GetProductApiConfigByCode(ctx context.Context, productCode string) (*responses.ProductApiConfigResponse, error)
|
||||
|
||||
// 批量获取产品API配置
|
||||
GetProductApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*responses.ProductApiConfigResponse, error)
|
||||
|
||||
// 创建产品API配置
|
||||
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
||||
|
||||
// 更新产品API配置
|
||||
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
||||
|
||||
// 删除产品API配置
|
||||
DeleteProductApiConfig(ctx context.Context, configID string) error
|
||||
}
|
||||
|
||||
// ProductApiConfigApplicationServiceImpl 产品API配置应用服务实现
|
||||
type ProductApiConfigApplicationServiceImpl struct {
|
||||
apiConfigService services.ProductApiConfigService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductApiConfigApplicationService 创建产品API配置应用服务
|
||||
func NewProductApiConfigApplicationService(
|
||||
apiConfigService services.ProductApiConfigService,
|
||||
logger *zap.Logger,
|
||||
) ProductApiConfigApplicationService {
|
||||
return &ProductApiConfigApplicationServiceImpl{
|
||||
apiConfigService: apiConfigService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetProductApiConfig 获取产品API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
||||
config, err := s.apiConfigService.GetApiConfigByProductID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.convertToResponse(config), nil
|
||||
}
|
||||
|
||||
// GetProductApiConfigByCode 根据产品代码获取API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfigByCode(ctx context.Context, productCode string) (*responses.ProductApiConfigResponse, error) {
|
||||
config, err := s.apiConfigService.GetApiConfigByProductCode(ctx, productCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.convertToResponse(config), nil
|
||||
}
|
||||
|
||||
// GetProductApiConfigsByProductIDs 批量获取产品API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) GetProductApiConfigsByProductIDs(ctx context.Context, productIDs []string) ([]*responses.ProductApiConfigResponse, error) {
|
||||
configs, err := s.apiConfigService.GetApiConfigsByProductIDs(ctx, productIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var responses []*responses.ProductApiConfigResponse
|
||||
for _, config := range configs {
|
||||
responses = append(responses, s.convertToResponse(config))
|
||||
}
|
||||
|
||||
return responses, nil
|
||||
}
|
||||
|
||||
// CreateProductApiConfig 创建产品API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) CreateProductApiConfig(ctx context.Context, productID string, configResponse *responses.ProductApiConfigResponse) error {
|
||||
// 检查是否已存在配置
|
||||
exists, err := s.apiConfigService.ExistsByProductID(ctx, productID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return errors.New("产品API配置已存在")
|
||||
}
|
||||
|
||||
// 转换为实体
|
||||
config := s.convertToEntity(configResponse)
|
||||
config.ProductID = productID
|
||||
|
||||
return s.apiConfigService.CreateApiConfig(ctx, config)
|
||||
}
|
||||
|
||||
// UpdateProductApiConfig 更新产品API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) UpdateProductApiConfig(ctx context.Context, configID string, configResponse *responses.ProductApiConfigResponse) error {
|
||||
// 获取现有配置
|
||||
existingConfig, err := s.apiConfigService.GetApiConfigByProductID(ctx, configResponse.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
config := s.convertToEntity(configResponse)
|
||||
config.ID = configID
|
||||
config.ProductID = existingConfig.ProductID
|
||||
|
||||
return s.apiConfigService.UpdateApiConfig(ctx, config)
|
||||
}
|
||||
|
||||
// DeleteProductApiConfig 删除产品API配置
|
||||
func (s *ProductApiConfigApplicationServiceImpl) DeleteProductApiConfig(ctx context.Context, configID string) error {
|
||||
return s.apiConfigService.DeleteApiConfig(ctx, configID)
|
||||
}
|
||||
|
||||
// convertToResponse 转换为响应DTO
|
||||
func (s *ProductApiConfigApplicationServiceImpl) convertToResponse(config *entities.ProductApiConfig) *responses.ProductApiConfigResponse {
|
||||
requestParams, _ := config.GetRequestParams()
|
||||
responseFields, _ := config.GetResponseFields()
|
||||
responseExample, _ := config.GetResponseExample()
|
||||
|
||||
// 转换请求参数
|
||||
var requestParamResponses []responses.RequestParamResponse
|
||||
for _, param := range requestParams {
|
||||
requestParamResponses = append(requestParamResponses, responses.RequestParamResponse{
|
||||
Name: param.Name,
|
||||
Field: param.Field,
|
||||
Type: param.Type,
|
||||
Required: param.Required,
|
||||
Description: param.Description,
|
||||
Example: param.Example,
|
||||
Validation: param.Validation,
|
||||
})
|
||||
}
|
||||
|
||||
// 转换响应字段
|
||||
var responseFieldResponses []responses.ResponseFieldResponse
|
||||
for _, field := range responseFields {
|
||||
responseFieldResponses = append(responseFieldResponses, responses.ResponseFieldResponse{
|
||||
Name: field.Name,
|
||||
Path: field.Path,
|
||||
Type: field.Type,
|
||||
Description: field.Description,
|
||||
Required: field.Required,
|
||||
Example: field.Example,
|
||||
})
|
||||
}
|
||||
|
||||
return &responses.ProductApiConfigResponse{
|
||||
ID: config.ID,
|
||||
ProductID: config.ProductID,
|
||||
RequestParams: requestParamResponses,
|
||||
ResponseFields: responseFieldResponses,
|
||||
ResponseExample: responseExample,
|
||||
CreatedAt: config.CreatedAt,
|
||||
UpdatedAt: config.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// convertToEntity 转换为实体
|
||||
func (s *ProductApiConfigApplicationServiceImpl) convertToEntity(configResponse *responses.ProductApiConfigResponse) *entities.ProductApiConfig {
|
||||
// 转换请求参数
|
||||
var requestParams []entities.RequestParam
|
||||
for _, param := range configResponse.RequestParams {
|
||||
requestParams = append(requestParams, entities.RequestParam{
|
||||
Name: param.Name,
|
||||
Field: param.Field,
|
||||
Type: param.Type,
|
||||
Required: param.Required,
|
||||
Description: param.Description,
|
||||
Example: param.Example,
|
||||
Validation: param.Validation,
|
||||
})
|
||||
}
|
||||
|
||||
// 转换响应字段
|
||||
var responseFields []entities.ResponseField
|
||||
for _, field := range configResponse.ResponseFields {
|
||||
responseFields = append(responseFields, entities.ResponseField{
|
||||
Name: field.Name,
|
||||
Path: field.Path,
|
||||
Type: field.Type,
|
||||
Description: field.Description,
|
||||
Required: field.Required,
|
||||
Example: field.Example,
|
||||
})
|
||||
}
|
||||
|
||||
config := &entities.ProductApiConfig{}
|
||||
|
||||
// 设置JSON字段
|
||||
config.SetRequestParams(requestParams)
|
||||
config.SetResponseFields(responseFields)
|
||||
config.SetResponseExample(configResponse.ResponseExample)
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductApplicationService 产品应用服务接口
|
||||
@@ -15,12 +16,26 @@ type ProductApplicationService interface {
|
||||
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
||||
|
||||
GetProductByID(ctx context.Context, query *queries.GetProductQuery) (*responses.ProductInfoResponse, error)
|
||||
ListProducts(ctx context.Context, query *queries.ListProductsQuery) (*responses.ProductListResponse, error)
|
||||
ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error)
|
||||
GetProductsByIDs(ctx context.Context, query *queries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
|
||||
// 业务查询
|
||||
GetSubscribableProducts(ctx context.Context, query *queries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error)
|
||||
|
||||
// 组合包管理
|
||||
AddPackageItem(ctx context.Context, packageID string, cmd *commands.AddPackageItemCommand) error
|
||||
UpdatePackageItem(ctx context.Context, packageID, itemID string, cmd *commands.UpdatePackageItemCommand) error
|
||||
RemovePackageItem(ctx context.Context, packageID, itemID string) error
|
||||
ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error
|
||||
UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error
|
||||
GetAvailableProducts(ctx context.Context, query *queries.GetAvailableProductsQuery) (*responses.ProductListResponse, error)
|
||||
|
||||
// API配置管理
|
||||
GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error)
|
||||
CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error
|
||||
UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error
|
||||
DeleteProductApiConfig(ctx context.Context, configID string) error
|
||||
}
|
||||
|
||||
// CategoryApplicationService 分类应用服务接口
|
||||
|
||||
@@ -2,7 +2,9 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
@@ -10,25 +12,29 @@ import (
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// ProductApplicationServiceImpl 产品应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type ProductApplicationServiceImpl struct {
|
||||
productManagementService *product_service.ProductManagementService
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
logger *zap.Logger
|
||||
productManagementService *product_service.ProductManagementService
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
productApiConfigAppService ProductApiConfigApplicationService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductApplicationService 创建产品应用服务
|
||||
func NewProductApplicationService(
|
||||
productManagementService *product_service.ProductManagementService,
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
productApiConfigAppService ProductApiConfigApplicationService,
|
||||
logger *zap.Logger,
|
||||
) ProductApplicationService {
|
||||
return &ProductApplicationServiceImpl{
|
||||
productManagementService: productManagementService,
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
productApiConfigAppService: productApiConfigAppService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -43,7 +49,7 @@ func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: cmd.Price,
|
||||
Price: decimal.NewFromFloat(cmd.Price),
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
@@ -72,7 +78,7 @@ func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *
|
||||
existingProduct.Description = cmd.Description
|
||||
existingProduct.Content = cmd.Content
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
existingProduct.Price = cmd.Price
|
||||
existingProduct.Price = decimal.NewFromFloat(cmd.Price)
|
||||
existingProduct.IsEnabled = cmd.IsEnabled
|
||||
existingProduct.IsVisible = cmd.IsVisible
|
||||
existingProduct.IsPackage = cmd.IsPackage
|
||||
@@ -92,22 +98,9 @@ func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *
|
||||
|
||||
// ListProducts 获取产品列表
|
||||
// 业务流程:1. 获取产品列表 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query *appQueries.ListProductsQuery) (*responses.ProductListResponse, error) {
|
||||
// 根据查询条件获取产品列表
|
||||
var products []*entities.Product
|
||||
var err error
|
||||
|
||||
if query.CategoryID != "" {
|
||||
products, err = s.productManagementService.GetProductsByCategory(ctx, query.CategoryID)
|
||||
} else if query.IsVisible != nil && *query.IsVisible {
|
||||
products, err = s.productManagementService.GetVisibleProducts(ctx)
|
||||
} else if query.IsEnabled != nil && *query.IsEnabled {
|
||||
products, err = s.productManagementService.GetEnabledProducts(ctx)
|
||||
} else {
|
||||
// 默认获取可见产品
|
||||
products, err = s.productManagementService.GetVisibleProducts(ctx)
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*responses.ProductListResponse, error) {
|
||||
// 调用领域服务获取产品列表
|
||||
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,9 +112,9 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
|
||||
}
|
||||
|
||||
return &responses.ProductListResponse{
|
||||
Total: int64(len(items)),
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@@ -186,6 +179,177 @@ func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*r
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AddPackageItem 添加组合包子产品
|
||||
func (s *ProductApplicationServiceImpl) AddPackageItem(ctx context.Context, packageID string, cmd *commands.AddPackageItemCommand) error {
|
||||
// 验证组合包是否存在
|
||||
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !packageProduct.IsPackage {
|
||||
return fmt.Errorf("产品不是组合包")
|
||||
}
|
||||
|
||||
// 验证子产品是否存在且不是组合包
|
||||
subProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subProduct.IsPackage {
|
||||
return fmt.Errorf("不能将组合包作为子产品")
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
existingItems, err := s.productManagementService.GetPackageItems(ctx, packageID)
|
||||
if err == nil {
|
||||
for _, item := range existingItems {
|
||||
if item.ProductID == cmd.ProductID {
|
||||
return fmt.Errorf("该产品已在组合包中")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前最大排序号
|
||||
maxSortOrder := 0
|
||||
if existingItems != nil {
|
||||
for _, item := range existingItems {
|
||||
if item.SortOrder > maxSortOrder {
|
||||
maxSortOrder = item.SortOrder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建组合包项目
|
||||
packageItem := &entities.ProductPackageItem{
|
||||
PackageID: packageID,
|
||||
ProductID: cmd.ProductID,
|
||||
SortOrder: maxSortOrder + 1,
|
||||
}
|
||||
|
||||
return s.productManagementService.CreatePackageItem(ctx, packageItem)
|
||||
}
|
||||
|
||||
// UpdatePackageItem 更新组合包子产品
|
||||
func (s *ProductApplicationServiceImpl) UpdatePackageItem(ctx context.Context, packageID, itemID string, cmd *commands.UpdatePackageItemCommand) error {
|
||||
// 验证组合包项目是否存在
|
||||
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packageItem.PackageID != packageID {
|
||||
return fmt.Errorf("组合包项目不属于指定组合包")
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
packageItem.SortOrder = cmd.SortOrder
|
||||
|
||||
return s.productManagementService.UpdatePackageItem(ctx, packageItem)
|
||||
}
|
||||
|
||||
// RemovePackageItem 移除组合包子产品
|
||||
func (s *ProductApplicationServiceImpl) RemovePackageItem(ctx context.Context, packageID, itemID string) error {
|
||||
// 验证组合包项目是否存在
|
||||
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packageItem.PackageID != packageID {
|
||||
return fmt.Errorf("组合包项目不属于指定组合包")
|
||||
}
|
||||
|
||||
return s.productManagementService.DeletePackageItem(ctx, itemID)
|
||||
}
|
||||
|
||||
// ReorderPackageItems 重新排序组合包子产品
|
||||
func (s *ProductApplicationServiceImpl) ReorderPackageItems(ctx context.Context, packageID string, cmd *commands.ReorderPackageItemsCommand) error {
|
||||
// 验证所有项目是否属于该组合包
|
||||
for i, itemID := range cmd.ItemIDs {
|
||||
packageItem, err := s.productManagementService.GetPackageItemByID(ctx, itemID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if packageItem.PackageID != packageID {
|
||||
return fmt.Errorf("组合包项目不属于指定组合包")
|
||||
}
|
||||
|
||||
// 更新排序
|
||||
packageItem.SortOrder = i + 1
|
||||
if err := s.productManagementService.UpdatePackageItem(ctx, packageItem); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePackageItems 批量更新组合包子产品
|
||||
func (s *ProductApplicationServiceImpl) UpdatePackageItems(ctx context.Context, packageID string, cmd *commands.UpdatePackageItemsCommand) error {
|
||||
// 验证组合包是否存在
|
||||
packageProduct, err := s.productManagementService.GetProductByID(ctx, packageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !packageProduct.IsPackage {
|
||||
return fmt.Errorf("产品不是组合包")
|
||||
}
|
||||
|
||||
// 验证所有子产品是否存在且不是组合包
|
||||
for _, item := range cmd.Items {
|
||||
subProduct, err := s.productManagementService.GetProductByID(ctx, item.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if subProduct.IsPackage {
|
||||
return fmt.Errorf("不能将组合包作为子产品")
|
||||
}
|
||||
}
|
||||
|
||||
// 使用事务进行批量更新
|
||||
return s.productManagementService.UpdatePackageItemsBatch(ctx, packageID, cmd.Items)
|
||||
}
|
||||
|
||||
// GetAvailableProducts 获取可选子产品列表
|
||||
func (s *ProductApplicationServiceImpl) GetAvailableProducts(ctx context.Context, query *appQueries.GetAvailableProductsQuery) (*responses.ProductListResponse, error) {
|
||||
// 构建筛选条件
|
||||
filters := make(map[string]interface{})
|
||||
filters["is_package"] = false // 只获取非组合包产品
|
||||
filters["is_enabled"] = true // 只获取启用产品
|
||||
|
||||
if query.Keyword != "" {
|
||||
filters["keyword"] = query.Keyword
|
||||
}
|
||||
if query.CategoryID != "" {
|
||||
filters["category_id"] = query.CategoryID
|
||||
}
|
||||
|
||||
// 设置分页选项
|
||||
options := interfaces.ListOptions{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
Sort: "created_at",
|
||||
Order: "desc",
|
||||
}
|
||||
|
||||
// 获取产品列表
|
||||
products, total, err := s.productManagementService.ListProducts(ctx, filters, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = *s.convertToProductInfoResponse(products[i])
|
||||
}
|
||||
|
||||
return &responses.ProductListResponse{
|
||||
Total: total,
|
||||
Page: options.Page,
|
||||
Size: options.PageSize,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertToProductInfoResponse 转换为产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
response := &responses.ProductInfoResponse{
|
||||
@@ -195,7 +359,7 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible,
|
||||
IsPackage: product.IsPackage,
|
||||
@@ -211,6 +375,21 @@ func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *en
|
||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||
}
|
||||
|
||||
// 转换组合包项目信息
|
||||
if product.IsPackage && len(product.PackageItems) > 0 {
|
||||
response.PackageItems = make([]*responses.PackageItemResponse, len(product.PackageItems))
|
||||
for i, item := range product.PackageItems {
|
||||
response.PackageItems[i] = &responses.PackageItemResponse{
|
||||
ID: item.ID,
|
||||
ProductID: item.ProductID,
|
||||
ProductCode: item.Product.Code,
|
||||
ProductName: item.Product.Name,
|
||||
SortOrder: item.SortOrder,
|
||||
Price: item.Product.Price.InexactFloat64(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
@@ -224,4 +403,24 @@ func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *
|
||||
CreatedAt: category.CreatedAt,
|
||||
UpdatedAt: category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// GetProductApiConfig 获取产品API配置
|
||||
func (s *ProductApplicationServiceImpl) GetProductApiConfig(ctx context.Context, productID string) (*responses.ProductApiConfigResponse, error) {
|
||||
return s.productApiConfigAppService.GetProductApiConfig(ctx, productID)
|
||||
}
|
||||
|
||||
// CreateProductApiConfig 创建产品API配置
|
||||
func (s *ProductApplicationServiceImpl) CreateProductApiConfig(ctx context.Context, productID string, config *responses.ProductApiConfigResponse) error {
|
||||
return s.productApiConfigAppService.CreateProductApiConfig(ctx, productID, config)
|
||||
}
|
||||
|
||||
// UpdateProductApiConfig 更新产品API配置
|
||||
func (s *ProductApplicationServiceImpl) UpdateProductApiConfig(ctx context.Context, configID string, config *responses.ProductApiConfigResponse) error {
|
||||
return s.productApiConfigAppService.UpdateProductApiConfig(ctx, configID, config)
|
||||
}
|
||||
|
||||
// DeleteProductApiConfig 删除产品API配置
|
||||
func (s *ProductApplicationServiceImpl) DeleteProductApiConfig(ctx context.Context, configID string) error {
|
||||
return s.productApiConfigAppService.DeleteProductApiConfig(ctx, configID)
|
||||
}
|
||||
@@ -4,12 +4,15 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
)
|
||||
|
||||
@@ -41,7 +44,7 @@ func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context
|
||||
}
|
||||
|
||||
// 2. 更新订阅价格
|
||||
subscription.Price = cmd.Price
|
||||
subscription.Price = decimal.NewFromFloat(cmd.Price)
|
||||
|
||||
// 3. 保存订阅
|
||||
// 这里需要扩展领域服务来支持更新操作
|
||||
@@ -70,13 +73,26 @@ func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Con
|
||||
// ListSubscriptions 获取订阅列表
|
||||
// 业务流程:1. 获取订阅列表 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
|
||||
// 这里需要扩展领域服务来支持列表查询
|
||||
// 暂时返回空列表
|
||||
repoQuery := &repoQueries.ListSubscriptionsQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
}
|
||||
subscriptions, total, err := s.productSubscriptionService.ListSubscriptions(ctx, repoQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i := range subscriptions {
|
||||
resp := s.convertToSubscriptionInfoResponse(subscriptions[i])
|
||||
if resp != nil {
|
||||
items[i] = *resp // 解引用指针
|
||||
}
|
||||
}
|
||||
return &responses.SubscriptionListResponse{
|
||||
Total: 0,
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: []responses.SubscriptionInfoResponse{},
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -137,7 +153,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(s
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price,
|
||||
Price: subscription.Price.InexactFloat64(),
|
||||
Product: s.convertToProductSimpleResponse(subscription.Product),
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
@@ -151,7 +168,8 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Price: product.Price,
|
||||
Price: product.Price.InexactFloat64(),
|
||||
Category: s.convertToCategorySimpleResponse(product.Category),
|
||||
IsPackage: product.IsPackage,
|
||||
}
|
||||
}
|
||||
|
||||
31
internal/application/user/dto/queries/list_users_query.go
Normal file
31
internal/application/user/dto/queries/list_users_query.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/user/repositories/queries"
|
||||
|
||||
// ListUsersQuery 用户列表查询DTO
|
||||
type ListUsersQuery struct {
|
||||
Page int `json:"page" validate:"min=1"`
|
||||
PageSize int `json:"page_size" validate:"min=1,max=100"`
|
||||
Phone string `json:"phone"`
|
||||
UserType string `json:"user_type"` // 用户类型: user/admin
|
||||
IsActive *bool `json:"is_active"` // 是否激活
|
||||
IsCertified *bool `json:"is_certified"` // 是否已认证
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ToDomainQuery 转换为领域查询对象
|
||||
func (q *ListUsersQuery) ToDomainQuery() *queries.ListUsersQuery {
|
||||
return &queries.ListUsersQuery{
|
||||
Page: q.Page,
|
||||
PageSize: q.PageSize,
|
||||
Phone: q.Phone,
|
||||
UserType: q.UserType,
|
||||
IsActive: q.IsActive,
|
||||
IsCertified: q.IsCertified,
|
||||
CompanyName: q.CompanyName,
|
||||
StartDate: q.StartDate,
|
||||
EndDate: q.EndDate,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// UserListItem 用户列表项
|
||||
type UserListItem struct {
|
||||
ID string `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
UserType string `json:"user_type"`
|
||||
Username string `json:"username"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsCertified bool `json:"is_certified"`
|
||||
LoginCount int `json:"login_count"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 企业信息
|
||||
EnterpriseInfo *EnterpriseInfoItem `json:"enterprise_info,omitempty"`
|
||||
|
||||
// 钱包信息
|
||||
WalletBalance string `json:"wallet_balance,omitempty"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoItem 企业信息项
|
||||
type EnterpriseInfoItem struct {
|
||||
ID string `json:"id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonPhone string `json:"legal_person_phone"`
|
||||
EnterpriseAddress string `json:"enterprise_address"`
|
||||
EnterpriseEmail string `json:"enterprise_email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// UserListResponse 用户列表响应
|
||||
type UserListResponse struct {
|
||||
Items []*UserListItem `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// UserStatsResponse 用户统计响应
|
||||
type UserStatsResponse struct {
|
||||
TotalUsers int64 `json:"total_users"`
|
||||
ActiveUsers int64 `json:"active_users"`
|
||||
CertifiedUsers int64 `json:"certified_users"`
|
||||
}
|
||||
@@ -19,9 +19,9 @@ type EnterpriseInfoResponse struct {
|
||||
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"`
|
||||
LegalPersonPhone string `json:"legal_person_phone" example:"13800138000"`
|
||||
EnterpriseAddress string `json:"enterprise_address" example:"北京市朝阳区xxx街道xxx号"`
|
||||
EnterpriseEmail string `json:"enterprise_email" example:"contact@example.com"`
|
||||
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"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/user/dto/commands"
|
||||
"tyapi-server/internal/application/user/dto/queries"
|
||||
"tyapi-server/internal/application/user/dto/responses"
|
||||
)
|
||||
|
||||
@@ -16,4 +17,8 @@ type UserApplicationService interface {
|
||||
ResetPassword(ctx context.Context, cmd *commands.ResetPasswordCommand) error
|
||||
GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error)
|
||||
SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error
|
||||
|
||||
// 管理员功能
|
||||
ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error)
|
||||
GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"tyapi-server/internal/application/user/dto/commands"
|
||||
"tyapi-server/internal/application/user/dto/queries"
|
||||
"tyapi-server/internal/application/user/dto/responses"
|
||||
finance_service "tyapi-server/internal/domains/finance/services"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/events"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
@@ -19,33 +20,33 @@ import (
|
||||
// UserApplicationServiceImpl 用户应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type UserApplicationServiceImpl struct {
|
||||
userManagementService *user_service.UserManagementService
|
||||
userAuthService *user_service.UserAuthService
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
enterpriseService *user_service.EnterpriseService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
userAggregateService user_service.UserAggregateService
|
||||
userAuthService *user_service.UserAuthService
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
walletService finance_service.WalletAggregateService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserApplicationService 创建用户应用服务
|
||||
func NewUserApplicationService(
|
||||
userManagementService *user_service.UserManagementService,
|
||||
userAggregateService user_service.UserAggregateService,
|
||||
userAuthService *user_service.UserAuthService,
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
enterpriseService *user_service.EnterpriseService,
|
||||
walletService finance_service.WalletAggregateService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) UserApplicationService {
|
||||
return &UserApplicationServiceImpl{
|
||||
userManagementService: userManagementService,
|
||||
userAuthService: userAuthService,
|
||||
smsCodeService: smsCodeService,
|
||||
enterpriseService: enterpriseService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
userAggregateService: userAggregateService,
|
||||
userAuthService: userAuthService,
|
||||
smsCodeService: smsCodeService,
|
||||
walletService: walletService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands
|
||||
}
|
||||
|
||||
// 2. 创建用户
|
||||
user, err := s.userManagementService.CreateUser(ctx, cmd.Phone, cmd.Password)
|
||||
user, err := s.userAggregateService.CreateUser(ctx, cmd.Phone, cmd.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -95,11 +96,11 @@ func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd
|
||||
|
||||
// 3. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
|
||||
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
@@ -163,11 +164,11 @@ func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *comm
|
||||
|
||||
// 4. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
if err := s.userAggregateService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
|
||||
updatedUser, err := s.userAggregateService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
@@ -236,7 +237,7 @@ func (s *UserApplicationServiceImpl) ResetPassword(ctx context.Context, cmd *com
|
||||
// 业务流程:1. 获取用户信息 2. 获取企业信息 3. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error) {
|
||||
// 1. 获取用户信息(包含企业信息)
|
||||
user, err := s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
user, err := s.userAggregateService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,6 +259,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
@@ -273,6 +275,9 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: user.EnterpriseInfo.LegalPersonID,
|
||||
LegalPersonPhone: user.EnterpriseInfo.LegalPersonPhone,
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
EnterpriseEmail: user.EnterpriseInfo.EnterpriseEmail,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
UpdatedAt: user.EnterpriseInfo.UpdatedAt,
|
||||
}
|
||||
@@ -284,7 +289,7 @@ func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID
|
||||
// GetUser 获取用户信息
|
||||
// 业务流程:1. 获取用户信息 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries.GetUserQuery) (*responses.UserProfileResponse, error) {
|
||||
user, err := s.userManagementService.GetUserByID(ctx, query.UserID)
|
||||
user, err := s.userAggregateService.GetUserByID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -301,3 +306,78 @@ func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表(管理员功能)
|
||||
// 业务流程:1. 查询用户列表 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) ListUsers(ctx context.Context, query *queries.ListUsersQuery) (*responses.UserListResponse, error) {
|
||||
// 1. 查询用户列表
|
||||
users, total, err := s.userAggregateService.ListUsers(ctx, query.ToDomainQuery())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 构建响应数据
|
||||
items := make([]*responses.UserListItem, 0, len(users))
|
||||
for _, user := range users {
|
||||
item := &responses.UserListItem{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
UserType: user.UserType,
|
||||
Username: user.Username,
|
||||
IsActive: user.Active,
|
||||
IsCertified: user.IsCertified,
|
||||
LoginCount: user.LoginCount,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加企业信息
|
||||
if user.EnterpriseInfo != nil {
|
||||
item.EnterpriseInfo = &responses.EnterpriseInfoItem{
|
||||
ID: user.EnterpriseInfo.ID,
|
||||
CompanyName: user.EnterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
|
||||
LegalPersonPhone: user.EnterpriseInfo.LegalPersonPhone,
|
||||
EnterpriseAddress: user.EnterpriseInfo.EnterpriseAddress,
|
||||
EnterpriseEmail: user.EnterpriseInfo.EnterpriseEmail,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// 添加钱包余额信息
|
||||
wallet, err := s.walletService.LoadWalletByUserId(ctx, user.ID)
|
||||
if err == nil && wallet != nil {
|
||||
item.WalletBalance = wallet.Balance.String()
|
||||
} else {
|
||||
item.WalletBalance = "0"
|
||||
}
|
||||
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
return &responses.UserListResponse{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserStats 获取用户统计信息(管理员功能)
|
||||
// 业务流程:1. 查询用户统计信息 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUserStats(ctx context.Context) (*responses.UserStatsResponse, error) {
|
||||
// 1. 查询用户统计信息
|
||||
stats, err := s.userAggregateService.GetUserStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 构建响应数据
|
||||
return &responses.UserStatsResponse{
|
||||
TotalUsers: stats.TotalUsers,
|
||||
ActiveUsers: stats.ActiveUsers,
|
||||
CertifiedUsers: stats.CertifiedUsers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type Config struct {
|
||||
Cache CacheConfig `mapstructure:"cache"`
|
||||
Logger LoggerConfig `mapstructure:"logger"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
API APIConfig `mapstructure:"api"`
|
||||
SMS SMSConfig `mapstructure:"sms"`
|
||||
Storage StorageConfig `mapstructure:"storage"`
|
||||
OCR OCRConfig `mapstructure:"ocr"`
|
||||
@@ -23,6 +24,12 @@ type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
WechatWork WechatWorkConfig `mapstructure:"wechat_work"`
|
||||
Esign EsignConfig `mapstructure:"esign"`
|
||||
Wallet WalletConfig `mapstructure:"wallet"`
|
||||
WestDex WestDexConfig `mapstructure:"westdex"`
|
||||
AliPay AliPayConfig `mapstructure:"alipay"`
|
||||
Recharge RechargeConfig `mapstructure:"recharge"`
|
||||
Yushan YushanConfig `mapstructure:"yushan"`
|
||||
Domain DomainConfig `mapstructure:"domain"`
|
||||
}
|
||||
|
||||
// ServerConfig HTTP服务器配置
|
||||
@@ -141,6 +148,11 @@ type AppConfig struct {
|
||||
Env string `mapstructure:"env"`
|
||||
}
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
Domain string `mapstructure:"domain"`
|
||||
}
|
||||
|
||||
// SMSConfig 短信配置
|
||||
type SMSConfig struct {
|
||||
AccessKeyID string `mapstructure:"access_key_id"`
|
||||
@@ -218,11 +230,10 @@ type EsignConfig struct {
|
||||
AppSecret string `mapstructure:"app_secret"` // 应用密钥
|
||||
ServerURL string `mapstructure:"server_url"` // 服务器URL
|
||||
TemplateID string `mapstructure:"template_id"` // 模板ID
|
||||
|
||||
|
||||
Contract ContractConfig `mapstructure:"contract"` // 合同配置
|
||||
Auth AuthConfig `mapstructure:"auth"` // 认证配置
|
||||
Sign SignConfig `mapstructure:"sign"` // 签署配置
|
||||
Notify NotifyConfig `mapstructure:"notify"` // 通知配置
|
||||
}
|
||||
|
||||
// ContractConfig 合同配置
|
||||
@@ -234,10 +245,11 @@ type ContractConfig struct {
|
||||
|
||||
// AuthConfig 认证配置
|
||||
type AuthConfig struct {
|
||||
OrgAuthModes []string `mapstructure:"org_auth_modes"` // 机构可用认证模式
|
||||
DefaultAuthMode string `mapstructure:"default_auth_mode"` // 默认认证模式
|
||||
PsnAuthModes []string `mapstructure:"psn_auth_modes"` // 个人可用认证模式
|
||||
OrgAuthModes []string `mapstructure:"org_auth_modes"` // 机构可用认证模式
|
||||
DefaultAuthMode string `mapstructure:"default_auth_mode"` // 默认认证模式
|
||||
PsnAuthModes []string `mapstructure:"psn_auth_modes"` // 个人可用认证模式
|
||||
WillingnessAuthModes []string `mapstructure:"willingness_auth_modes"` // 意愿认证模式
|
||||
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
|
||||
}
|
||||
|
||||
// SignConfig 签署配置
|
||||
@@ -245,10 +257,46 @@ type SignConfig struct {
|
||||
AutoFinish bool `mapstructure:"auto_finish"` // 是否自动完结
|
||||
SignFieldStyle int `mapstructure:"sign_field_style"` // 签署区样式
|
||||
ClientType string `mapstructure:"client_type"` // 客户端类型
|
||||
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
|
||||
}
|
||||
|
||||
// NotifyConfig 通知配置
|
||||
type NotifyConfig struct {
|
||||
Types string `mapstructure:"types"` // 通知类型
|
||||
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
|
||||
// WalletConfig 钱包配置
|
||||
type WalletConfig struct {
|
||||
DefaultCreditLimit float64 `mapstructure:"default_credit_limit"`
|
||||
}
|
||||
|
||||
// WestDexConfig WestDex配置
|
||||
type WestDexConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
Key string `mapstructure:"key"`
|
||||
SecretId string `mapstructure:"secret_id"`
|
||||
SecretSecondId string `mapstructure:"secret_second_id"`
|
||||
}
|
||||
|
||||
// AliPayConfig 支付宝配置
|
||||
type AliPayConfig struct {
|
||||
AppID string `mapstructure:"app_id"`
|
||||
PrivateKey string `mapstructure:"private_key"`
|
||||
AlipayPublicKey string `mapstructure:"alipay_public_key"`
|
||||
IsProduction bool `mapstructure:"is_production"`
|
||||
NotifyURL string `mapstructure:"notify_url"`
|
||||
ReturnURL string `mapstructure:"return_url"`
|
||||
}
|
||||
|
||||
// RechargeConfig 充值配置
|
||||
type RechargeConfig struct {
|
||||
MinAmount string `mapstructure:"min_amount"` // 最低充值金额
|
||||
MaxAmount string `mapstructure:"max_amount"` // 最高充值金额
|
||||
}
|
||||
|
||||
// YushanConfig 羽山配置
|
||||
type YushanConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
AcctID string `mapstructure:"acct_id"`
|
||||
}
|
||||
|
||||
// DomainConfig 域名配置
|
||||
type DomainConfig struct {
|
||||
API string `mapstructure:"api"` // API域名
|
||||
}
|
||||
@@ -26,25 +26,35 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 100 * time.Millisecond,
|
||||
|
||||
|
||||
// 配置启用缓存的表
|
||||
EnabledTables: []string{
|
||||
"users",
|
||||
"products",
|
||||
"product_categories",
|
||||
"product",
|
||||
"product_category",
|
||||
"enterprise_info_submit_records",
|
||||
"sms_codes",
|
||||
"wallets",
|
||||
"subscription",
|
||||
"product_category",
|
||||
"product_documentation",
|
||||
"enterprise_infos",
|
||||
"api_users",
|
||||
// 添加更多需要缓存的表
|
||||
},
|
||||
|
||||
|
||||
// 配置禁用缓存的表(日志表等)
|
||||
DisabledTables: []string{
|
||||
"sms_codes", // 短信验证码变化频繁
|
||||
"audit_logs", // 审计日志
|
||||
"system_logs", // 系统日志
|
||||
"operation_logs", // 操作日志
|
||||
"audit_logs", // 审计日志
|
||||
"system_logs", // 系统日志
|
||||
"operation_logs", // 操作日志
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
// 创建缓存插件
|
||||
cachePlugin := cache.NewGormCachePlugin(cacheService, logger, cacheConfig)
|
||||
|
||||
@@ -54,7 +64,7 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("GORM缓存插件已成功注册",
|
||||
logger.Info("GORM缓存插件已成功注册",
|
||||
zap.Duration("default_ttl", cacheConfig.DefaultTTL),
|
||||
zap.Strings("enabled_tables", cacheConfig.EnabledTables),
|
||||
zap.Strings("disabled_tables", cacheConfig.DisabledTables),
|
||||
@@ -67,52 +77,67 @@ func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *conf
|
||||
func GetCacheConfig(cfg *config.Config) cache.CacheConfig {
|
||||
// 生产环境配置
|
||||
if cfg.Server.Mode == "release" {
|
||||
return cache.CacheConfig{
|
||||
DefaultTTL: 60 * time.Minute, // 生产环境延长缓存时间
|
||||
cacheConfig := cache.CacheConfig{
|
||||
DefaultTTL: 60 * time.Minute, // 生产环境延长缓存时间
|
||||
TablePrefix: "prod_cache",
|
||||
MaxCacheSize: 5000, // 生产环境增加缓存大小
|
||||
CacheComplexSQL: false, // 生产环境不缓存复杂SQL
|
||||
MaxCacheSize: 5000, // 生产环境增加缓存大小
|
||||
CacheComplexSQL: false, // 生产环境不缓存复杂SQL
|
||||
EnableStats: true,
|
||||
EnableWarmup: true,
|
||||
PenetrationGuard: true,
|
||||
BloomFilter: true, // 生产环境启用布隆过滤器
|
||||
BloomFilter: true, // 生产环境启用布隆过滤器
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 50 * time.Millisecond,
|
||||
|
||||
|
||||
EnabledTables: []string{
|
||||
"users", "products", "product_categories",
|
||||
"enterprise_info_submit_records", "certifications",
|
||||
"product_documentations",
|
||||
"users",
|
||||
"product",
|
||||
"product_category",
|
||||
"enterprise_info_submit_records",
|
||||
"sms_codes",
|
||||
},
|
||||
|
||||
|
||||
DisabledTables: []string{
|
||||
"sms_codes", "audit_logs", "system_logs",
|
||||
"operation_logs", "sessions", "api_keys",
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
return cacheConfig
|
||||
}
|
||||
|
||||
// 开发环境配置
|
||||
return cache.CacheConfig{
|
||||
DefaultTTL: 10 * time.Minute, // 开发环境缩短缓存时间,便于测试
|
||||
cacheConfig := cache.CacheConfig{
|
||||
DefaultTTL: 10 * time.Minute, // 开发环境缩短缓存时间,便于测试
|
||||
TablePrefix: "dev_cache",
|
||||
MaxCacheSize: 500,
|
||||
CacheComplexSQL: true, // 开发环境允许缓存复杂SQL,便于调试
|
||||
CacheComplexSQL: true, // 开发环境允许缓存复杂SQL,便于调试
|
||||
EnableStats: true,
|
||||
EnableWarmup: false, // 开发环境关闭预热
|
||||
PenetrationGuard: false, // 开发环境关闭穿透保护
|
||||
EnableWarmup: false, // 开发环境关闭预热
|
||||
PenetrationGuard: false, // 开发环境关闭穿透保护
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 200 * time.Millisecond,
|
||||
|
||||
|
||||
EnabledTables: []string{
|
||||
"users", "products", "product_categories",
|
||||
"users",
|
||||
"product",
|
||||
"product_category",
|
||||
"enterprise_info_submit_records",
|
||||
"sms_codes",
|
||||
},
|
||||
|
||||
|
||||
DisabledTables: []string{
|
||||
"sms_codes", "audit_logs",
|
||||
"api_calls", // API调用日志表,变化频繁,不适合缓存
|
||||
},
|
||||
}
|
||||
|
||||
// 初始化全局缓存配置管理器
|
||||
cache.InitCacheConfigManager(cacheConfig)
|
||||
|
||||
return cacheConfig
|
||||
}
|
||||
|
||||
// CacheMetrics 缓存性能指标
|
||||
@@ -136,7 +161,7 @@ func GetCacheMetrics(cacheService interfaces.CacheService) (*CacheMetrics, error
|
||||
total := stats.Hits + stats.Misses
|
||||
hitRate := float64(0)
|
||||
missRate := float64(0)
|
||||
|
||||
|
||||
if total > 0 {
|
||||
hitRate = float64(stats.Hits) / float64(total) * 100
|
||||
missRate = float64(stats.Misses) / float64(total) * 100
|
||||
@@ -150,4 +175,4 @@ func GetCacheMetrics(cacheService interfaces.CacheService) (*CacheMetrics, error
|
||||
CacheSize: stats.Memory,
|
||||
CachedTables: int(stats.Keys),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"tyapi-server/internal/application/user"
|
||||
"tyapi-server/internal/config"
|
||||
domain_certification_repo "tyapi-server/internal/domains/certification/repositories"
|
||||
certification_service "tyapi-server/internal/domains/certification/services"
|
||||
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
finance_service "tyapi-server/internal/domains/finance/services"
|
||||
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
||||
@@ -28,6 +28,8 @@ import (
|
||||
"tyapi-server/internal/infrastructure/external/ocr"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
"tyapi-server/internal/infrastructure/http/handlers"
|
||||
"tyapi-server/internal/infrastructure/http/routes"
|
||||
shared_database "tyapi-server/internal/shared/database"
|
||||
@@ -41,9 +43,9 @@ import (
|
||||
"tyapi-server/internal/shared/metrics"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||
"tyapi-server/internal/shared/payment"
|
||||
"tyapi-server/internal/shared/resilience"
|
||||
"tyapi-server/internal/shared/saga"
|
||||
sharedStorage "tyapi-server/internal/shared/storage"
|
||||
"tyapi-server/internal/shared/tracing"
|
||||
"tyapi-server/internal/shared/validator"
|
||||
|
||||
@@ -51,6 +53,11 @@ import (
|
||||
user_repo "tyapi-server/internal/infrastructure/database/repositories/user"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
api_app "tyapi-server/internal/application/api"
|
||||
domain_api_repo "tyapi-server/internal/domains/api/repositories"
|
||||
api_service "tyapi-server/internal/domains/api/services"
|
||||
api_repo "tyapi-server/internal/infrastructure/database/repositories/api"
|
||||
)
|
||||
|
||||
// Container 应用容器
|
||||
@@ -156,7 +163,6 @@ func NewContainer() *Container {
|
||||
logger,
|
||||
)
|
||||
},
|
||||
fx.As(new(sharedStorage.StorageService)),
|
||||
),
|
||||
// OCR服务
|
||||
fx.Annotate(
|
||||
@@ -169,19 +175,49 @@ func NewContainer() *Container {
|
||||
},
|
||||
fx.As(new(sharedOCR.OCRService)),
|
||||
),
|
||||
// e签宝服务
|
||||
func(cfg *config.Config) *esign.Client {
|
||||
esignConfig, err := esign.NewConfig(
|
||||
// e签宝配置
|
||||
func(cfg *config.Config) (*esign.Config, error) {
|
||||
return esign.NewConfig(
|
||||
cfg.Esign.AppID,
|
||||
cfg.Esign.AppSecret,
|
||||
cfg.Esign.ServerURL,
|
||||
cfg.Esign.TemplateID,
|
||||
&esign.EsignContractConfig{
|
||||
Name: cfg.Esign.Contract.Name,
|
||||
ExpireDays: cfg.Esign.Contract.ExpireDays,
|
||||
RetryCount: cfg.Esign.Contract.RetryCount,
|
||||
},
|
||||
&esign.EsignAuthConfig{
|
||||
OrgAuthModes: cfg.Esign.Auth.OrgAuthModes,
|
||||
DefaultAuthMode: cfg.Esign.Auth.DefaultAuthMode,
|
||||
PsnAuthModes: cfg.Esign.Auth.PsnAuthModes,
|
||||
WillingnessAuthModes: cfg.Esign.Auth.WillingnessAuthModes,
|
||||
RedirectUrl: cfg.Esign.Auth.RedirectURL,
|
||||
},
|
||||
&esign.EsignSignConfig{
|
||||
AutoFinish: cfg.Esign.Sign.AutoFinish,
|
||||
SignFieldStyle: cfg.Esign.Sign.SignFieldStyle,
|
||||
ClientType: cfg.Esign.Sign.ClientType,
|
||||
RedirectUrl: cfg.Esign.Sign.RedirectURL,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("e签宝配置创建失败: %v", err))
|
||||
}
|
||||
},
|
||||
// e签宝服务
|
||||
func(esignConfig *esign.Config) *esign.Client {
|
||||
return esign.NewClient(esignConfig)
|
||||
},
|
||||
// 支付宝支付服务
|
||||
func(cfg *config.Config) *payment.AliPayService {
|
||||
config := payment.AlipayConfig{
|
||||
AppID: cfg.AliPay.AppID,
|
||||
PrivateKey: cfg.AliPay.PrivateKey,
|
||||
AlipayPublicKey: cfg.AliPay.AlipayPublicKey,
|
||||
IsProduction: cfg.AliPay.IsProduction,
|
||||
NotifyUrl: cfg.AliPay.NotifyURL,
|
||||
ReturnURL: cfg.AliPay.ReturnURL,
|
||||
}
|
||||
return payment.NewAliPayService(config)
|
||||
},
|
||||
),
|
||||
|
||||
// 高级特性模块
|
||||
@@ -210,6 +246,22 @@ func NewContainer() *Container {
|
||||
fx.Provide(
|
||||
sharedhttp.NewResponseBuilder,
|
||||
validator.NewRequestValidator,
|
||||
// WestDexService - 需要从配置中获取参数
|
||||
func(cfg *config.Config) *westdex.WestDexService {
|
||||
return westdex.NewWestDexService(
|
||||
cfg.WestDex.URL,
|
||||
cfg.WestDex.Key,
|
||||
cfg.WestDex.SecretId,
|
||||
cfg.WestDex.SecretSecondId,
|
||||
)
|
||||
},
|
||||
func(cfg *config.Config) *yushan.YushanService {
|
||||
return yushan.NewYushanService(
|
||||
cfg.Yushan.URL,
|
||||
cfg.Yushan.APIKey,
|
||||
cfg.Yushan.AcctID,
|
||||
)
|
||||
},
|
||||
sharedhttp.NewGinRouter,
|
||||
),
|
||||
|
||||
@@ -224,6 +276,7 @@ func NewContainer() *Container {
|
||||
middleware.NewJWTAuthMiddleware,
|
||||
middleware.NewOptionalAuthMiddleware,
|
||||
middleware.NewAdminAuthMiddleware,
|
||||
middleware.NewDomainAuthMiddleware,
|
||||
middleware.NewTraceIDMiddleware,
|
||||
middleware.NewErrorTrackingMiddleware,
|
||||
NewRequestBodyLoggerMiddlewareWrapper,
|
||||
@@ -236,16 +289,22 @@ func NewContainer() *Container {
|
||||
user_repo.NewGormUserRepository,
|
||||
fx.As(new(domain_user_repo.UserRepository)),
|
||||
),
|
||||
// 企业信息仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
user_repo.NewGormEnterpriseInfoRepository,
|
||||
fx.As(new(domain_user_repo.EnterpriseInfoRepository)),
|
||||
),
|
||||
|
||||
// 短信验证码仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
user_repo.NewGormSMSCodeRepository,
|
||||
fx.As(new(domain_user_repo.SMSCodeRepository)),
|
||||
),
|
||||
// 用户信息仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
user_repo.NewGormEnterpriseInfoRepository,
|
||||
fx.As(new(domain_user_repo.EnterpriseInfoRepository)),
|
||||
),
|
||||
// 合同信息仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
user_repo.NewGormContractInfoRepository,
|
||||
fx.As(new(domain_user_repo.ContractInfoRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 仓储层 - 认证域
|
||||
@@ -260,6 +319,11 @@ func NewContainer() *Container {
|
||||
certification_repo.NewGormCertificationQueryRepository,
|
||||
fx.As(new(domain_certification_repo.CertificationQueryRepository)),
|
||||
),
|
||||
// 企业信息提交记录仓储
|
||||
fx.Annotate(
|
||||
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
|
||||
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 仓储层 - 财务域
|
||||
@@ -269,10 +333,20 @@ func NewContainer() *Container {
|
||||
finance_repo.NewGormWalletRepository,
|
||||
fx.As(new(domain_finance_repo.WalletRepository)),
|
||||
),
|
||||
// 用户密钥仓储
|
||||
// 钱包交易记录仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormUserSecretsRepository,
|
||||
fx.As(new(domain_finance_repo.UserSecretsRepository)),
|
||||
finance_repo.NewGormWalletTransactionRepository,
|
||||
fx.As(new(domain_finance_repo.WalletTransactionRepository)),
|
||||
),
|
||||
// 充值记录仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormRechargeRecordRepository,
|
||||
fx.As(new(domain_finance_repo.RechargeRecordRepository)),
|
||||
),
|
||||
// 支付宝订单仓储
|
||||
fx.Annotate(
|
||||
finance_repo.NewGormAlipayOrderRepository,
|
||||
fx.As(new(domain_finance_repo.AlipayOrderRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -293,18 +367,50 @@ func NewContainer() *Container {
|
||||
product_repo.NewGormSubscriptionRepository,
|
||||
fx.As(new(domain_product_repo.SubscriptionRepository)),
|
||||
),
|
||||
// 产品API配置仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
product_repo.NewGormProductApiConfigRepository,
|
||||
fx.As(new(domain_product_repo.ProductApiConfigRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// API域仓储层
|
||||
fx.Provide(
|
||||
fx.Annotate(
|
||||
api_repo.NewGormApiUserRepository,
|
||||
fx.As(new(domain_api_repo.ApiUserRepository)),
|
||||
),
|
||||
fx.Annotate(
|
||||
api_repo.NewGormApiCallRepository,
|
||||
fx.As(new(domain_api_repo.ApiCallRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 领域服务
|
||||
fx.Provide(
|
||||
user_service.NewUserManagementService,
|
||||
user_service.NewUserAggregateService,
|
||||
user_service.NewUserAuthService,
|
||||
user_service.NewSMSCodeService,
|
||||
user_service.NewEnterpriseService,
|
||||
user_service.NewContractAggregateService,
|
||||
product_service.NewProductManagementService,
|
||||
product_service.NewProductSubscriptionService,
|
||||
// 认证域的领域服务已经整合到应用服务中
|
||||
finance_service.NewFinanceService,
|
||||
product_service.NewProductApiConfigService,
|
||||
finance_service.NewWalletAggregateService,
|
||||
finance_service.NewRechargeRecordService,
|
||||
certification_service.NewCertificationAggregateService,
|
||||
certification_service.NewEnterpriseInfoSubmitRecordService,
|
||||
),
|
||||
|
||||
// API域服务层
|
||||
fx.Provide(
|
||||
api_service.NewApiUserAggregateService,
|
||||
api_service.NewApiCallAggregateService,
|
||||
api_service.NewApiRequestService,
|
||||
),
|
||||
|
||||
// API域应用服务
|
||||
fx.Provide(
|
||||
api_app.NewApiApplicationService,
|
||||
),
|
||||
|
||||
// 应用服务
|
||||
@@ -329,6 +435,11 @@ func NewContainer() *Container {
|
||||
product.NewProductApplicationService,
|
||||
fx.As(new(product.ProductApplicationService)),
|
||||
),
|
||||
// 产品API配置应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewProductApiConfigApplicationService,
|
||||
fx.As(new(product.ProductApiConfigApplicationService)),
|
||||
),
|
||||
// 分类应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
product.NewCategoryApplicationService,
|
||||
@@ -353,6 +464,8 @@ func NewContainer() *Container {
|
||||
handlers.NewProductHandler,
|
||||
// 产品管理员HTTP处理器
|
||||
handlers.NewProductAdminHandler,
|
||||
// API Handler
|
||||
handlers.NewApiHandler,
|
||||
),
|
||||
|
||||
// 路由注册
|
||||
@@ -367,6 +480,8 @@ func NewContainer() *Container {
|
||||
routes.NewProductRoutes,
|
||||
// 产品管理员路由
|
||||
routes.NewProductAdminRoutes,
|
||||
// API路由
|
||||
routes.NewApiRoutes,
|
||||
),
|
||||
|
||||
// 应用生命周期
|
||||
@@ -445,12 +560,16 @@ func RegisterRoutes(
|
||||
financeRoutes *routes.FinanceRoutes,
|
||||
productRoutes *routes.ProductRoutes,
|
||||
productAdminRoutes *routes.ProductAdminRoutes,
|
||||
apiRoutes *routes.ApiRoutes,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) {
|
||||
router.SetupDefaultRoutes()
|
||||
|
||||
// 注册所有路由
|
||||
// api域名路由
|
||||
apiRoutes.Register(router)
|
||||
|
||||
// 所有域名路由路由
|
||||
userRoutes.Register(router)
|
||||
certificationRoutes.Register(router)
|
||||
financeRoutes.Register(router)
|
||||
|
||||
173
internal/domains/api/dto/api_request_dto.go
Normal file
173
internal/domains/api/dto/api_request_dto.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package dto
|
||||
|
||||
type FLXG3D56Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
TimeRange string `json:"time_range" validate:"omitempty,validTimeRange"` // 非必填字段
|
||||
}
|
||||
type FLXG75FEReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXG0V3BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXG0V4BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
}
|
||||
type FLXG54F5Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type FLXG162AReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXG0687Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type FLXG970FReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXG5876Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type FLXG9687Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXGC9D1Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXGCA3DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type FLXGDEC7Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type IVYZ385EReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type IVYZ5733Req struct {
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type IVYZ9363Req struct {
|
||||
ManName string `json:"man_name" validate:"required,min=1,validName"`
|
||||
ManIDCard string `json:"man_id_card" validate:"required,validIDCard"`
|
||||
WomanName string `json:"woman_name" validate:"required,min=1,validName"`
|
||||
WomanIDCard string `json:"woman_id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type JRZQ0A03Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQ4AA8Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQ8203Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type JRZQDBCEReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
BankCard string `json:"bank_card" validate:"required,validBankCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type QYGL2ACDReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
}
|
||||
type QYGL6F2DReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type QYGL45BDReq struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validName"`
|
||||
LegalPerson string `json:"legal_person" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type QYGL8261Req struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type QYGL8271Req struct {
|
||||
EntName string `json:"ent_name" validate:"required,min=1,validName"`
|
||||
EntCode string `json:"ent_code" validate:"required,validUSCI"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
}
|
||||
type QYGLB4C0Req struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
|
||||
type YYSY4B37Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type YYSY4B21Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
}
|
||||
type YYSY6F2EReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
MobileType string `json:"mobile_type" validate:"omitempty,validMobileType"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type YYSY09CDReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
MobileType string `json:"mobile_type" validate:"omitempty,validMobileType"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type IVYZ0b03Req struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
type YYSYBE08Req struct{
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type YYSYD50FReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
}
|
||||
type YYSYF7DBReq struct {
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
StartDate string `json:"start_date" validate:"required,validDate" encrypt:"false"`
|
||||
}
|
||||
type IVYZ9A2BReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
}
|
||||
|
||||
type COMB298YReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
}
|
||||
|
||||
type COMB86PMReq struct {
|
||||
IDCard string `json:"id_card" validate:"required,validIDCard"`
|
||||
Name string `json:"name" validate:"required,min=1,validName"`
|
||||
MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"`
|
||||
AuthDate string `json:"auth_date" validate:"required,validAuthDate" encrypt:"false"`
|
||||
}
|
||||
196
internal/domains/api/entities/api_call.go
Normal file
196
internal/domains/api/entities/api_call.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApiCallStatus API调用状态
|
||||
const (
|
||||
ApiCallStatusPending = "pending"
|
||||
ApiCallStatusSuccess = "success"
|
||||
ApiCallStatusFailed = "failed"
|
||||
)
|
||||
|
||||
// ApiCall错误类型常量定义,供各领域服务和应用层统一引用。
|
||||
// 使用时可通过 entities.ApiCallErrorInvalidAccess 方式获得,编辑器可自动补全和提示。
|
||||
// 错误类型与业务含义:
|
||||
//
|
||||
// ApiCallErrorInvalidAccess = "invalid_access" // 无效AccessId
|
||||
// ApiCallErrorFrozenAccount = "frozen_account" // 账户冻结
|
||||
// ApiCallErrorInvalidIP = "invalid_ip" // IP无效
|
||||
// ApiCallErrorArrears = "arrears" // 账户欠费
|
||||
// ApiCallErrorNotSubscribed = "not_subscribed" // 未订阅产品
|
||||
// ApiCallErrorProductNotFound = "product_not_found" // 产品不存在
|
||||
// ApiCallErrorProductDisabled = "product_disabled" // 产品已停用
|
||||
// ApiCallErrorSystem = "system_error" // 系统错误
|
||||
// ApiCallErrorDatasource = "datasource_error" // 数据源异常
|
||||
// ApiCallErrorInvalidParam = "invalid_param" // 参数不正确
|
||||
// ApiCallErrorDecryptFail = "decrypt_fail" // 解密失败
|
||||
const (
|
||||
ApiCallErrorInvalidAccess = "invalid_access" // 无效AccessId
|
||||
ApiCallErrorFrozenAccount = "frozen_account" // 账户冻结
|
||||
ApiCallErrorInvalidIP = "invalid_ip" // IP无效
|
||||
ApiCallErrorArrears = "arrears" // 账户欠费
|
||||
ApiCallErrorNotSubscribed = "not_subscribed" // 未订阅产品
|
||||
ApiCallErrorProductNotFound = "product_not_found" // 产品不存在
|
||||
ApiCallErrorProductDisabled = "product_disabled" // 产品已停用
|
||||
ApiCallErrorSystem = "system_error" // 系统错误
|
||||
ApiCallErrorDatasource = "datasource_error" // 数据源异常
|
||||
ApiCallErrorInvalidParam = "invalid_param" // 参数不正确
|
||||
ApiCallErrorDecryptFail = "decrypt_fail" // 解密失败
|
||||
ApiCallErrorQueryEmpty = "query_empty" // 查询为空
|
||||
)
|
||||
|
||||
// ApiCall API调用(聚合根)
|
||||
type ApiCall struct {
|
||||
ID string `gorm:"type:varchar(64);primaryKey" json:"id"`
|
||||
AccessId string `gorm:"type:varchar(64);not null;index" json:"access_id"`
|
||||
UserId *string `gorm:"type:varchar(36);index" json:"user_id,omitempty"`
|
||||
ProductId *string `gorm:"type:varchar(64);index" json:"product_id,omitempty"`
|
||||
TransactionId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"transaction_id"`
|
||||
ClientIp string `gorm:"type:varchar(64);not null;index" json:"client_ip"`
|
||||
RequestParams string `gorm:"type:text" json:"request_params"`
|
||||
ResponseData *string `gorm:"type:text" json:"response_data,omitempty"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"`
|
||||
StartAt time.Time `gorm:"not null;index" json:"start_at"`
|
||||
EndAt *time.Time `gorm:"index" json:"end_at,omitempty"`
|
||||
Cost *decimal.Decimal `gorm:"default:0" json:"cost,omitempty"`
|
||||
ErrorType *string `gorm:"type:varchar(32)" json:"error_type,omitempty"`
|
||||
ErrorMsg *string `gorm:"type:varchar(256)" json:"error_msg,omitempty"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
||||
// NewApiCall 工厂方法
|
||||
func NewApiCall(accessId, requestParams, clientIp string) (*ApiCall, error) {
|
||||
if accessId == "" {
|
||||
return nil, errors.New("AccessId不能为空")
|
||||
}
|
||||
if requestParams == "" {
|
||||
return nil, errors.New("请求参数不能为空")
|
||||
}
|
||||
if clientIp == "" {
|
||||
return nil, errors.New("ClientIp不能为空")
|
||||
}
|
||||
|
||||
return &ApiCall{
|
||||
ID: uuid.New().String(),
|
||||
AccessId: accessId,
|
||||
TransactionId: GenerateTransactionID(),
|
||||
ClientIp: clientIp,
|
||||
RequestParams: requestParams,
|
||||
Status: ApiCallStatusPending,
|
||||
StartAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarkSuccess 标记为成功
|
||||
func (a *ApiCall) MarkSuccess(responseData string, cost decimal.Decimal) error {
|
||||
// 校验除ErrorMsg和ErrorType外所有字段不能为空
|
||||
if a.ID == "" || a.AccessId == "" || a.TransactionId == "" || a.RequestParams == "" || a.Status == "" || a.StartAt.IsZero() {
|
||||
return errors.New("ApiCall字段不能为空(除ErrorMsg和ErrorType)")
|
||||
}
|
||||
// 可选字段也要有值
|
||||
if a.UserId == nil || a.ProductId == nil || responseData == "" {
|
||||
return errors.New("ApiCall标记成功时UserId、ProductId、ResponseData不能为空")
|
||||
}
|
||||
a.Status = ApiCallStatusSuccess
|
||||
a.ResponseData = &responseData
|
||||
endAt := time.Now()
|
||||
a.EndAt = &endAt
|
||||
a.Cost = &cost
|
||||
a.ErrorType = nil
|
||||
a.ErrorMsg = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkFailed 标记为失败
|
||||
func (a *ApiCall) MarkFailed(errorType, errorMsg string) {
|
||||
a.Status = ApiCallStatusFailed
|
||||
a.ErrorType = &errorType
|
||||
shortMsg := errorMsg
|
||||
if len(shortMsg) > 120 {
|
||||
shortMsg = shortMsg[:120]
|
||||
}
|
||||
a.ErrorMsg = &shortMsg
|
||||
endAt := time.Now()
|
||||
a.EndAt = &endAt
|
||||
}
|
||||
|
||||
// Validate 校验ApiCall聚合根的业务规则
|
||||
func (a *ApiCall) Validate() error {
|
||||
if a.ID == "" {
|
||||
return errors.New("ID不能为空")
|
||||
}
|
||||
if a.AccessId == "" {
|
||||
return errors.New("AccessId不能为空")
|
||||
}
|
||||
if a.TransactionId == "" {
|
||||
return errors.New("TransactionId不能为空")
|
||||
}
|
||||
if a.RequestParams == "" {
|
||||
return errors.New("请求参数不能为空")
|
||||
}
|
||||
if a.Status != ApiCallStatusPending && a.Status != ApiCallStatusSuccess && a.Status != ApiCallStatusFailed {
|
||||
return errors.New("无效的调用状态")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 全局计数器,用于确保TransactionID的唯一性
|
||||
var (
|
||||
transactionCounter int64
|
||||
counterMutex sync.Mutex
|
||||
)
|
||||
|
||||
// GenerateTransactionID 生成16位数的交易单号
|
||||
func GenerateTransactionID() string {
|
||||
// 使用互斥锁确保计数器的线程安全
|
||||
counterMutex.Lock()
|
||||
transactionCounter++
|
||||
currentCounter := transactionCounter
|
||||
counterMutex.Unlock()
|
||||
|
||||
// 获取当前时间戳(微秒精度)
|
||||
timestamp := time.Now().UnixMicro()
|
||||
|
||||
// 组合时间戳和计数器,确保唯一性
|
||||
combined := fmt.Sprintf("%d%06d", timestamp, currentCounter%1000000)
|
||||
|
||||
// 如果长度超出16位,截断;如果不够,填充随机字符
|
||||
if len(combined) >= 16 {
|
||||
return combined[:16]
|
||||
}
|
||||
|
||||
// 如果长度不够,使用随机字节填充
|
||||
if len(combined) < 16 {
|
||||
randomBytes := make([]byte, 8)
|
||||
rand.Read(randomBytes)
|
||||
randomHex := hex.EncodeToString(randomBytes)
|
||||
combined += randomHex[:16-len(combined)]
|
||||
}
|
||||
|
||||
return combined
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (ApiCall) TableName() string {
|
||||
return "api_calls"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *ApiCall) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
193
internal/domains/api/entities/api_user.go
Normal file
193
internal/domains/api/entities/api_user.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApiUserStatus API用户状态
|
||||
const (
|
||||
ApiUserStatusNormal = "normal"
|
||||
ApiUserStatusFrozen = "frozen"
|
||||
)
|
||||
|
||||
// ApiUser API用户(聚合根)
|
||||
type ApiUser struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"id"`
|
||||
UserId string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_id"`
|
||||
AccessId string `gorm:"type:varchar(64);not null;uniqueIndex" json:"access_id"`
|
||||
SecretKey string `gorm:"type:varchar(128);not null" json:"secret_key"`
|
||||
Status string `gorm:"type:varchar(20);not null;default:'normal'" json:"status"`
|
||||
WhiteList []string `gorm:"type:json;serializer:json;default:'[]'" json:"white_list"` // 支持多个白名单
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
||||
// IsWhiteListed 校验IP/域名是否在白名单
|
||||
func (u *ApiUser) IsWhiteListed(target string) bool {
|
||||
for _, w := range u.WhiteList {
|
||||
if w == target {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsActive 是否可用
|
||||
func (u *ApiUser) IsActive() bool {
|
||||
return u.Status == ApiUserStatusNormal
|
||||
}
|
||||
|
||||
// IsFrozen 是否冻结
|
||||
func (u *ApiUser) IsFrozen() bool {
|
||||
return u.Status == ApiUserStatusFrozen
|
||||
}
|
||||
|
||||
// NewApiUser 工厂方法
|
||||
func NewApiUser(userId string) (*ApiUser, error) {
|
||||
if userId == "" {
|
||||
return nil, errors.New("用户ID不能为空")
|
||||
}
|
||||
accessId, err := GenerateSecretId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secretKey, err := GenerateSecretKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ApiUser{
|
||||
ID: uuid.New().String(),
|
||||
UserId: userId,
|
||||
AccessId: accessId,
|
||||
SecretKey: secretKey,
|
||||
Status: ApiUserStatusNormal,
|
||||
WhiteList: []string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 领域行为
|
||||
func (u *ApiUser) Freeze() {
|
||||
u.Status = ApiUserStatusFrozen
|
||||
}
|
||||
func (u *ApiUser) Unfreeze() {
|
||||
u.Status = ApiUserStatusNormal
|
||||
}
|
||||
func (u *ApiUser) UpdateWhiteList(list []string) {
|
||||
u.WhiteList = list
|
||||
}
|
||||
|
||||
// AddToWhiteList 新增白名单项(防御性校验)
|
||||
func (u *ApiUser) AddToWhiteList(entry string) error {
|
||||
if len(u.WhiteList) >= 10 {
|
||||
return errors.New("白名单最多只能有10个")
|
||||
}
|
||||
if net.ParseIP(entry) == nil {
|
||||
return errors.New("非法IP")
|
||||
}
|
||||
for _, w := range u.WhiteList {
|
||||
if w == entry {
|
||||
return errors.New("白名单已存在")
|
||||
}
|
||||
}
|
||||
u.WhiteList = append(u.WhiteList, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeUpdate GORM钩子:更新前确保WhiteList不为nil
|
||||
func (u *ApiUser) BeforeUpdate(tx *gorm.DB) error {
|
||||
if u.WhiteList == nil {
|
||||
u.WhiteList = []string{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveFromWhiteList 删除白名单项
|
||||
func (u *ApiUser) RemoveFromWhiteList(entry string) error {
|
||||
newList := make([]string, 0, len(u.WhiteList))
|
||||
for _, w := range u.WhiteList {
|
||||
if w != entry {
|
||||
newList = append(newList, w)
|
||||
}
|
||||
}
|
||||
if len(newList) == len(u.WhiteList) {
|
||||
return errors.New("白名单不存在")
|
||||
}
|
||||
u.WhiteList = newList
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate 校验ApiUser聚合根的业务规则
|
||||
func (u *ApiUser) Validate() error {
|
||||
if u.UserId == "" {
|
||||
return errors.New("用户ID不能为空")
|
||||
}
|
||||
if u.AccessId == "" {
|
||||
return errors.New("AccessId不能为空")
|
||||
}
|
||||
if u.SecretKey == "" {
|
||||
return errors.New("SecretKey不能为空")
|
||||
}
|
||||
switch u.Status {
|
||||
case ApiUserStatusNormal, ApiUserStatusFrozen:
|
||||
// ok
|
||||
default:
|
||||
return errors.New("无效的用户状态")
|
||||
}
|
||||
if len(u.WhiteList) > 10 {
|
||||
return errors.New("白名单最多只能有10个")
|
||||
}
|
||||
for _, ip := range u.WhiteList {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return errors.New("白名单项必须为合法IP地址: " + ip)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 生成AES-128密钥的函数,符合市面规范
|
||||
func GenerateSecretKey() (string, error) {
|
||||
key := make([]byte, 16) // 16字节密钥
|
||||
_, err := io.ReadFull(rand.Reader, key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(key), nil
|
||||
}
|
||||
|
||||
func GenerateSecretId() (string, error) {
|
||||
// 创建一个字节数组,用于存储随机数据
|
||||
bytes := make([]byte, 8) // 因为每个字节表示两个16进制字符
|
||||
|
||||
// 读取随机字节到数组中
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将字节数组转换为16进制字符串
|
||||
return hex.EncodeToString(bytes), nil
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (ApiUser) TableName() string {
|
||||
return "api_users"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID并确保WhiteList不为nil
|
||||
func (c *ApiUser) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
if c.WhiteList == nil {
|
||||
c.WhiteList = []string{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
29
internal/domains/api/repositories/api_call_repository.go
Normal file
29
internal/domains/api/repositories/api_call_repository.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
type ApiCallRepository interface {
|
||||
Create(ctx context.Context, call *entities.ApiCall) error
|
||||
Update(ctx context.Context, call *entities.ApiCall) error
|
||||
FindById(ctx context.Context, id string) (*entities.ApiCall, error)
|
||||
FindByUserId(ctx context.Context, userId string, limit, offset int) ([]*entities.ApiCall, error)
|
||||
|
||||
// 新增:分页查询用户API调用记录
|
||||
ListByUserId(ctx context.Context, userId string, options interfaces.ListOptions) ([]*entities.ApiCall, int64, error)
|
||||
|
||||
// 新增:根据条件筛选API调用记录
|
||||
ListByUserIdWithFilters(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.ApiCall, int64, error)
|
||||
|
||||
// 新增:根据条件筛选API调用记录(包含产品名称)
|
||||
ListByUserIdWithFiltersAndProductName(ctx context.Context, userId string, filters map[string]interface{}, options interfaces.ListOptions) (map[string]string, []*entities.ApiCall, int64, error)
|
||||
|
||||
// 新增:统计用户API调用次数
|
||||
CountByUserId(ctx context.Context, userId string) (int64, error)
|
||||
|
||||
// 新增:根据TransactionID查询
|
||||
FindByTransactionId(ctx context.Context, transactionId string) (*entities.ApiCall, error)
|
||||
}
|
||||
13
internal/domains/api/repositories/api_user_repository.go
Normal file
13
internal/domains/api/repositories/api_user_repository.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
)
|
||||
|
||||
type ApiUserRepository interface {
|
||||
Create(ctx context.Context, user *entities.ApiUser) error
|
||||
Update(ctx context.Context, user *entities.ApiUser) error
|
||||
FindByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error)
|
||||
FindByUserId(ctx context.Context, userId string) (*entities.ApiUser, error)
|
||||
}
|
||||
69
internal/domains/api/services/api_call_aggregate_service.go
Normal file
69
internal/domains/api/services/api_call_aggregate_service.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
repo "tyapi-server/internal/domains/api/repositories"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ApiCallAggregateService 聚合服务,管理ApiCall生命周期
|
||||
type ApiCallAggregateService interface {
|
||||
CreateApiCall(accessId, requestParams, clientIp string) (*entities.ApiCall, error)
|
||||
LoadApiCall(ctx context.Context, id string) (*entities.ApiCall, error)
|
||||
SaveApiCall(ctx context.Context, call *entities.ApiCall) error
|
||||
}
|
||||
|
||||
type ApiCallAggregateServiceImpl struct {
|
||||
apiUserRepo repo.ApiUserRepository
|
||||
apiCallRepo repo.ApiCallRepository
|
||||
}
|
||||
|
||||
func NewApiCallAggregateService(apiUserRepo repo.ApiUserRepository, apiCallRepo repo.ApiCallRepository) ApiCallAggregateService {
|
||||
return &ApiCallAggregateServiceImpl{
|
||||
apiUserRepo: apiUserRepo,
|
||||
apiCallRepo: apiCallRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// NewApiCall 创建ApiCall
|
||||
func (s *ApiCallAggregateServiceImpl) CreateApiCall(accessId, requestParams, clientIp string) (*entities.ApiCall, error) {
|
||||
return entities.NewApiCall(accessId, requestParams, clientIp)
|
||||
}
|
||||
|
||||
// GetApiCallById 查询ApiCall
|
||||
func (s *ApiCallAggregateServiceImpl) LoadApiCall(ctx context.Context, id string) (*entities.ApiCall, error) {
|
||||
return s.apiCallRepo.FindById(ctx, id)
|
||||
}
|
||||
|
||||
// SaveApiCall 保存ApiCall
|
||||
func (s *ApiCallAggregateServiceImpl) SaveApiCall(ctx context.Context, call *entities.ApiCall) error {
|
||||
// 先尝试查找现有记录
|
||||
existingCall, err := s.apiCallRepo.FindById(ctx, call.ID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
// 记录不存在,执行创建
|
||||
err = s.apiCallRepo.Create(ctx, call)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建ApiCall失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// 其他错误
|
||||
return fmt.Errorf("查询ApiCall失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录存在,执行更新
|
||||
if existingCall != nil {
|
||||
err = s.apiCallRepo.Update(ctx, call)
|
||||
if err != nil {
|
||||
return fmt.Errorf("更新ApiCall失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 理论上不会到达这里,但为了安全起见
|
||||
return s.apiCallRepo.Create(ctx, call)
|
||||
}
|
||||
135
internal/domains/api/services/api_request_service.go
Normal file
135
internal/domains/api/services/api_request_service.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/domains/api/services/processors/comb"
|
||||
"tyapi-server/internal/domains/api/services/processors/flxg"
|
||||
"tyapi-server/internal/domains/api/services/processors/ivyz"
|
||||
"tyapi-server/internal/domains/api/services/processors/jrzq"
|
||||
"tyapi-server/internal/domains/api/services/processors/qygl"
|
||||
"tyapi-server/internal/domains/api/services/processors/yysy"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDatasource = processors.ErrDatasource
|
||||
ErrSystem = processors.ErrSystem
|
||||
ErrInvalidParam = processors.ErrInvalidParam
|
||||
ErrNotFound = processors.ErrNotFound
|
||||
)
|
||||
|
||||
type ApiRequestService struct {
|
||||
// 可注入依赖,如第三方服务、模型等
|
||||
westDexService *westdex.WestDexService
|
||||
yushanService *yushan.YushanService
|
||||
validator interfaces.RequestValidator
|
||||
processorDeps *processors.ProcessorDependencies
|
||||
combService *comb.CombService
|
||||
}
|
||||
|
||||
func NewApiRequestService(
|
||||
westDexService *westdex.WestDexService,
|
||||
yushanService *yushan.YushanService,
|
||||
validator interfaces.RequestValidator,
|
||||
productManagementService *services.ProductManagementService,
|
||||
) *ApiRequestService {
|
||||
// 创建组合包服务
|
||||
combService := comb.NewCombService(productManagementService)
|
||||
|
||||
// 创建处理器依赖容器
|
||||
processorDeps := processors.NewProcessorDependencies(westDexService, yushanService, validator, combService)
|
||||
|
||||
// 统一注册所有处理器
|
||||
registerAllProcessors(combService)
|
||||
|
||||
return &ApiRequestService{
|
||||
westDexService: westDexService,
|
||||
yushanService: yushanService,
|
||||
validator: validator,
|
||||
processorDeps: processorDeps,
|
||||
combService: combService,
|
||||
}
|
||||
}
|
||||
|
||||
// registerAllProcessors 统一注册所有处理器
|
||||
func registerAllProcessors(combService *comb.CombService) {
|
||||
// 定义所有处理器映射
|
||||
processorMap := map[string]processors.ProcessorFunc{
|
||||
// FLXG系列处理器
|
||||
"FLXG0V3B": flxg.ProcessFLXG0V3Bequest,
|
||||
"FLXG0V4B": flxg.ProcessFLXG0V4BRequest,
|
||||
"FLXG162A": flxg.ProcessFLXG162ARequest,
|
||||
"FLXG3D56": flxg.ProcessFLXG3D56Request,
|
||||
"FLXG54F5": flxg.ProcessFLXG54F5Request,
|
||||
"FLXG5876": flxg.ProcessFLXG5876Request,
|
||||
"FLXG75FE": flxg.ProcessFLXG75FERequest,
|
||||
"FLXG9687": flxg.ProcessFLXG9687Request,
|
||||
"FLXG970F": flxg.ProcessFLXG970FRequest,
|
||||
"FLXGC9D1": flxg.ProcessFLXGC9D1Request,
|
||||
"FLXGCA3D": flxg.ProcessFLXGCA3DRequest,
|
||||
"FLXGDEC7": flxg.ProcessFLXGDEC7Request,
|
||||
|
||||
// JRZQ系列处理器
|
||||
"JRZQ8203": jrzq.ProcessJRZQ8203Request,
|
||||
"JRZQ0A03": jrzq.ProcessJRZQ0A03Request,
|
||||
"JRZQ4AA8": jrzq.ProcessJRZQ4AA8Request,
|
||||
"JRZQDCBE": jrzq.ProcessJRZQDCBERequest,
|
||||
|
||||
// QYGL系列处理器
|
||||
"QYGL8261": qygl.ProcessQYGL8261Request,
|
||||
"QYGL2ACD": qygl.ProcessQYGL2ACDRequest,
|
||||
"QYGL45BD": qygl.ProcessQYGL45BDRequest,
|
||||
"QYGL6F2D": qygl.ProcessQYGL6F2DRequest,
|
||||
"QYGL8271": qygl.ProcessQYGL8271Request,
|
||||
"QYGLB4C0": qygl.ProcessQYGLB4C0Request,
|
||||
|
||||
// YYSY系列处理器
|
||||
"YYSYD50F": yysy.ProcessYYSYD50FRequest,
|
||||
"YYSY09CD": yysy.ProcessYYSY09CDRequest,
|
||||
"YYSY4B21": yysy.ProcessYYSY4B21Request,
|
||||
"YYSY4B37": yysy.ProcessYYSY4B37Request,
|
||||
"YYSY6F2E": yysy.ProcessYYSY6F2ERequest,
|
||||
"YYSYBE08": yysy.ProcessYYSYBE08Request,
|
||||
"YYSYF7DB": yysy.ProcessYYSYF7DBRequest,
|
||||
|
||||
// IVYZ系列处理器
|
||||
"IVYZ0B03": ivyz.ProcessIVYZ0B03Request,
|
||||
"IVYZ2125": ivyz.ProcessIVYZ2125Request,
|
||||
"IVYZ385E": ivyz.ProcessIVYZ385ERequest,
|
||||
"IVYZ5733": ivyz.ProcessIVYZ5733Request,
|
||||
"IVYZ9363": ivyz.ProcessIVYZ9363Request,
|
||||
"IVYZ9A2B": ivyz.ProcessIVYZ9A2BRequest,
|
||||
"IVYZADEE": ivyz.ProcessIVYZADEERequest,
|
||||
|
||||
// COMB系列处理器
|
||||
"COMB298Y": comb.ProcessCOMB298YRequest,
|
||||
}
|
||||
|
||||
// 批量注册到组合包服务
|
||||
for apiCode, processor := range processorMap {
|
||||
combService.RegisterProcessor(apiCode, processor)
|
||||
}
|
||||
|
||||
// 同时设置全局处理器映射
|
||||
RequestProcessors = processorMap
|
||||
}
|
||||
|
||||
// 注册API处理器 - 现在通过registerAllProcessors统一管理
|
||||
var RequestProcessors map[string]processors.ProcessorFunc
|
||||
|
||||
// PreprocessRequestApi 调用指定的请求处理函数
|
||||
func (a *ApiRequestService) PreprocessRequestApi(ctx context.Context, apiCode string, params []byte, options *commands.ApiCallOptions) ([]byte, error) {
|
||||
if processor, exists := RequestProcessors[apiCode]; exists {
|
||||
// 设置Options到依赖容器
|
||||
depsWithOptions := a.processorDeps.WithOptions(options)
|
||||
return processor(ctx, params, depsWithOptions)
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %s", ErrSystem, "api请求, 未找到相应的处理程序")
|
||||
}
|
||||
124
internal/domains/api/services/api_user_aggregate_service.go
Normal file
124
internal/domains/api/services/api_user_aggregate_service.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/api/entities"
|
||||
repo "tyapi-server/internal/domains/api/repositories"
|
||||
)
|
||||
|
||||
type ApiUserAggregateService interface {
|
||||
CreateApiUser(ctx context.Context, apiUserId string) error
|
||||
UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error
|
||||
AddToWhiteList(ctx context.Context, apiUserId string, entry string) error
|
||||
RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error
|
||||
FreezeApiUser(ctx context.Context, apiUserId string) error
|
||||
UnfreezeApiUser(ctx context.Context, apiUserId string) error
|
||||
LoadApiUserByUserId(ctx context.Context, apiUserId string) (*entities.ApiUser, error)
|
||||
LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error)
|
||||
SaveApiUser(ctx context.Context, apiUser *entities.ApiUser) error
|
||||
}
|
||||
|
||||
type ApiUserAggregateServiceImpl struct {
|
||||
repo repo.ApiUserRepository
|
||||
}
|
||||
|
||||
func NewApiUserAggregateService(repo repo.ApiUserRepository) ApiUserAggregateService {
|
||||
return &ApiUserAggregateServiceImpl{repo: repo}
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) CreateApiUser(ctx context.Context, apiUserId string) error {
|
||||
apiUser, err := entities.NewApiUser(apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := apiUser.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.repo.Create(ctx, apiUser)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) UpdateWhiteList(ctx context.Context, apiUserId string, whiteList []string) error {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiUser.UpdateWhiteList(whiteList)
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) AddToWhiteList(ctx context.Context, apiUserId string, entry string) error {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = apiUser.AddToWhiteList(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) RemoveFromWhiteList(ctx context.Context, apiUserId string, entry string) error {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = apiUser.RemoveFromWhiteList(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) FreezeApiUser(ctx context.Context, apiUserId string) error {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiUser.Freeze()
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) UnfreezeApiUser(ctx context.Context, apiUserId string) error {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiUser.Unfreeze()
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
}
|
||||
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) LoadApiUserByAccessId(ctx context.Context, accessId string) (*entities.ApiUser, error) {
|
||||
return s.repo.FindByAccessId(ctx, accessId)
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) LoadApiUserByUserId(ctx context.Context, apiUserId string) (*entities.ApiUser, error) {
|
||||
apiUser, err := s.repo.FindByUserId(ctx, apiUserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
}
|
||||
|
||||
return apiUser, nil
|
||||
}
|
||||
|
||||
func (s *ApiUserAggregateServiceImpl) SaveApiUser(ctx context.Context, apiUser *entities.ApiUser) error {
|
||||
exists, err := s.repo.FindByUserId(ctx, apiUser.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists != nil {
|
||||
// 确保WhiteList不为nil
|
||||
if apiUser.WhiteList == nil {
|
||||
apiUser.WhiteList = []string{}
|
||||
}
|
||||
return s.repo.Update(ctx, apiUser)
|
||||
} else {
|
||||
return s.repo.Create(ctx, apiUser)
|
||||
}
|
||||
}
|
||||
195
internal/domains/api/services/processors/OPTIONS_USAGE.md
Normal file
195
internal/domains/api/services/processors/OPTIONS_USAGE.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# Options 使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
Options 机制允许在 API 调用时传递额外的配置选项,这些选项会传递给所有处理器(包括组合包处理器和子处理器),实现灵活的配置控制。
|
||||
|
||||
## 架构设计
|
||||
|
||||
```
|
||||
ApiCallCommand.Options → api_application_service → api_request_service → 所有处理器
|
||||
↓
|
||||
组合包处理器 → 子处理器
|
||||
```
|
||||
|
||||
## Options 字段说明
|
||||
|
||||
```go
|
||||
type ApiCallOptions struct {
|
||||
Json bool `json:"json,omitempty"` // 是否返回JSON格式
|
||||
Debug bool `json:"debug,omitempty"` // 调试模式
|
||||
Timeout int `json:"timeout,omitempty"` // 超时时间(秒)
|
||||
RetryCount int `json:"retry_count,omitempty"` // 重试次数
|
||||
Async bool `json:"async,omitempty"` // 异步处理
|
||||
Priority int `json:"priority,omitempty"` // 优先级(1-10)
|
||||
Cache bool `json:"cache,omitempty"` // 是否使用缓存
|
||||
Compress bool `json:"compress,omitempty"` // 是否压缩响应
|
||||
Encrypt bool `json:"encrypt,omitempty"` // 是否加密响应
|
||||
Validate bool `json:"validate,omitempty"` // 是否严格验证
|
||||
LogLevel string `json:"log_level,omitempty"` // 日志级别
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 普通处理器中使用 Options
|
||||
|
||||
```go
|
||||
func ProcessYYSY4B37Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
// ... 参数验证 ...
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"mobile": paramsDto.Mobile,
|
||||
}
|
||||
|
||||
// 使用 Options 调整请求参数
|
||||
if deps.Options != nil {
|
||||
if deps.Options.Timeout > 0 {
|
||||
reqData["timeout"] = deps.Options.Timeout
|
||||
}
|
||||
|
||||
if deps.Options.RetryCount > 0 {
|
||||
reqData["retry_count"] = deps.Options.RetryCount
|
||||
}
|
||||
|
||||
if deps.Options.Debug {
|
||||
reqData["debug"] = true
|
||||
}
|
||||
}
|
||||
|
||||
return deps.YushanService.CallAPI("YYSY4B37", reqData)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 组合包处理器中使用 Options
|
||||
|
||||
```go
|
||||
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
// ... 参数验证 ...
|
||||
|
||||
// 根据 Options 调整组合策略
|
||||
if deps.Options != nil {
|
||||
if deps.Options.Priority > 0 {
|
||||
// 高优先级时调整处理策略
|
||||
fmt.Printf("组合包处理优先级: %d\n", deps.Options.Priority)
|
||||
}
|
||||
|
||||
if deps.Options.Debug {
|
||||
fmt.Printf("组合包调试模式开启\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Options 会自动传递给所有子处理器
|
||||
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB298Y")
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 客户端调用示例
|
||||
|
||||
```json
|
||||
{
|
||||
"data": "加密的请求数据",
|
||||
"options": {
|
||||
"debug": true,
|
||||
"timeout": 30,
|
||||
"retry_count": 3,
|
||||
"priority": 5,
|
||||
"cache": true,
|
||||
"log_level": "debug"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 空值检查
|
||||
始终检查 `deps.Options` 是否为 `nil`,避免空指针异常:
|
||||
|
||||
```go
|
||||
if deps.Options != nil {
|
||||
// 使用 Options
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 默认值处理
|
||||
为 Options 字段提供合理的默认值:
|
||||
|
||||
```go
|
||||
timeout := 30 // 默认30秒
|
||||
if deps.Options != nil && deps.Options.Timeout > 0 {
|
||||
timeout = deps.Options.Timeout
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 验证选项值
|
||||
对 Options 中的值进行验证:
|
||||
|
||||
```go
|
||||
if deps.Options != nil {
|
||||
if deps.Options.Priority < 1 || deps.Options.Priority > 10 {
|
||||
return nil, fmt.Errorf("优先级必须在1-10之间")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 日志记录
|
||||
在调试模式下记录 Options 信息:
|
||||
|
||||
```go
|
||||
if deps.Options != nil && deps.Options.Debug {
|
||||
log.Printf("处理器选项: %+v", deps.Options)
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展性
|
||||
|
||||
### 1. 添加新的 Options 字段
|
||||
在 `ApiCallOptions` 结构体中添加新字段:
|
||||
|
||||
```go
|
||||
type ApiCallOptions struct {
|
||||
// ... 现有字段 ...
|
||||
CustomField string `json:"custom_field,omitempty"` // 自定义字段
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 处理器特定选项
|
||||
可以为特定处理器创建专门的选项结构:
|
||||
|
||||
```go
|
||||
type YYSYOptions struct {
|
||||
ApiCallOptions
|
||||
YYSYSpecific bool `json:"yysy_specific,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能影响**: Options 传递会增加少量性能开销,但影响微乎其微
|
||||
2. **向后兼容**: 新增的 Options 字段应该使用 `omitempty` 标签
|
||||
3. **安全性**: 敏感配置不应该通过 Options 传递
|
||||
4. **文档化**: 新增 Options 字段时应该更新文档
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 启用调试模式
|
||||
```json
|
||||
{
|
||||
"options": {
|
||||
"debug": true,
|
||||
"log_level": "debug"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 查看 Options 传递
|
||||
在处理器中添加日志:
|
||||
|
||||
```go
|
||||
if deps.Options != nil {
|
||||
log.Printf("处理器 %s 收到选项: %+v", "YYSY4B37", deps.Options)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 组合包调试
|
||||
组合包处理器会自动将 Options 传递给所有子处理器,无需额外配置。
|
||||
155
internal/domains/api/services/processors/README.md
Normal file
155
internal/domains/api/services/processors/README.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# API处理器架构说明
|
||||
|
||||
## 概述
|
||||
|
||||
本目录实现了基于依赖注入容器的API处理器架构,支持灵活的依赖管理和组合调用模式。
|
||||
|
||||
## 架构模式
|
||||
|
||||
### 1. 依赖注入容器模式
|
||||
|
||||
#### ProcessorDependencies
|
||||
```go
|
||||
type ProcessorDependencies struct {
|
||||
WestDexService *westdex.WestDexService
|
||||
YushanService *yushan.YushanService
|
||||
Validator interfaces.RequestValidator
|
||||
}
|
||||
```
|
||||
|
||||
**优势:**
|
||||
- 统一的依赖管理
|
||||
- 类型安全的依赖注入
|
||||
- 易于测试和mock
|
||||
- 支持未来扩展新的服务
|
||||
|
||||
#### 处理器函数签名
|
||||
```go
|
||||
type ProcessorFunc func(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error)
|
||||
```
|
||||
|
||||
### 2. 组合处理器模式
|
||||
|
||||
#### CompositeProcessor
|
||||
专门用于处理组合包(COMB系列)的处理器,支持:
|
||||
- 动态注册其他处理器
|
||||
- 批量调用多个处理器
|
||||
- 结果组合和格式化
|
||||
|
||||
```go
|
||||
type CompositeProcessor struct {
|
||||
processors map[string]ProcessorFunc
|
||||
deps *ProcessorDependencies
|
||||
}
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
processors/
|
||||
├── dependencies.go # 依赖容器定义
|
||||
├── comb/
|
||||
│ ├── comb_processor.go # 组合处理器基类
|
||||
│ └── comb298y_processor.go # COMB298Y组合处理器
|
||||
├── flxg/ # FLXG系列处理器
|
||||
├── jrzq/ # JRZQ系列处理器
|
||||
├── qygl/ # QYGL系列处理器
|
||||
├── yysy/ # YYSY系列处理器
|
||||
└── ivyz/ # IVYZ系列处理器
|
||||
```
|
||||
|
||||
## 服务分配策略
|
||||
|
||||
### WestDexService
|
||||
- FLXG系列:使用WestDexService
|
||||
- JRZQ系列:使用WestDexService
|
||||
- IVYZ系列:使用WestDexService
|
||||
|
||||
### YushanService
|
||||
- QYGL系列:使用YushanService
|
||||
- YYSY系列:使用YushanService
|
||||
|
||||
### 组合包(COMB)
|
||||
- 调用多个其他处理器
|
||||
- 组合结果并返回统一格式
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 普通处理器
|
||||
```go
|
||||
func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error) {
|
||||
// 参数验证
|
||||
var paramsDto dto.FLXG0V3BequestReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("参数校验不正确: 解密后的数据格式错误")
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("参数校验不正确: %s", err.Error())
|
||||
}
|
||||
|
||||
// 调用服务
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
"mobile": paramsDto.Mobile,
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("FLXG0V3B", reqData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("调用外部服务失败: %s", err.Error())
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
```
|
||||
|
||||
### 组合处理器
|
||||
```go
|
||||
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error) {
|
||||
// 创建组合处理器
|
||||
compositeProcessor := NewCompositeProcessor(deps)
|
||||
|
||||
// 注册需要调用的处理器
|
||||
compositeProcessor.RegisterProcessor("FLXG0V3B", flxg.ProcessFLXG0V3Bequest)
|
||||
compositeProcessor.RegisterProcessor("JRZQ8203", jrzq.ProcessJRZQ8203Request)
|
||||
|
||||
// 调用并组合结果
|
||||
results := make(map[string]interface{})
|
||||
|
||||
flxgResult, err := compositeProcessor.CallProcessor(ctx, "FLXG0V3B", params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("调用FLXG0V3B处理器失败: %s", err.Error())
|
||||
}
|
||||
results["flxg0v3b"] = string(flxgResult)
|
||||
|
||||
// 返回组合结果
|
||||
return compositeProcessor.CombineResults(results)
|
||||
}
|
||||
```
|
||||
|
||||
## 扩展指南
|
||||
|
||||
### 添加新的服务依赖
|
||||
1. 在 `ProcessorDependencies` 中添加新字段
|
||||
2. 更新 `NewProcessorDependencies` 构造函数
|
||||
3. 在 `ApiRequestService` 中注入新服务
|
||||
|
||||
### 添加新的处理器
|
||||
1. 在对应目录下创建新的处理器文件
|
||||
2. 实现 `ProcessorFunc` 接口
|
||||
3. 在 `RequestProcessors` 映射中注册
|
||||
|
||||
### 添加新的组合包
|
||||
1. 在 `comb/` 目录下创建新的组合处理器
|
||||
2. 使用 `CompositeProcessor` 基类
|
||||
3. 注册需要调用的处理器并组合结果
|
||||
|
||||
## 优势
|
||||
|
||||
1. **解耦**:处理器与具体服务实现解耦
|
||||
2. **可测试**:易于进行单元测试和集成测试
|
||||
3. **可扩展**:支持添加新的服务和处理器
|
||||
4. **类型安全**:编译时检查依赖关系
|
||||
5. **组合支持**:灵活的组合调用模式
|
||||
6. **维护性**:清晰的代码结构和职责分离
|
||||
117
internal/domains/api/services/processors/UPDATE_SUMMARY.md
Normal file
117
internal/domains/api/services/processors/UPDATE_SUMMARY.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 处理器文件更新总结
|
||||
|
||||
## 更新概述
|
||||
|
||||
已成功将所有36个API处理器文件更新为使用新的依赖注入容器模式。
|
||||
|
||||
## 更新统计
|
||||
|
||||
### 已更新的处理器文件总数:36个
|
||||
|
||||
#### FLXG系列 (12个)
|
||||
- ✅ flxg0v3b_processor.go
|
||||
- ✅ flxg0v4b_processor.go
|
||||
- ✅ flxg162a_processor.go
|
||||
- ✅ flxg3d56_processor.go
|
||||
- ✅ flxg54f5_processor.go
|
||||
- ✅ flxg5876_processor.go
|
||||
- ✅ flxg75fe_processor.go
|
||||
- ✅ flxg9687_processor.go
|
||||
- ✅ flxg970f_processor.go
|
||||
- ✅ flxgc9d1_processor.go
|
||||
- ✅ flxgca3d_processor.go
|
||||
- ✅ flxgdec7_processor.go
|
||||
|
||||
#### JRZQ系列 (4个)
|
||||
- ✅ jrzq8203_processor.go
|
||||
- ✅ jrzq0a03_processor.go
|
||||
- ✅ jrzq4aa8_processor.go
|
||||
- ✅ jrzqdcbe_processor.go
|
||||
|
||||
#### QYGL系列 (6个)
|
||||
- ✅ qygl8261_processor.go
|
||||
- ✅ qygl2acd_processor.go
|
||||
- ✅ qygl45bd_processor.go
|
||||
- ✅ qygl6f2d_processor.go
|
||||
- ✅ qygl8271_processor.go
|
||||
- ✅ qyglb4c0_processor.go
|
||||
|
||||
#### YYSY系列 (7个)
|
||||
- ✅ yysyd50f_processor.go
|
||||
- ✅ yysy09cd_processor.go
|
||||
- ✅ yysy4b21_processor.go
|
||||
- ✅ yysy4b37_processor.go
|
||||
- ✅ yysy6f2e_processor.go
|
||||
- ✅ yysybe08_processor.go
|
||||
- ✅ yysyf7db_processor.go
|
||||
|
||||
#### IVYZ系列 (7个)
|
||||
- ✅ ivyz0b03_processor.go
|
||||
- ✅ ivyz2125_processor.go
|
||||
- ✅ ivyz385e_processor.go
|
||||
- ✅ ivyz5733_processor.go
|
||||
- ✅ ivyz9363_processor.go
|
||||
- ✅ ivyz9a2b_processor.go
|
||||
- ✅ ivyzadee_processor.go
|
||||
|
||||
#### COMB系列 (1个)
|
||||
- ✅ comb298y_processor.go (组合处理器)
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 1. 函数签名更新
|
||||
所有处理器函数的签名已从:
|
||||
```go
|
||||
func ProcessXXXRequest(ctx context.Context, params []byte, validator interfaces.RequestValidator) ([]byte, error)
|
||||
```
|
||||
更新为:
|
||||
```go
|
||||
func ProcessXXXRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error)
|
||||
```
|
||||
|
||||
### 2. 导入更新
|
||||
- 移除了 `"tyapi-server/internal/shared/interfaces"` 导入
|
||||
- 添加了 `"tyapi-server/internal/domains/api/services/processors"` 导入
|
||||
|
||||
### 3. 验证器调用更新
|
||||
- 从 `validator.ValidateStruct(paramsDto)`
|
||||
- 更新为 `deps.Validator.ValidateStruct(paramsDto)`
|
||||
|
||||
### 4. 服务调用实现
|
||||
根据API前缀分配不同的服务:
|
||||
|
||||
#### WestDexService (FLXG, JRZQ, IVYZ系列)
|
||||
```go
|
||||
respBytes, err := deps.WestDexService.CallAPI("API_CODE", reqData)
|
||||
```
|
||||
|
||||
#### YushanService (QYGL, YYSY系列)
|
||||
```go
|
||||
respBytes, err := deps.WestDexService.CallAPI("API_CODE", reqData)
|
||||
```
|
||||
|
||||
### 5. 组合处理器
|
||||
COMB298Y处理器实现了组合调用模式:
|
||||
- 使用 `CompositeProcessor` 基类
|
||||
- 动态注册其他处理器
|
||||
- 组合多个处理器的结果
|
||||
|
||||
## 架构优势
|
||||
|
||||
1. **统一依赖管理**:所有处理器通过 `ProcessorDependencies` 容器访问依赖
|
||||
2. **类型安全**:编译时检查依赖关系
|
||||
3. **易于测试**:可以轻松mock依赖进行单元测试
|
||||
4. **可扩展性**:新增服务只需在容器中添加
|
||||
5. **组合支持**:COMB系列支持灵活的组合调用
|
||||
6. **维护性**:清晰的代码结构和职责分离
|
||||
|
||||
## 编译验证
|
||||
|
||||
✅ 项目编译成功,无语法错误
|
||||
|
||||
## 下一步建议
|
||||
|
||||
1. **单元测试**:为各个处理器编写单元测试
|
||||
2. **集成测试**:测试实际的API调用流程
|
||||
3. **性能测试**:验证新架构的性能表现
|
||||
4. **文档完善**:补充API文档和使用说明
|
||||
@@ -0,0 +1,26 @@
|
||||
package comb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
)
|
||||
|
||||
// ProcessCOMB298YRequest COMB298Y API处理方法
|
||||
func ProcessCOMB298YRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.COMB298YReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 调用组合包服务处理请求
|
||||
// Options会自动传递给所有子处理器
|
||||
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB298Y")
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package comb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
)
|
||||
|
||||
// ProcessCOMB86PMRequest COMB86PM API处理方法
|
||||
func ProcessCOMB86PMRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.COMB86PMReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
// 调用组合包服务处理请求
|
||||
// Options会自动传递给所有子处理器
|
||||
return deps.CombService.ProcessCombRequest(ctx, params, deps, "COMB86PM")
|
||||
}
|
||||
166
internal/domains/api/services/processors/comb/comb_service.go
Normal file
166
internal/domains/api/services/processors/comb/comb_service.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package comb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
)
|
||||
|
||||
// CombService 组合包服务
|
||||
type CombService struct {
|
||||
productManagementService *services.ProductManagementService
|
||||
processorRegistry map[string]processors.ProcessorFunc
|
||||
}
|
||||
|
||||
// NewCombService 创建组合包服务
|
||||
func NewCombService(productManagementService *services.ProductManagementService) *CombService {
|
||||
return &CombService{
|
||||
productManagementService: productManagementService,
|
||||
processorRegistry: make(map[string]processors.ProcessorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterProcessor 注册处理器
|
||||
func (cs *CombService) RegisterProcessor(apiCode string, processor processors.ProcessorFunc) {
|
||||
cs.processorRegistry[apiCode] = processor
|
||||
}
|
||||
|
||||
// ProcessCombRequest 处理组合包请求 - 实现 CombServiceInterface
|
||||
func (cs *CombService) ProcessCombRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies, packageCode string) ([]byte, error) {
|
||||
// 1. 根据组合包code获取产品信息
|
||||
packageProduct, err := cs.productManagementService.GetProductByCode(ctx, packageCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取组合包信息失败: %s", err.Error())
|
||||
}
|
||||
|
||||
if !packageProduct.IsPackage {
|
||||
return nil, fmt.Errorf("产品 %s 不是组合包", packageCode)
|
||||
}
|
||||
|
||||
// 2. 获取组合包的所有子产品
|
||||
packageItems, err := cs.productManagementService.GetPackageItems(ctx, packageProduct.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取组合包子产品失败: %s", err.Error())
|
||||
}
|
||||
|
||||
if len(packageItems) == 0 {
|
||||
return nil, fmt.Errorf("组合包 %s 没有配置子产品", packageCode)
|
||||
}
|
||||
|
||||
// 3. 并发调用所有子产品的处理器
|
||||
results := cs.processSubProducts(ctx, params, deps, packageItems)
|
||||
|
||||
// 4. 组合结果
|
||||
return cs.combineResults(results)
|
||||
}
|
||||
|
||||
// processSubProducts 并发处理子产品
|
||||
func (cs *CombService) processSubProducts(
|
||||
ctx context.Context,
|
||||
params []byte,
|
||||
deps *processors.ProcessorDependencies,
|
||||
packageItems []*entities.ProductPackageItem,
|
||||
) []*SubProductResult {
|
||||
results := make([]*SubProductResult, 0, len(packageItems))
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
|
||||
// 并发处理每个子产品
|
||||
for _, item := range packageItems {
|
||||
wg.Add(1)
|
||||
go func(item *entities.ProductPackageItem) {
|
||||
defer wg.Done()
|
||||
|
||||
result := cs.processSingleSubProduct(ctx, params, deps, item)
|
||||
|
||||
mu.Lock()
|
||||
results = append(results, result)
|
||||
mu.Unlock()
|
||||
}(item)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// 按SortOrder排序
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
return results[i].SortOrder < results[j].SortOrder
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// processSingleSubProduct 处理单个子产品
|
||||
func (cs *CombService) processSingleSubProduct(
|
||||
ctx context.Context,
|
||||
params []byte,
|
||||
deps *processors.ProcessorDependencies,
|
||||
item *entities.ProductPackageItem,
|
||||
) *SubProductResult {
|
||||
result := &SubProductResult{
|
||||
ApiCode: item.Product.Code,
|
||||
SortOrder: item.SortOrder,
|
||||
Success: false,
|
||||
}
|
||||
|
||||
// 查找对应的处理器
|
||||
processor, exists := cs.processorRegistry[item.Product.Code]
|
||||
if !exists {
|
||||
result.Error = fmt.Sprintf("未找到处理器: %s", item.Product.Code)
|
||||
return result
|
||||
}
|
||||
|
||||
// 调用处理器
|
||||
respBytes, err := processor(ctx, params, deps)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var responseData interface{}
|
||||
if err := json.Unmarshal(respBytes, &responseData); err != nil {
|
||||
result.Error = fmt.Sprintf("解析响应失败: %s", err.Error())
|
||||
return result
|
||||
}
|
||||
|
||||
result.Success = true
|
||||
result.Data = responseData
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// combineResults 组合所有子产品的结果
|
||||
func (cs *CombService) combineResults(results []*SubProductResult) ([]byte, error) {
|
||||
// 构建组合结果
|
||||
combinedResult := &CombinedResult{
|
||||
Responses: results,
|
||||
}
|
||||
|
||||
// 序列化结果
|
||||
respBytes, err := json.Marshal(combinedResult)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化组合结果失败: %s", err.Error())
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// SubProductResult 子产品处理结果
|
||||
type SubProductResult struct {
|
||||
ApiCode string `json:"api_code"` // 子接口标识
|
||||
Data interface{} `json:"data"` // 子接口返回数据
|
||||
Success bool `json:"success"` // 是否成功
|
||||
Error string `json:"error,omitempty"` // 错误信息(仅在失败时)
|
||||
SortOrder int `json:"-"` // 排序字段,不输出到JSON
|
||||
}
|
||||
|
||||
// CombinedResult 组合结果
|
||||
type CombinedResult struct {
|
||||
Responses []*SubProductResult `json:"responses"` // 子接口响应列表
|
||||
}
|
||||
48
internal/domains/api/services/processors/dependencies.go
Normal file
48
internal/domains/api/services/processors/dependencies.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package processors
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/application/api/commands"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
"tyapi-server/internal/infrastructure/external/yushan"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CombServiceInterface 组合包服务接口
|
||||
type CombServiceInterface interface {
|
||||
ProcessCombRequest(ctx context.Context, params []byte, deps *ProcessorDependencies, packageCode string) ([]byte, error)
|
||||
}
|
||||
|
||||
// ProcessorDependencies 处理器依赖容器
|
||||
type ProcessorDependencies struct {
|
||||
WestDexService *westdex.WestDexService
|
||||
YushanService *yushan.YushanService
|
||||
Validator interfaces.RequestValidator
|
||||
CombService CombServiceInterface // Changed to interface to break import cycle
|
||||
Options *commands.ApiCallOptions // 添加Options支持
|
||||
}
|
||||
|
||||
// NewProcessorDependencies 创建处理器依赖容器
|
||||
func NewProcessorDependencies(
|
||||
westDexService *westdex.WestDexService,
|
||||
yushanService *yushan.YushanService,
|
||||
validator interfaces.RequestValidator,
|
||||
combService CombServiceInterface, // Changed to interface
|
||||
) *ProcessorDependencies {
|
||||
return &ProcessorDependencies{
|
||||
WestDexService: westDexService,
|
||||
YushanService: yushanService,
|
||||
Validator: validator,
|
||||
CombService: combService,
|
||||
Options: nil, // 初始化为nil,在调用时设置
|
||||
}
|
||||
}
|
||||
|
||||
// WithOptions 设置Options的便捷方法
|
||||
func (deps *ProcessorDependencies) WithOptions(options *commands.ApiCallOptions) *ProcessorDependencies {
|
||||
deps.Options = options
|
||||
return deps
|
||||
}
|
||||
|
||||
// ProcessorFunc 处理器函数类型定义
|
||||
type ProcessorFunc func(ctx context.Context, params []byte, deps *ProcessorDependencies) ([]byte, error)
|
||||
11
internal/domains/api/services/processors/errors.go
Normal file
11
internal/domains/api/services/processors/errors.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package processors
|
||||
|
||||
import "errors"
|
||||
|
||||
// 处理器相关错误类型
|
||||
var (
|
||||
ErrDatasource = errors.New("数据源异常")
|
||||
ErrSystem = errors.New("系统异常")
|
||||
ErrInvalidParam = errors.New("参数校验不正确")
|
||||
ErrNotFound = errors.New("查询为空")
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG0687Request FLXG0687 API处理方法
|
||||
func ProcessFLXG0687Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG0687Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"keyWord": paramsDto.IDCard,
|
||||
"type": 3,
|
||||
}
|
||||
|
||||
respBytes, err := deps.YushanService.CallAPI("RIS031", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG0V3Bequest FLXG0V3B API处理方法
|
||||
func ProcessFLXG0V3Bequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG0V3BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G34BJ03", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ProcessFLXG0V4BRequest FLXG0V4B API处理方法
|
||||
func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG0V4BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"inquired_auth": paramsDto.AuthDate,
|
||||
},
|
||||
}
|
||||
respBytes, err := deps.WestDexService.CallAPI("G22SC01", reqData)
|
||||
if err != nil {
|
||||
// 数据源错误
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
// 如果有返回内容,优先解析返回内容
|
||||
if respBytes != nil {
|
||||
if deps.Options.Json {
|
||||
parsed, parseErr := ParseJsonResponse(respBytes)
|
||||
if parseErr == nil {
|
||||
return parsed, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
}
|
||||
// 解析失败,返回原始内容和系统错误
|
||||
return respBytes, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
|
||||
}
|
||||
return respBytes, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
}
|
||||
// 没有返回内容,直接返回数据源错误
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
}
|
||||
// 其他系统错误
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
// 正常返回
|
||||
if deps.Options.Json {
|
||||
parsed, parseErr := ParseJsonResponse(respBytes)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, parseErr)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// ParseWestResponse 解析西部返回的响应数据(获取data字段后解析)
|
||||
// westResp: 西部返回的原始响应
|
||||
// Returns: 解析后的数据字节数组
|
||||
func ParseWestResponse(westResp []byte) ([]byte, error) {
|
||||
dataResult := gjson.GetBytes(westResp, "data")
|
||||
if !dataResult.Exists() {
|
||||
return nil, errors.New("data not found")
|
||||
}
|
||||
return ParseJsonResponse([]byte(dataResult.Raw))
|
||||
}
|
||||
|
||||
// ParseJsonResponse 直接解析JSON响应数据
|
||||
// jsonResp: JSON响应数据
|
||||
// Returns: 解析后的数据字节数组
|
||||
func ParseJsonResponse(jsonResp []byte) ([]byte, error) {
|
||||
parseResult, err := RecursiveParse(string(jsonResp))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultResp, marshalErr := json.Marshal(parseResult)
|
||||
if marshalErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resultResp, nil
|
||||
}
|
||||
|
||||
// RecursiveParse 递归解析JSON数据
|
||||
func RecursiveParse(data interface{}) (interface{}, error) {
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
var parsed interface{}
|
||||
if err := json.Unmarshal([]byte(v), &parsed); err == nil {
|
||||
return RecursiveParse(parsed)
|
||||
}
|
||||
return v, nil
|
||||
case map[string]interface{}:
|
||||
for key, val := range v {
|
||||
parsed, err := RecursiveParse(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[key] = parsed
|
||||
}
|
||||
return v, nil
|
||||
case []interface{}:
|
||||
for i, item := range v {
|
||||
parsed, err := RecursiveParse(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[i] = parsed
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG162ARequest FLXG162A API处理方法
|
||||
func ProcessFLXG162ARequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG162AReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G32BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG3D56Request FLXG3D56 API处理方法
|
||||
func ProcessFLXG3D56Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG3D56Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
encryptedTimeRange, err := deps.WestDexService.Encrypt(paramsDto.TimeRange)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
"time_range": encryptedTimeRange,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G26BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG54F5Request FLXG54F5 API处理方法
|
||||
func ProcessFLXG54F5Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG54F5Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"mobile": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G03HZ01", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG5876Request FLXG5876 API处理方法
|
||||
func ProcessFLXG5876Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG5876Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"phone": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G03XM02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG75FERequest FLXG75FE API处理方法
|
||||
func ProcessFLXG75FERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG75FEReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"name": paramsDto.Name,
|
||||
"idCard": paramsDto.IDCard,
|
||||
"mobile": paramsDto.MobileNo,
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("FLXG75FE", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG9687Request FLXG9687 API处理方法
|
||||
func ProcessFLXG9687Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG9687Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G31BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXG970FRequest FLXG970F API处理方法
|
||||
func ProcessFLXG970FRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXG970FReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"cardNo": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("WEST00028", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXGC9D1Request FLXGC9D1 API处理方法
|
||||
func ProcessFLXGC9D1Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXGC9D1Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G30BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXGCA3DRequest FLXGCA3D API处理方法
|
||||
func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXGCA3DReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G22BJ03", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package flxg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessFLXGDEC7Request FLXGDEC7 API处理方法
|
||||
func ProcessFLXGDEC7Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.FLXGDEC7Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id_card": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G23BJ03", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessIVYZ0B03Request IVYZ0B03 API处理方法
|
||||
func ProcessIVYZ0B03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ0b03Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"phone": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G17BJ02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
)
|
||||
|
||||
// ProcessIVYZ2125Request IVYZ2125 API处理方法
|
||||
func ProcessIVYZ2125Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, "服务已停用")
|
||||
// var paramsDto dto.IVYZ2125Req
|
||||
// if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
// }
|
||||
|
||||
// if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
// }
|
||||
|
||||
// reqData := map[string]interface{}{
|
||||
// "name": paramsDto.Name,
|
||||
// "idCard": paramsDto.IDCard,
|
||||
// "mobile": paramsDto.Mobile,
|
||||
// }
|
||||
|
||||
// respBytes, err := deps.WestDexService.CallAPI("IVYZ2125", reqData)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, westdex.ErrDatasource) {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
// } else {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessIVYZ385ERequest IVYZ385E API处理方法
|
||||
func ProcessIVYZ385ERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ385EReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"xm": encryptedName,
|
||||
"gmsfzhm": encryptedIDCard,
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("WEST00020", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessIVYZ5733Request IVYZ5733 API处理方法
|
||||
func ProcessIVYZ5733Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ5733Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idCard": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G09XM02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessIVYZ9363Request IVYZ9363 API处理方法
|
||||
func ProcessIVYZ9363Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ9363Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedManName, err := deps.WestDexService.Encrypt(paramsDto.ManName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedManIDCard, err := deps.WestDexService.Encrypt(paramsDto.ManIDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedWomanName, err := deps.WestDexService.Encrypt(paramsDto.WomanName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedWomanIDCard, err := deps.WestDexService.Encrypt(paramsDto.WomanIDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"nameMan": encryptedManName,
|
||||
"certNumMan": encryptedManIDCard,
|
||||
"nameWoman": encryptedWomanName,
|
||||
"certNumWoman": encryptedWomanIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G10SC02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessIVYZ9A2BRequest IVYZ9A2B API处理方法
|
||||
func ProcessIVYZ9A2BRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.IVYZ9A2BReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name_value": encryptedName,
|
||||
"id_card_value": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G11BJ06", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package ivyz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
)
|
||||
|
||||
// ProcessIVYZADEERequest IVYZADEE API处理方法
|
||||
func ProcessIVYZADEERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, "服务已停用")
|
||||
// var paramsDto dto.IVYZADEEReq
|
||||
// if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
// }
|
||||
|
||||
// if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
// }
|
||||
|
||||
// reqData := map[string]interface{}{
|
||||
// "name": paramsDto.Name,
|
||||
// "idCard": paramsDto.IDCard,
|
||||
// "mobile": paramsDto.Mobile,
|
||||
// }
|
||||
|
||||
// respBytes, err := deps.WestDexService.CallAPI("IVYZADEE", reqData)
|
||||
// if err != nil {
|
||||
// if errors.Is(err, westdex.ErrDatasource) {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
// } else {
|
||||
// return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQ0A03Request JRZQ0A03 API处理方法
|
||||
func ProcessJRZQ0A03Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ0A03Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G27BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQ4AA8Request JRZQ4AA8 API处理方法
|
||||
func ProcessJRZQ4AA8Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ4AA8Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G29BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQ8203Request JRZQ8203 API处理方法
|
||||
func ProcessJRZQ8203Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQ8203Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"id": encryptedIDCard,
|
||||
"cell": encryptedMobileNo,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G28BJ05", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package jrzq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessJRZQDCBERequest JRZQDCBE API处理方法
|
||||
func ProcessJRZQDCBERequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.JRZQDBCEReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedBankCard, err := deps.WestDexService.Encrypt(paramsDto.BankCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idcard": encryptedIDCard,
|
||||
"mobile": encryptedMobileNo,
|
||||
"acc_no": encryptedBankCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G20GZ01", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessQYGL2ACDRequest QYGL2ACD API处理方法
|
||||
func ProcessQYGL2ACDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL2ACDReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedLegalPerson, err := deps.WestDexService.Encrypt(paramsDto.LegalPerson)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"entname": encryptedEntName,
|
||||
"realname": encryptedLegalPerson,
|
||||
"idcard": encryptedEntCode,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("WEST00022", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessQYGL45BDRequest QYGL45BD API处理方法
|
||||
func ProcessQYGL45BDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL45BDReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedLegalPerson, err := deps.WestDexService.Encrypt(paramsDto.LegalPerson)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"entname": encryptedEntName,
|
||||
"realname": encryptedLegalPerson,
|
||||
"entmark": encryptedEntCode,
|
||||
"idcard": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("WEST00021", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessQYGL6F2DRequest QYGL6F2D API处理方法
|
||||
func ProcessQYGL6F2DRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL6F2DReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"idno": encryptedIDCard,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G05XM02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessQYGL8261Request QYGL8261 API处理方法
|
||||
func ProcessQYGL8261Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL8261Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"ent_name": encryptedEntName,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("Q03BJ03", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessQYGL8271Request QYGL8271 API处理方法
|
||||
func ProcessQYGL8271Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGL8271Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedEntName, err := deps.WestDexService.Encrypt(paramsDto.EntName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedEntCode, err := deps.WestDexService.Encrypt(paramsDto.EntCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"org_name": encryptedEntName,
|
||||
"uscc": encryptedEntCode,
|
||||
"inquired_auth": paramsDto.AuthDate, // AuthDate 有 encrypt:"false" 标签,不加密
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("Q03SC01", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package qygl
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// ProcessQYGLB4C0Request QYGLB4C0 API处理方法
|
||||
func ProcessQYGLB4C0Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.QYGLB4C0Req
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedIDCard := deps.WestDexService.Md5Encrypt(paramsDto.IDCard)
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"pid": encryptedIDCard,
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.G05HZ01CallAPI("G05HZ01", reqData)
|
||||
if err != nil {
|
||||
// 数据源错误
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
// 如果有返回内容,优先解析返回内容
|
||||
if respBytes != nil {
|
||||
var westData map[string]interface{}
|
||||
if err := json.Unmarshal(respBytes, &westData); err == nil {
|
||||
if code, ok := westData["code"].(string); ok && code == "1404" {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrNotFound, err)
|
||||
}
|
||||
}
|
||||
return respBytes, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
}
|
||||
// 没有返回内容,直接返回数据源错误
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
}
|
||||
// 其他系统错误
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
|
||||
// ParseWestResponse 解析西部返回的响应数据(获取data字段后解析)
|
||||
// westResp: 西部返回的原始响应
|
||||
// Returns: 解析后的数据字节数组
|
||||
func ParseWestResponse(westResp []byte) ([]byte, error) {
|
||||
dataResult := gjson.GetBytes(westResp, "data")
|
||||
if !dataResult.Exists() {
|
||||
return nil, errors.New("data not found")
|
||||
}
|
||||
return ParseJsonResponse([]byte(dataResult.Raw))
|
||||
}
|
||||
|
||||
// ParseJsonResponse 直接解析JSON响应数据
|
||||
// jsonResp: JSON响应数据
|
||||
// Returns: 解析后的数据字节数组
|
||||
func ParseJsonResponse(jsonResp []byte) ([]byte, error) {
|
||||
parseResult, err := RecursiveParse(string(jsonResp))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultResp, marshalErr := json.Marshal(parseResult)
|
||||
if marshalErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resultResp, nil
|
||||
}
|
||||
|
||||
// RecursiveParse 递归解析JSON数据
|
||||
func RecursiveParse(data interface{}) (interface{}, error) {
|
||||
switch v := data.(type) {
|
||||
case string:
|
||||
var parsed interface{}
|
||||
if err := json.Unmarshal([]byte(v), &parsed); err == nil {
|
||||
return RecursiveParse(parsed)
|
||||
}
|
||||
return v, nil
|
||||
case map[string]interface{}:
|
||||
for key, val := range v {
|
||||
parsed, err := RecursiveParse(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[key] = parsed
|
||||
}
|
||||
return v, nil
|
||||
case []interface{}:
|
||||
for i, item := range v {
|
||||
parsed, err := RecursiveParse(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v[i] = parsed
|
||||
}
|
||||
return v, nil
|
||||
default:
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package yysy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"tyapi-server/internal/domains/api/dto"
|
||||
"tyapi-server/internal/domains/api/services/processors"
|
||||
"tyapi-server/internal/infrastructure/external/westdex"
|
||||
)
|
||||
|
||||
// ProcessYYSY09CDRequest YYSY09CD API处理方法
|
||||
func ProcessYYSY09CDRequest(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) {
|
||||
var paramsDto dto.YYSY09CDReq
|
||||
if err := json.Unmarshal(params, ¶msDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, err)
|
||||
}
|
||||
|
||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedIDCard, err := deps.WestDexService.Encrypt(paramsDto.IDCard)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
encryptedMobileNo, err := deps.WestDexService.Encrypt(paramsDto.MobileNo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
|
||||
reqData := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"name": encryptedName,
|
||||
"idNo": encryptedIDCard,
|
||||
"phone": encryptedMobileNo,
|
||||
"phoneType": paramsDto.MobileType,
|
||||
},
|
||||
}
|
||||
|
||||
respBytes, err := deps.WestDexService.CallAPI("G16BJ02", reqData)
|
||||
if err != nil {
|
||||
if errors.Is(err, westdex.ErrDatasource) {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrDatasource, err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s: %w", processors.ErrSystem, err)
|
||||
}
|
||||
}
|
||||
|
||||
return respBytes, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user