You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Axum与SQLx构建动态表数据API的实现方案问询

解决方案

核心思路

因为无法预先知晓表的结构,我们需要利用sqlx提供的动态值获取能力(try_get_raw)拿到sqlx::Value,再将其转换为serde_json::Value(serde_json的通用类型,可直接序列化为JSON),最终把每一行数据转成键值对的JSON对象,再组合成数组返回。

步骤1:实现行转JSON的工具函数

这个函数负责将单个sqlx::Row转换成serde_json::Value,处理所有可能的sqlx::Value类型:

use serde_json::{Map, Value as JsonValue};
use sqlx::Row;

fn row_to_json(row: &sqlx::Row) -> JsonValue {
    let mut json_map = Map::new();

    for col in row.columns() {
        // 用try_get_raw获取动态值,无需预先指定类型
        let sqlx_value = match row.try_get_raw(col.name()) {
            Ok(val) => val,
            Err(e) => {
                eprintln!("Failed to get column {}: {}", col.name(), e);
                json_map.insert(col.name().to_string(), JsonValue::Null);
                continue;
            }
        };

        // 将sqlx::Value转换为serde_json::Value
        let json_val = match sqlx_value {
            sqlx::Value::Null => JsonValue::Null,
            sqlx::Value::Bool(b) => JsonValue::Bool(b),
            sqlx::Value::I8(n) => JsonValue::Number(n.into()),
            sqlx::Value::I16(n) => JsonValue::Number(n.into()),
            sqlx::Value::I32(n) => JsonValue::Number(n.into()),
            sqlx::Value::I64(n) => JsonValue::Number(n.into()),
            sqlx::Value::U8(n) => JsonValue::Number(n.into()),
            sqlx::Value::U16(n) => JsonValue::Number(n.into()),
            sqlx::Value::U32(n) => JsonValue::Number(n.into()),
            sqlx::Value::U64(n) => JsonValue::Number(n.into()),
            sqlx::Value::F32(f) => JsonValue::Number(serde_json::Number::from_f64(f.into()).unwrap_or_default()),
            sqlx::Value::F64(f) => JsonValue::Number(serde_json::Number::from_f64(f).unwrap_or_default()),
            sqlx::Value::Bytes(b) => JsonValue::String(base64::encode(b)), // 二进制数据用base64编码
            sqlx::Value::Str(s) => JsonValue::String(s.to_string()),
            // 数据库特定类型处理,以PostgreSQL为例
            #[cfg(feature = "postgres")]
            sqlx::Value::Postgres(pg_val) => match pg_val {
                sqlx::postgres::PgValue::Uuid(u) => JsonValue::String(u.to_string()),
                sqlx::postgres::PgValue::Timestamp(t) => JsonValue::String(t.to_rfc3339()),
                sqlx::postgres::PgValue::Date(d) => JsonValue::String(d.to_string()),
                // 其他PostgreSQL类型按需添加
                _ => JsonValue::String(format!("{:?}", pg_val)),
            },
            // 若使用MySQL/SQLite,添加对应分支
            #[cfg(feature = "mysql")]
            sqlx::Value::MySql(mysql_val) => JsonValue::String(format!("{:?}", mysql_val)),
        };

        json_map.insert(col.name().to_string(), json_val);
    }

    JsonValue::Object(json_map)
}

步骤2:编写API端点

在Axum的处理函数中,完成查询、转换并返回JSON:

use axum::{extract::Path, http::StatusCode, response::IntoResponse, Extension};
use sqlx::PgPool; // 根据你的数据库替换成MySqlPool/SqlitePool

async fn get_table_data(
    Extension(db_pool): Extension<PgPool>,
    Path(table_name): Path<String>,
) -> impl IntoResponse {
    // 关键:必须验证表名,防止SQL注入!
    // 方式1:白名单验证
    let allowed_tables = ["users", "posts", "products"];
    if !allowed_tables.contains(&table_name.as_str()) {
        return (StatusCode::BAD_REQUEST, "Invalid table name").into_response();
    }

    // 方式2:查询information_schema确认表存在(更灵活)
    // match sqlx::query!("SELECT 1 FROM information_schema.tables WHERE table_name = $1", &table_name).fetch_optional(&db_pool).await {
    //     Ok(Some(_)) => {},
    //     Ok(None) => return (StatusCode::BAD_REQUEST, "Table does not exist").into_response(),
    //     Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    // }

    // 构造查询语句,注意给表名加双引号避免特殊字符冲突
    let query = format!("SELECT * FROM \"{}\"", table_name);
    let rows = match sqlx::query(&query).fetch_all(&db_pool).await {
        Ok(r) => r,
        Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
    };

    // 将所有行转换为JSON数组
    let json_result: Vec<JsonValue> = rows.iter().map(row_to_json).collect();

    (StatusCode::OK, axum::Json(json_result)).into_response()
}

注意事项

  1. SQL注入防护:动态表名无法通过参数化查询避免注入,必须通过白名单或查询系统表验证表的合法性,绝对不能直接使用用户传入的表名构造查询。
  2. 错误处理:示例中简化了错误处理,实际项目中应根据场景返回更友好的错误信息,避免暴露数据库细节。
  3. 数据库特定类型:不同数据库的特有类型(如PostgreSQL的UUID、MySQL的DateTime)需要单独处理,确保转换后的JSON符合预期。
  4. 依赖补充:需要在Cargo.toml中添加serde_json和对应数据库的sqlx特性,例如:
    [dependencies]
    axum = "0.7"
    sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-native-tls"] }
    serde_json = "1.0"
    base64 = "0.21"
    tokio = { version = "1.0", features = ["full"] }
    

内容的提问来源于stack exchange,提问作者ExcalPrime0

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 20:22:36