404 lines
13 KiB
Rust
404 lines
13 KiB
Rust
use actix_web::middleware::from_fn;
|
||
use actix_web::{App, HttpResponse, HttpServer, Responder, Result, delete, get, post, web};
|
||
use reqwest::Client;
|
||
use sqlx::postgres::PgPool;
|
||
mod auth;
|
||
mod db;
|
||
mod models;
|
||
|
||
use auth::{generate_token, jwt_middleware};
|
||
use db::{create_pool, insert_weather_data};
|
||
use models::{ErrorResponse, WeChatApiResponse, WeChatLoginRequest, WeatherData};
|
||
|
||
use crate::models::Claims;
|
||
|
||
// 登录的API端点
|
||
#[post("/api/login")]
|
||
async fn login(
|
||
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,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 3. 检查用户是否已存在,不存在则创建(显式处理数据库错误)
|
||
let query = r#"
|
||
INSERT INTO users (openid, name, type)
|
||
VALUES ($1, left($1, 8), 2)
|
||
ON CONFLICT (openid) DO NOTHING
|
||
RETURNING id
|
||
"#;
|
||
|
||
let user_id = match sqlx::query_as::<_, (i32,)>(query)
|
||
.bind(&openid)
|
||
.fetch_optional(pool.get_ref())
|
||
.await
|
||
{
|
||
Ok(Some((id,))) => {
|
||
// 已存在用户,直接获取ID
|
||
println!("用户已存在,ID: {}", id);
|
||
id
|
||
}
|
||
Ok(None) => {
|
||
// 新用户创建成功,需再次查询ID(因ON CONFLICT DO NOTHING不返回值)
|
||
println!("新用户已创建,openid: {}", openid);
|
||
match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE openid = $1")
|
||
.bind(&openid)
|
||
.fetch_one(pool.get_ref())
|
||
.await
|
||
{
|
||
Ok((id,)) => id,
|
||
Err(e) => {
|
||
eprintln!("查询新用户ID失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "创建用户后查询ID失败".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: Some(e.to_string()),
|
||
});
|
||
}
|
||
}
|
||
}
|
||
Err(e) => {
|
||
// 数据库操作失败
|
||
eprintln!("用户查询/创建失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "用户信息处理失败".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: Some(e.to_string()),
|
||
});
|
||
}
|
||
};
|
||
|
||
// 4. 生成JWT token(显式处理所有可能的错误)
|
||
// 4.1 获取JWT密钥
|
||
let jwt_secret = match std::env::var("JWT_SECRET") {
|
||
Ok(secret) => secret,
|
||
Err(_) => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "服务器配置错误:缺少JWT_SECRET".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 4.2 编码生成token
|
||
let token = generate_token(user_id, &openid, 2, &jwt_secret);
|
||
|
||
// 5. 返回成功响应(包含token)
|
||
HttpResponse::Ok().json(token)
|
||
}
|
||
|
||
#[post("/api/post-weather-data")]
|
||
async fn post_weather_data(
|
||
data: web::Json<WeatherData>,
|
||
pool: web::Data<PgPool>,
|
||
) -> impl Responder {
|
||
println!("Received weather data, preparing to insert into DB...");
|
||
|
||
// 调用 db.rs 中的函数来处理数据库逻辑
|
||
match insert_weather_data(pool.get_ref(), &data).await {
|
||
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(error_msg) => {
|
||
eprintln!("Database operation failed: {}", error_msg);
|
||
// 根据错误信息返回统一格式的错误响应
|
||
let mut errcode = 500;
|
||
if error_msg.starts_with("未找到openid") {
|
||
errcode = 404;
|
||
}
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": errcode,
|
||
"errmsg": error_msg
|
||
}))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 在main.rs中添加以下处理函数
|
||
|
||
// 获取天气数据详情
|
||
#[get("/weather/details/{id}")]
|
||
async fn get_weather_details(
|
||
path: web::Path<i32>,
|
||
pool: web::Data<PgPool>,
|
||
claims: web::ReqData<Claims>,
|
||
) -> impl Responder {
|
||
let weather_id = path.into_inner();
|
||
println!("获取天气数据详情, ID: {}", weather_id);
|
||
|
||
match db::get_weather_details(pool.get_ref(), weather_id).await {
|
||
Ok(weather_data) => {
|
||
// 检查数据是否属于当前用户
|
||
if weather_data.openid != claims.openid {
|
||
return HttpResponse::Forbidden().json(ErrorResponse {
|
||
error: "无权限访问该数据".to_string(),
|
||
errcode: Some(403),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"data": weather_data
|
||
}))
|
||
}
|
||
Err(error_msg) => {
|
||
eprintln!("获取天气数据详情失败: {}", error_msg);
|
||
let errcode = if error_msg.starts_with("未找到") {
|
||
404
|
||
} else {
|
||
500
|
||
};
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": errcode,
|
||
"errmsg": error_msg
|
||
}))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 修改main.rs中的get_weather函数
|
||
#[get("/weather")]
|
||
async fn get_weather(
|
||
query: web::Query<serde_json::Value>,
|
||
pool: web::Data<PgPool>,
|
||
claims: web::ReqData<Claims>,
|
||
) -> impl Responder {
|
||
let page = query.get("page").and_then(|v| v.as_i64()).unwrap_or(1) as i32;
|
||
let limit = query.get("limit").and_then(|v| v.as_i64()).unwrap_or(10) as i32;
|
||
|
||
println!("获取天气数据列表, 页码: {}, 每页条数: {}", page, limit);
|
||
|
||
match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await {
|
||
Ok(response) => {
|
||
// 这里的response是WeatherListResponse类型
|
||
// 打印获取到的天气列表和总数
|
||
println!(
|
||
"获取到的天气列表: {:?}, 总条数: {}",
|
||
response.list, response.total
|
||
);
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"data": response.list, // 分页数据
|
||
"page": page,
|
||
"limit": limit,
|
||
"total": response.total // 总条数(关键修改)
|
||
}))
|
||
}
|
||
Err(error_msg) => {
|
||
eprintln!("获取天气数据列表失败: {}", error_msg);
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": 500,
|
||
"errmsg": error_msg
|
||
}))
|
||
}
|
||
}
|
||
}
|
||
|
||
// 删除天气数据
|
||
#[delete("/weather/delete/{id}")]
|
||
async fn delete_weather(
|
||
path: web::Path<i32>,
|
||
pool: web::Data<PgPool>,
|
||
claims: web::ReqData<Claims>,
|
||
) -> impl Responder {
|
||
let weather_id = path.into_inner();
|
||
println!("删除天气数据, ID: {}", weather_id);
|
||
|
||
match db::delete_weather_data(pool.get_ref(), weather_id, claims.user_id).await {
|
||
Ok(_) => HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"message": format!("天气数据 {} 已成功删除", weather_id)
|
||
})),
|
||
Err(error_msg) => {
|
||
eprintln!("删除天气数据失败: {}", error_msg);
|
||
let errcode = if error_msg.starts_with("未找到") {
|
||
404
|
||
} else {
|
||
500
|
||
};
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": errcode,
|
||
"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(login)
|
||
// 需要验证的接口(使用 from_fn 包装中间件)
|
||
.service(
|
||
web::scope("")
|
||
.wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装
|
||
.service(post_weather_data)
|
||
.service(get_weather_details) // 新增
|
||
.service(get_weather) // 新增
|
||
.service(delete_weather), // 新增
|
||
)
|
||
}
|
||
|
||
#[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);
|
||
}
|
||
}
|
||
}
|