charging-cabinet/tasks/014-mimo-review-fixes.md
2026-07-02 05:38:01 +08:00

285 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 任务014MIMO审查问题修复
## 目标
修复MIMO审查发现的5个严重问题和部分警告。
## 修复清单
### 1. list_users元组编译问题 高优先级)
**位置:** `src/routes/users.rs`
**问题:** `list_users` 使用7元素元组可能导致sqlx编译错误。
**修复方案:**
```rust
// 定义结构体替代元组
#[derive(sqlx::FromRow)]
struct UserRow {
id: i64,
phone: String,
name: Option<String>,
role: i32,
organization_id: Option<i64>,
status: i32,
created_at: chrono::NaiveDateTime,
}
// 查询时使用结构体
let users = sqlx::query_as::<_, UserRow>(
"SELECT id, phone, name, role, organization_id, status, created_at FROM users ..."
)
.fetch_all(pool)
.await?;
```
### 2. 充电记录COUNT查询缺少JOIN 高优先级)
**位置:** `src/routes/charge_records.rs`
**问题:** COUNT查询缺少`compartments`表JOIN分页总数计算错误。
**修复方案:**
```rust
// 修改COUNT查询添加JOIN
let total: (i64,) = sqlx::query_as(
r#"SELECT COUNT(*) FROM charge_records cr
INNER JOIN compartments c ON cr.compartment_id = c.id
INNER JOIN cabin_boards cb ON c.cabin_board_id = cb.id
INNER JOIN cabinets cab ON cb.cabinet_id = cab.id
WHERE cab.project_id = ?"# // 添加组织过滤
)
.bind(project_id)
.fetch_one(pool)
.await?;
```
### 3. H5后端接口实现🔴 高优先级)
**位置:** 新建 `src/routes/h5.rs`
**问题:** H5前端10+个接口均会404后端未实现。
**修复方案:**
```rust
// 新建 h5.rs 模块
use axum::extract::{Path, State};
use axum::Json;
use serde_json::{json, Value};
use crate::error::AppError;
use crate::middleware::auth::{self, CurrentUser};
use crate::tcp::commands::AppState;
/// GET /api/h5/dashboard — H5首页数据
pub async fn get_dashboard(
user: CurrentUser,
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
// 查询柜子统计、今日充电、告警等
// ...
}
/// GET /api/h5/projects — H5项目列表
pub async fn get_projects(
user: CurrentUser,
State(state): State<AppState>,
) -> Result<Json<Value>, AppError> {
// 查询用户有权访问的项目
// ...
}
/// GET /api/h5/cabinets — H5设备列表
pub async fn get_cabinets(
user: CurrentUser,
State(state): State<AppState>,
Path(project_id): Path<i64>,
) -> Result<Json<Value>, AppError> {
// 查询项目下的柜子
// ...
}
/// GET /api/h5/cabinets/:id — H5设备详情
pub async fn get_cabinet_detail(
user: CurrentUser,
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Json<Value>, AppError> {
// 查询柜子详情(仓控板+仓体)
// ...
}
/// POST /api/h5/charge/start — H5开始充电
pub async fn start_charge(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<StartChargeRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:start")?;
// 下发充电指令到设备
// ...
}
/// POST /api/h5/charge/stop — H5停止充电
pub async fn stop_charge(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<StopChargeRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:stop")?;
// 下发停止指令到设备
// ...
}
/// POST /api/h5/door/open — H5开门
pub async fn open_door(
user: CurrentUser,
State(state): State<AppState>,
Json(body): Json<OpenDoorRequest>,
) -> Result<Json<Value>, AppError> {
auth::check_permission(&user, "charge:open_door")?;
// 下发开门指令到设备
// ...
}
```
**路由注册:**`src/routes/mod.rs` 中添加:
```rust
mod h5;
// 在 build 函数中添加
router = router
.route("/api/h5/dashboard", get(h5::get_dashboard))
.route("/api/h5/projects", get(h5::get_projects))
.route("/api/h5/cabinets", get(h5::get_cabinets))
.route("/api/h5/cabinets/:id", get(h5::get_cabinet_detail))
.route("/api/h5/charge/start", post(h5::start_charge))
.route("/api/h5/charge/stop", post(h5::stop_charge))
.route("/api/h5/door/open", post(h5::open_door));
```
### 4. 文件下载端点实现(🔴 高优先级)
**位置:** `src/routes/downloads.rs`
**问题:** `/api/downloads/:id/file` 未实现,导出功能不完整。
**修复方案:**
```rust
use axum::body::Body;
use axum::http::header;
use axum::response::Response;
use tokio::fs;
/// GET /api/downloads/:id/file — 下载文件
pub async fn download_file(
user: CurrentUser,
State(state): State<AppState>,
Path(id): Path<i64>,
) -> Result<Response<Body>, AppError> {
auth::check_permission(&user, "charge_record:export")?;
let db = &state.mysql;
// 查询下载任务
let task: Option<DownloadTask> = sqlx::query_as(
"SELECT * FROM download_tasks WHERE id = ? AND user_id = ?"
)
.bind(id)
.bind(user.user_id)
.fetch_optional(db)
.await?;
let task = task.ok_or(AppError::NotFound("下载任务不存在".into()))?;
if task.status != 2 {
return Err(AppError::BadRequest("文件未生成完成".into()));
}
// 读取文件
let file_path = task.file_path.ok_or(AppError::NotFound("文件路径不存在".into()))?;
let file_content = fs::read(&file_path).await.map_err(|e| {
AppError::Internal(format!("读取文件失败: {}", e))
})?;
// 返回文件
let response = Response::builder()
.status(200)
.header(header::CONTENT_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
.header(header::CONTENT_DISPOSITION, format!("attachment; filename=\"{}\"", task.file_name))
.body(Body::from(file_content))
.map_err(|e| AppError::Internal(format!("构建响应失败: {}", e)))?;
Ok(response)
}
```
### 5. TCP auth_str频率限制🟡 中优先级)
**位置:** `src/tcp/handler.rs`
**问题:** `auth_str`接口无频率限制,可能被暴力调用。
**修复方案:**
```rust
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use std::time::{Duration, Instant};
// 频率限制器
pub struct RateLimiter {
attempts: Arc<RwLock<HashMap<String, Vec<Instant>>>>,
max_attempts: usize,
window: Duration,
}
impl RateLimiter {
pub fn new(max_attempts: usize, window_secs: u64) -> Self {
Self {
attempts: Arc::new(RwLock::new(HashMap::new())),
max_attempts,
window: Duration::from_secs(window_secs),
}
}
pub async fn check(&self, key: &str) -> bool {
let mut attempts = self.attempts.write().await;
let now = Instant::now();
let window_start = now - self.window;
// 清理过期记录
let entry = attempts.entry(key.to_string()).or_insert_with(Vec::new);
entry.retain(|t| *t > window_start);
if entry.len() >= self.max_attempts {
return false; // 超限
}
entry.push(now);
true
}
}
// 在 handler 中使用
lazy_static::lazy_static! {
static ref AUTH_STR_LIMITER: RateLimiter = RateLimiter::new(5, 300); // 5次/5分钟
}
// 在 auth_str handler 中
if !AUTH_STR_LIMITER.check(&dev_id).await {
return Err(AppError::TooManyRequests("请求过于频繁".into()));
}
```
## 质量约束
1. 完成代码后执行 `cargo check` + `cargo clippy` + `tsc --noEmit`,零报错零警告
2. 分层拆分单函数≤80行命名语义化完整注释
3. 所有外部IO/网络请求异常捕获禁止裸panic
4. 分支逻辑全覆盖,不遗漏兜底分支
5. 常量抽离不使用废弃API
6. Rust内存安全React函数组件+Hooks
7. H5接口必须添加权限校验