diff --git a/.sqlx/query-f9d7de7f6e5e6297a3e80a4748d730def5c5236bdcc704b89abc0a1dca24f13e.json b/.sqlx/query-f9d7de7f6e5e6297a3e80a4748d730def5c5236bdcc704b89abc0a1dca24f13e.json new file mode 100644 index 0000000..3a6c69b --- /dev/null +++ b/.sqlx/query-f9d7de7f6e5e6297a3e80a4748d730def5c5236bdcc704b89abc0a1dca24f13e.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n id,\n data_time\n FROM data_ingestion\n WHERE source = $1\n ORDER BY ABS(EXTRACT(EPOCH FROM (data_time - $2)))\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "data_time", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Text", + "Timestamp" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "f9d7de7f6e5e6297a3e80a4748d730def5c5236bdcc704b89abc0a1dca24f13e" +} diff --git a/src/app.rs b/src/app.rs index c9b885a..9def0c3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,4 +1,4 @@ -use axum::{Error, response::Response}; +use axum::{Error, Json, response::Response}; use std::num::NonZero; use crate::{ @@ -48,6 +48,7 @@ pub async fn create_router(config: &Config) -> Router { Router::new() .route("/api/v1/health", get(health_handler)) .route("/api/v1/data", get(data_handler)) + .route("/api/v1/data/nearest", get(data_nearest_handler)) .layer(CorsLayer::permissive()) .with_state(app_state) } @@ -173,6 +174,35 @@ async fn data_handler( } } +async fn data_nearest_handler( + Query(params): Query, + State(mut state): State, +) -> impl IntoResponse { + match chrono::NaiveDateTime::parse_from_str(¶ms.datetime, "%Y%m%d%H%M%S") { + Ok(naive_datetime) => { + let datetime = naive_datetime.and_utc(); + let area = params.area.unwrap_or_default(); + + match state.service.get_nearest_tile_data(datetime, &area).await { + Ok(tile_response) => (StatusCode::OK, Json(tile_response)).into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("数据库错误: {:?}", e), + ) + .into_response(), + } + } + Err(e) => { + // 返回400错误状态码 + ( + StatusCode::BAD_REQUEST, + format!("日期格式错误: {}. 期望格式: YYYYMMDDHHMMSS", e), + ) + .into_response() + } + } +} + async fn download_image(url: &str) -> Result<(Vec, String), Box> { let client = reqwest::Client::new(); let response = client.get(url).send().await?; diff --git a/src/model/responses.rs b/src/model/responses.rs index c169e49..2ed807e 100644 --- a/src/model/responses.rs +++ b/src/model/responses.rs @@ -11,6 +11,12 @@ pub struct TileResponse { pub storage_url: String, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NearestTileResponse { + pub id: Uuid, + pub nearest_data_time: DateTime, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImageInfo { pub url: String, diff --git a/src/service/mod.rs b/src/service/mod.rs index 424be83..de01433 100644 --- a/src/service/mod.rs +++ b/src/service/mod.rs @@ -1,4 +1,4 @@ -use crate::model::responses::TileResponse; +use crate::model::responses::{NearestTileResponse, TileResponse}; use axum::Error; use chrono::{DateTime, Utc}; use sqlx::PgPool; @@ -52,4 +52,38 @@ impl Service { ))), } } + + pub async fn get_nearest_tile_data( + &self, + datetime: DateTime, + area: &str, + ) -> Result { + let primitive_datetime = datetime.format("%Y%m%d%H%M%S").to_string(); + + // 查找距离指定 datetime 最近的数据 + let record = sqlx::query!( + r#" + SELECT + id, + data_time + FROM data_ingestion + WHERE source = $1 + ORDER BY ABS(EXTRACT(EPOCH FROM (data_time - $2))) + LIMIT 1 + "#, + area, + datetime.naive_utc() + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| Error::new(format!("Database error: {}", e)))?; + + match record { + Some(row) => Ok(NearestTileResponse { + id: row.id, + nearest_data_time: row.data_time.and_utc(), + }), + None => Err(Error::new(format!("No tile data found for area: {}", area))), + } + } }