500 lines
15 KiB
Rust
500 lines
15 KiB
Rust
use actix_web::{App, HttpResponse, HttpServer, Responder, Result, post, web};
|
||
use db::create_pool;
|
||
use reqwest::Client;
|
||
use serde::{Deserialize, Serialize};
|
||
use sqlx::postgres::PgPool;
|
||
|
||
mod db;
|
||
mod models;
|
||
|
||
#[derive(Debug, Deserialize, Serialize)]
|
||
pub struct WeatherData {
|
||
#[serde(rename = "areaType")]
|
||
area_type: String,
|
||
|
||
#[serde(rename = "assignmentnumber")]
|
||
assignment_number: String,
|
||
|
||
#[serde(rename = "atmosphericStability")]
|
||
atmospheric_stability: String,
|
||
|
||
#[serde(rename = "averageWindDirection")]
|
||
average_wind_direction: f64,
|
||
|
||
#[serde(rename = "averageWindSpeed")]
|
||
average_wind_speed: f64,
|
||
|
||
#[serde(rename = "calculatedwindspeed")]
|
||
calculated_wind_speed: f64,
|
||
|
||
#[serde(rename = "cloudIndex")]
|
||
cloud_index: String,
|
||
|
||
#[serde(rename = "convertedWindSpeed")]
|
||
converted_wind_speed: f64,
|
||
|
||
date: String,
|
||
|
||
#[serde(rename = "daySinceJanFirst")]
|
||
day_since_jan_first: i32,
|
||
|
||
#[serde(rename = "hasMeasuredWindSpeed")]
|
||
has_measured_wind_speed: bool,
|
||
|
||
hours: i32,
|
||
|
||
#[serde(rename = "inspectiontype")]
|
||
inspection_type: String,
|
||
|
||
latitude: f64,
|
||
longitude: f64,
|
||
|
||
#[serde(rename = "lowCloudiness")]
|
||
low_cloudiness: String,
|
||
|
||
#[serde(rename = "measuredWindSpeed")]
|
||
measured_wind_speed: f64,
|
||
|
||
#[serde(rename = "measurementHeight")]
|
||
measurement_height: f64,
|
||
|
||
min: i32,
|
||
openid: String,
|
||
|
||
#[serde(rename = "overallCloudiness")]
|
||
overall_cloudiness: String,
|
||
|
||
#[serde(rename = "overallSuitability")]
|
||
overall_suitability: String,
|
||
|
||
#[serde(rename = "pointWindSpeed")]
|
||
point_wind_speed: f64,
|
||
|
||
#[serde(rename = "solarDeclination")]
|
||
solar_declination: f64,
|
||
|
||
#[serde(rename = "solarRadiationLevel")]
|
||
solar_radiation_level: f64,
|
||
|
||
#[serde(rename = "suitabilityDegree")]
|
||
suitability_degree: String,
|
||
|
||
#[serde(rename = "sunAltitude")]
|
||
sun_altitude: f64,
|
||
|
||
theta: f64,
|
||
title: String,
|
||
|
||
#[serde(rename = "windDirection")]
|
||
wind_direction: Vec<f64>,
|
||
|
||
#[serde(rename = "windDirectionStandardDeviation")]
|
||
wind_direction_standard_deviation: f64,
|
||
|
||
#[serde(rename = "windDirectionSuitability")]
|
||
wind_direction_suitability: String,
|
||
|
||
#[serde(rename = "windSpeed")]
|
||
wind_speed: Vec<f64>,
|
||
|
||
#[serde(rename = "windSpeedSuitability")]
|
||
wind_speed_suitability: String,
|
||
}
|
||
|
||
// 定义请求体结构
|
||
#[derive(Debug, Deserialize)]
|
||
struct OpenIdRequest {
|
||
openid: String,
|
||
}
|
||
|
||
// 定义成功响应结构
|
||
#[derive(Debug, Serialize)]
|
||
struct UserIdResponse {
|
||
user_id: i32,
|
||
}
|
||
|
||
// 微信登录请求结构
|
||
#[derive(Debug, Deserialize)]
|
||
struct WeChatLoginRequest {
|
||
code: String,
|
||
}
|
||
|
||
// 微信API响应结构
|
||
#[derive(Debug, Deserialize)]
|
||
struct WeChatApiResponse {
|
||
openid: Option<String>,
|
||
errcode: Option<i32>,
|
||
errmsg: Option<String>,
|
||
}
|
||
|
||
// 我们的API响应结构
|
||
#[derive(Debug, Serialize)]
|
||
struct OpenIdResponse {
|
||
openid: String,
|
||
}
|
||
|
||
#[derive(Debug, Serialize)]
|
||
struct ErrorResponse {
|
||
error: String,
|
||
errcode: Option<i32>,
|
||
errmsg: Option<String>,
|
||
}
|
||
|
||
// 新增:根据openid获取用户ID的端点
|
||
#[post("/getmyid")]
|
||
async fn get_user_id(pool: web::Data<PgPool>, request: web::Json<OpenIdRequest>) -> impl Responder {
|
||
let openid = &request.openid;
|
||
|
||
// 查询用户ID
|
||
let query = r#"
|
||
SELECT id FROM users WHERE openid = $1
|
||
"#;
|
||
|
||
let result: Result<Option<(i32,)>, sqlx::Error> = sqlx::query_as::<_, (i32,)>(query)
|
||
.bind(openid)
|
||
.fetch_optional(pool.get_ref())
|
||
.await;
|
||
|
||
match result {
|
||
Ok(Some(user_id)) => {
|
||
println!("Found user ID {} for openid {}", user_id.0, openid);
|
||
HttpResponse::Ok().json(UserIdResponse { user_id: user_id.0 })
|
||
}
|
||
Ok(None) => {
|
||
println!("No user found with openid: {}", openid);
|
||
HttpResponse::NotFound().json(ErrorResponse {
|
||
error: format!("未找到openid为 {} 的用户", openid),
|
||
errcode: Some(404), // 添加错误码
|
||
errmsg: Some("用户不存在".to_string()), // 添加错误消息
|
||
})
|
||
}
|
||
Err(e) => {
|
||
eprintln!("Database error: {}", e);
|
||
HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: format!("数据库查询错误: {}", e),
|
||
errcode: Some(500), // 添加错误码
|
||
errmsg: Some(e.to_string()), // 添加错误消息
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取openid的API端点
|
||
#[post("/getopenid")]
|
||
async fn get_openid(
|
||
pool: web::Data<PgPool>,
|
||
req: web::Json<WeChatLoginRequest>,
|
||
http_client: web::Data<Client>,
|
||
) -> impl Responder {
|
||
// 从环境变量获取微信小程序配置
|
||
let appid = match std::env::var("WECHAT_APPID") {
|
||
Ok(id) => id,
|
||
Err(_) => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "服务器配置错误:缺少微信小程序appid".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
let secret = match std::env::var("WECHAT_SECRET") {
|
||
Ok(secret) => secret,
|
||
Err(_) => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "服务器配置错误:缺少微信小程序secret".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 构建微信API请求URL
|
||
let url = format!(
|
||
"https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",
|
||
appid, secret, req.code
|
||
);
|
||
|
||
// 调用微信API
|
||
let wechat_response = match http_client.get(&url).send().await {
|
||
Ok(response) => response,
|
||
Err(e) => {
|
||
eprintln!("请求微信API失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "请求微信服务失败".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 解析微信API响应
|
||
let wechat_data: WeChatApiResponse = match wechat_response.json().await {
|
||
Ok(data) => data,
|
||
Err(e) => {
|
||
eprintln!("解析微信API响应失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "解析微信响应失败".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 检查微信API响应中的错误
|
||
if let Some(errcode) = wechat_data.errcode {
|
||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||
error: "微信登录失败".to_string(),
|
||
errcode: Some(errcode),
|
||
errmsg: wechat_data.errmsg,
|
||
});
|
||
}
|
||
// 获取openid
|
||
let openid = match wechat_data.openid {
|
||
Some(id) => id,
|
||
None => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "微信API未返回openid".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 检查用户是否已存在,不存在则创建
|
||
let query = r#"
|
||
INSERT INTO users (openid)
|
||
VALUES ($1)
|
||
ON CONFLICT (openid) DO NOTHING
|
||
RETURNING id
|
||
"#;
|
||
|
||
let result = sqlx::query_as::<_, (i32,)>(query)
|
||
.bind(&openid)
|
||
.fetch_optional(pool.get_ref())
|
||
.await;
|
||
match result {
|
||
Ok(Some(user_id)) => {
|
||
println!("用户已存在,ID: {}", user_id.0);
|
||
}
|
||
Ok(None) => {
|
||
println!("新用户已创建,openid: {}", openid);
|
||
}
|
||
Err(e) => {
|
||
eprintln!("数据库操作失败: {}", e);
|
||
// 即使数据库操作失败,我们仍然返回openid,因为微信登录已经成功
|
||
}
|
||
}
|
||
|
||
// 返回openid给前端
|
||
HttpResponse::Ok().json(OpenIdResponse { openid })
|
||
}
|
||
|
||
#[post("/weather-test")]
|
||
async fn post_weather_data(
|
||
data: web::Json<WeatherData>,
|
||
pool: web::Data<PgPool>,
|
||
) -> impl Responder {
|
||
// 打印接收的数据
|
||
println!("Received weather data: {:#?}", data);
|
||
|
||
// 1. 根据openid查询user_id
|
||
let openid = &data.openid;
|
||
let query_user = r#"
|
||
SELECT id FROM users WHERE openid = $1
|
||
"#;
|
||
|
||
let user_id_result: Result<Option<(i32,)>, sqlx::Error> =
|
||
sqlx::query_as::<_, (i32,)>(query_user)
|
||
.bind(openid)
|
||
.fetch_optional(pool.get_ref())
|
||
.await;
|
||
|
||
let user_id = match user_id_result {
|
||
Ok(Some((id,))) => id,
|
||
Ok(None) => {
|
||
let error_msg = format!("未找到openid为 {} 的用户", openid);
|
||
eprintln!("{}", error_msg);
|
||
// 返回200状态码,但success为false
|
||
return HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": 404,
|
||
"errmsg": error_msg
|
||
}));
|
||
}
|
||
Err(e) => {
|
||
let error_msg = format!("查询用户失败: {}", e);
|
||
eprintln!("{}", error_msg);
|
||
return HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": 500,
|
||
"errmsg": error_msg
|
||
}));
|
||
}
|
||
};
|
||
|
||
// 2. 将天气数据插入数据库
|
||
let insert_query = r#"
|
||
INSERT INTO weather_data (
|
||
user_id, title, date, hour, min, longitude, latitude, daysincejanfirst,
|
||
theta, solardeclination, sunaltitude, overallcloudiness, lowcloudiness,
|
||
cloudindex, solarradiationlevel, hasmeasuredwindspeed, measuredwindspeed,
|
||
convertedwindspeed, measurementheight, areatype, pointwindspeed,
|
||
atmosphericstability, suitabilitydegree, winddirection, averagewinddirection,
|
||
winddirectionstandarddeviation, windspeed, averagewindspeed, windspeedsuitability,
|
||
winddirectionsuitability, overallsuitability, inspectiontype, assignmentnumber,
|
||
calculatedwindspeed
|
||
) VALUES (
|
||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19,
|
||
$20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34
|
||
) RETURNING id
|
||
"#;
|
||
|
||
// 转换经纬度为字符串
|
||
let longitude_str = data.longitude.to_string();
|
||
let latitude_str = data.latitude.to_string();
|
||
|
||
// 执行插入操作
|
||
let result = sqlx::query_as::<_, (i32,)>(insert_query)
|
||
.bind(user_id)
|
||
.bind(&data.title)
|
||
.bind(&data.date)
|
||
.bind(data.hours)
|
||
.bind(data.min)
|
||
.bind(&longitude_str)
|
||
.bind(&latitude_str)
|
||
.bind(data.day_since_jan_first)
|
||
.bind(data.theta)
|
||
.bind(data.solar_declination)
|
||
.bind(data.sun_altitude)
|
||
.bind(&data.overall_cloudiness)
|
||
.bind(&data.low_cloudiness)
|
||
.bind(&data.cloud_index)
|
||
.bind(data.solar_radiation_level as i32)
|
||
.bind(data.has_measured_wind_speed)
|
||
.bind(data.measured_wind_speed)
|
||
.bind(data.converted_wind_speed as i32)
|
||
.bind(data.measurement_height)
|
||
.bind(&data.area_type)
|
||
.bind(data.point_wind_speed)
|
||
.bind(&data.atmospheric_stability)
|
||
.bind(&data.suitability_degree)
|
||
.bind(serde_json::to_value(&data.wind_direction).unwrap())
|
||
.bind(data.average_wind_direction)
|
||
.bind(data.wind_direction_standard_deviation)
|
||
.bind(serde_json::to_value(&data.wind_speed).unwrap())
|
||
.bind(data.average_wind_speed)
|
||
.bind(&data.wind_speed_suitability)
|
||
.bind(&data.wind_direction_suitability)
|
||
.bind(&data.overall_suitability)
|
||
.bind(&data.inspection_type)
|
||
.bind(&data.assignment_number)
|
||
.bind(data.calculated_wind_speed)
|
||
.fetch_one(pool.get_ref())
|
||
.await;
|
||
|
||
match result {
|
||
Ok((inserted_id,)) => {
|
||
println!(
|
||
"Successfully inserted weather data with id: {}",
|
||
inserted_id
|
||
);
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true, // 前端期望的成功标识
|
||
"message": "Weather data inserted successfully",
|
||
"inserted_id": inserted_id,
|
||
"received_assignment": data.assignment_number
|
||
}))
|
||
}
|
||
Err(e) => {
|
||
let error_msg = format!("插入数据失败: {}", e);
|
||
eprintln!("{}", error_msg);
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false, // 前端期望的失败标识
|
||
"errcode": 500,
|
||
"errmsg": error_msg
|
||
}))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建服务器配置的函数
|
||
fn create_server_config(
|
||
pool: PgPool,
|
||
http_client: Client,
|
||
) -> App<
|
||
impl actix_web::dev::ServiceFactory<
|
||
actix_web::dev::ServiceRequest,
|
||
Config = (),
|
||
Response = actix_web::dev::ServiceResponse,
|
||
Error = actix_web::Error,
|
||
InitError = (),
|
||
>,
|
||
> {
|
||
App::new()
|
||
.app_data(web::Data::new(pool))
|
||
.app_data(web::Data::new(http_client))
|
||
.service(get_user_id)
|
||
.service(get_openid)
|
||
.service(post_weather_data)
|
||
}
|
||
|
||
#[actix_web::main]
|
||
async fn main() -> std::io::Result<()> {
|
||
// 初始化数据库连接池
|
||
let pool = match create_pool().await {
|
||
Ok(pool) => pool,
|
||
Err(e) => {
|
||
eprintln!("Failed to create database pool: {}", e);
|
||
eprintln!("Please check your database connection configuration in .env file");
|
||
std::process::exit(1);
|
||
}
|
||
};
|
||
|
||
// 初始化HTTP客户端
|
||
let http_client = Client::new();
|
||
|
||
println!("Attempting to start server...");
|
||
// 尝试多个端口
|
||
let ports = vec![8080, 3000, 8000, 8888];
|
||
let mut server = None;
|
||
|
||
for port in ports {
|
||
let addr = format!("0.0.0.0:{}", port);
|
||
println!("Trying to bind to {}", addr);
|
||
// 为每个服务器创建克隆的连接池和HTTP客户端
|
||
let pool_clone = pool.clone();
|
||
let http_client_clone = http_client.clone();
|
||
|
||
match HttpServer::new(move || {
|
||
create_server_config(pool_clone.clone(), http_client_clone.clone())
|
||
})
|
||
.bind(&addr)
|
||
{
|
||
Ok(s) => {
|
||
println!("Successfully bound to {}", addr);
|
||
server = Some(s);
|
||
break;
|
||
}
|
||
Err(e) => {
|
||
eprintln!("Failed to bind to {}: {}", addr, e);
|
||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||
eprintln!(" -> Permission denied. Try running with sudo or use a port > 1024");
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
match server {
|
||
Some(s) => {
|
||
println!("Server started successfully");
|
||
s.run().await
|
||
}
|
||
None => {
|
||
eprintln!("Failed to bind to any port. Please check your system configuration.");
|
||
std::process::exit(1);
|
||
}
|
||
}
|
||
}
|