tangweijie d7f81893c5 Initial commit: 完整的 Rust 账户管理系统
- 实现账户管理改进设计文档中的所有核心功能
- 三科目余额管理 (个人余额、劳动报酬、冻结余额)
- 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed)
- 三键幂等体系 (JZTxId/BankTxId/SourceKey)
- 优先级扣款规则 (先个人后劳动)
- 在途资金管理 (可用→在途→结转/回退)
- 三账对账闭环 (总账 = 银行账 + 在途净额)
- 补偿服务域 (超时检测、重试、死信队列)
- 虚拟银行模拟器用于业务测试
- 完整的集成测试套件 (133 个测试全部通过)
- Docker 容器化部署配置
- 前端 Vue3 + TypeScript 项目结构
2026-01-05 17:56:01 +08:00

320 lines
11 KiB
Rust

//! 账户 API 处理器
use axum::{
extract::{Path, Query, State},
Json,
};
use crate::api::AppState;
use crate::application::dto::*;
use crate::domain::account::{CreatePhysicalAccountRequest, CreateVirtualSubAccountRequest, AccountType};
use crate::error::Result;
/// 创建实体账户
pub async fn create_physical_account(
State(state): State<AppState>,
Json(dto): Json<CreatePhysicalAccountDto>,
) -> Result<Json<SuccessResponse<PhysicalAccountResponse>>> {
let service = state.account_service();
let request = CreatePhysicalAccountRequest {
account_no: dto.account_no,
bank_code: dto.bank_code,
bank_name: dto.bank_name,
consistency_mode: dto.consistency_mode,
outbound_control: dto.outbound_control,
};
let account = service.create_physical_account(request).await?;
Ok(Json(SuccessResponse::new(PhysicalAccountResponse {
id: account.id,
account_no: account.account_no,
bank_code: account.bank_code,
bank_name: account.bank_name,
consistency_mode: account.consistency_mode,
outbound_control: account.outbound_control,
status: account.status,
created_at: account.created_at,
personal_balance: None,
labor_balance: None,
frozen_balance: None,
bank_balance: None,
transit_amount: None,
available_balance: None,
})))
}
/// 账户列表查询参数
#[derive(serde::Deserialize)]
pub struct ListAccountsQuery {
pub status: Option<String>,
pub keyword: Option<String>,
pub page: Option<u32>,
pub page_size: Option<u32>,
}
/// 获取实体账户列表
pub async fn list_physical_accounts(
State(state): State<AppState>,
Query(query): Query<ListAccountsQuery>,
) -> Result<Json<SuccessResponse<PageResponse<PhysicalAccountResponse>>>> {
let service = state.account_service();
let ledger_service = state.ledger_service();
let mut accounts = service.list_physical_accounts().await?;
// 筛选:按状态
if let Some(status_str) = &query.status {
if let Ok(status) = status_str.parse::<crate::domain::account::AccountStatus>() {
accounts.retain(|a| a.status == status);
}
}
// 筛选:按关键词(账号或银行名称)
if let Some(keyword) = &query.keyword {
let keyword_lower = keyword.to_lowercase();
accounts.retain(|a| {
a.account_no.to_lowercase().contains(&keyword_lower)
|| a.bank_name.as_ref().map(|n| n.to_lowercase().contains(&keyword_lower)).unwrap_or(false)
});
}
// 分页
let page = query.page.unwrap_or(1).max(1);
let page_size = query.page_size.unwrap_or(10).max(1).min(100);
let total = accounts.len() as i64;
let start = ((page - 1) * page_size) as usize;
let end = (start + page_size as usize).min(accounts.len());
let paged_accounts = if start < accounts.len() {
accounts[start..end].to_vec()
} else {
Vec::new()
};
// 转换为响应并获取余额信息
let mut responses = Vec::new();
for account in paged_accounts {
// 获取余额信息
let balance = ledger_service
.get_balance(account.id, AccountType::Physical)
.await
.ok();
responses.push(PhysicalAccountResponse {
id: account.id,
account_no: account.account_no,
bank_code: account.bank_code,
bank_name: account.bank_name,
consistency_mode: account.consistency_mode,
outbound_control: account.outbound_control,
status: account.status,
created_at: account.created_at,
personal_balance: balance.as_ref().map(|b| b.personal_balance),
labor_balance: balance.as_ref().map(|b| b.labor_balance),
frozen_balance: balance.as_ref().map(|b| b.frozen_balance),
bank_balance: balance.as_ref().map(|b| b.bank_balance),
transit_amount: balance.as_ref().map(|b| b.transit_amount),
available_balance: balance.as_ref().map(|b| b.available_balance),
});
}
Ok(Json(SuccessResponse::new(PageResponse::new(
responses,
total,
page as i64,
page_size as i64,
))))
}
/// 获取实体账户
pub async fn get_physical_account(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<SuccessResponse<PhysicalAccountResponse>>> {
let service = state.account_service();
let ledger_service = state.ledger_service();
let account = service.get_physical_account(id).await?;
// 获取余额信息
let balance = ledger_service
.get_balance(id, AccountType::Physical)
.await
.ok();
Ok(Json(SuccessResponse::new(PhysicalAccountResponse {
id: account.id,
account_no: account.account_no,
bank_code: account.bank_code,
bank_name: account.bank_name,
consistency_mode: account.consistency_mode,
outbound_control: account.outbound_control,
status: account.status,
created_at: account.created_at,
personal_balance: balance.as_ref().map(|b| b.personal_balance),
labor_balance: balance.as_ref().map(|b| b.labor_balance),
frozen_balance: balance.as_ref().map(|b| b.frozen_balance),
bank_balance: balance.as_ref().map(|b| b.bank_balance),
transit_amount: balance.as_ref().map(|b| b.transit_amount),
available_balance: balance.as_ref().map(|b| b.available_balance),
})))
}
/// 冻结实体账户余额
pub async fn freeze_physical_account(
State(state): State<AppState>,
Path(id): Path<i64>,
Json(dto): Json<FreezeAccountDto>,
) -> Result<Json<SuccessResponse<String>>> {
let ledger_service = state.ledger_service();
ledger_service.freeze_amount(id, AccountType::Physical, dto.amount).await?;
Ok(Json(SuccessResponse::new(format!("已冻结金额 {}", dto.amount))))
}
/// 解冻实体账户余额
pub async fn unfreeze_physical_account(
State(state): State<AppState>,
Path(id): Path<i64>,
Json(dto): Json<UnfreezeAccountDto>,
) -> Result<Json<SuccessResponse<String>>> {
let ledger_service = state.ledger_service();
ledger_service.unfreeze_amount(id, AccountType::Physical, dto.amount).await?;
Ok(Json(SuccessResponse::new(format!("已解冻金额 {}", dto.amount))))
}
/// 创建虚拟子账户
pub async fn create_sub_account(
State(state): State<AppState>,
Json(dto): Json<CreateVirtualSubAccountDto>,
) -> Result<Json<SuccessResponse<VirtualSubAccountResponse>>> {
let service = state.account_service();
let request = CreateVirtualSubAccountRequest {
physical_account_id: dto.physical_account_id,
account_code: dto.account_code,
account_type: dto.account_type,
valid_from: dto.valid_from,
valid_to: dto.valid_to,
};
let account = service.create_virtual_sub_account(request).await?;
Ok(Json(SuccessResponse::new(VirtualSubAccountResponse {
id: account.id,
physical_account_id: account.physical_account_id,
account_code: account.account_code,
account_type: account.account_type,
valid_from: account.valid_from,
valid_to: account.valid_to,
status: account.status,
balance: None,
created_at: account.created_at,
})))
}
/// 获取虚拟子账户
pub async fn get_sub_account(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<SuccessResponse<VirtualSubAccountResponse>>> {
let account_service = state.account_service();
let ledger_service = state.ledger_service();
let account = account_service.get_virtual_sub_account(id).await?;
let balance = ledger_service.get_balance(id, AccountType::Virtual).await?;
Ok(Json(SuccessResponse::new(VirtualSubAccountResponse {
id: account.id,
physical_account_id: account.physical_account_id,
account_code: account.account_code,
account_type: account.account_type,
valid_from: account.valid_from,
valid_to: account.valid_to,
status: account.status,
balance: Some(BalanceResponse {
system_balance: balance.system_balance,
bank_balance: balance.bank_balance,
available_balance: balance.available_balance,
frozen_amount: balance.frozen_amount,
transit_amount: balance.transit_amount,
}),
created_at: account.created_at,
})))
}
/// 获取子账户余额
pub async fn get_sub_account_balance(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<SuccessResponse<BalanceResponse>>> {
let ledger_service = state.ledger_service();
let balance = ledger_service.get_balance(id, AccountType::Virtual).await?;
Ok(Json(SuccessResponse::new(BalanceResponse {
system_balance: balance.system_balance,
bank_balance: balance.bank_balance,
available_balance: balance.available_balance,
frozen_amount: balance.frozen_amount,
transit_amount: balance.transit_amount,
})))
}
/// 冻结子账户余额
pub async fn freeze_sub_account(
State(state): State<AppState>,
Path(id): Path<i64>,
Json(dto): Json<FreezeAccountDto>,
) -> Result<Json<SuccessResponse<String>>> {
let ledger_service = state.ledger_service();
ledger_service.freeze_amount(id, AccountType::Virtual, dto.amount).await?;
Ok(Json(SuccessResponse::new(format!("已冻结金额 {}", dto.amount))))
}
/// 解冻子账户余额
pub async fn unfreeze_sub_account(
State(state): State<AppState>,
Path(id): Path<i64>,
Json(dto): Json<UnfreezeAccountDto>,
) -> Result<Json<SuccessResponse<String>>> {
let ledger_service = state.ledger_service();
ledger_service.unfreeze_amount(id, AccountType::Virtual, dto.amount).await?;
Ok(Json(SuccessResponse::new(format!("已解冻金额 {}", dto.amount))))
}
/// 销户
pub async fn close_sub_account(
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<SuccessResponse<String>>> {
let service = state.account_service();
service.close_sub_account(id).await?;
Ok(Json(SuccessResponse::new("子账户已销户".to_string())))
}
/// 获取实体账户下的子账户列表
pub async fn list_sub_accounts(
State(state): State<AppState>,
Path(physical_account_id): Path<i64>,
) -> Result<Json<SuccessResponse<Vec<VirtualSubAccountResponse>>>> {
let service = state.account_service();
let accounts = service.list_sub_accounts(physical_account_id).await?;
let responses: Vec<VirtualSubAccountResponse> = accounts
.into_iter()
.map(|a| VirtualSubAccountResponse {
id: a.id,
physical_account_id: a.physical_account_id,
account_code: a.account_code,
account_type: a.account_type,
valid_from: a.valid_from,
valid_to: a.valid_to,
status: a.status,
balance: None,
created_at: a.created_at,
})
.collect();
Ok(Json(SuccessResponse::new(responses)))
}