Files
asd-backend/src/main.rs

404 lines
13 KiB
Rust
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.
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);
}
}
}