745 lines
26 KiB
Rust
745 lines
26 KiB
Rust
use actix_web::middleware::from_fn;
|
||
use actix_web::{App, HttpResponse, HttpServer, Responder, delete, get, post, web};
|
||
use include_dir::{Dir, include_dir};
|
||
use log::{debug, error, info, warn};
|
||
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
|
||
use reqwest::Client;
|
||
use sqlx::postgres::PgPool;
|
||
use std::pin::Pin;
|
||
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, TokenResponse};
|
||
|
||
// 编译时嵌入整个 static 目录
|
||
static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static");
|
||
static TEMPLATES_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||
|
||
// 加载TLS证书和私钥
|
||
fn create_ssl_acceptor() -> Result<SslAcceptorBuilder, Box<dyn std::error::Error>> {
|
||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?;
|
||
|
||
// 从环境变量获取证书和私钥路径
|
||
let key_path =
|
||
std::env::var("SSL_KEY_PATH").unwrap_or_else(|_| "path/to/private.key".to_string());
|
||
let cert_path =
|
||
std::env::var("SSL_CERT_PATH").unwrap_or_else(|_| "path/to/certificate.crt".to_string());
|
||
|
||
builder.set_private_key_file(&key_path, SslFiletype::PEM)?;
|
||
builder.set_certificate_chain_file(&cert_path)?;
|
||
|
||
// 返回构建器而不是构建后的接受器
|
||
Ok(builder)
|
||
}
|
||
|
||
#[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) => {
|
||
debug!("成功加载微信小程序appid");
|
||
id
|
||
}
|
||
Err(_) => {
|
||
error!("环境变量中未配置WECHAT_APPID");
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "服务器配置错误:缺少微信小程序appid".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
let secret = match std::env::var("WECHAT_SECRET") {
|
||
Ok(secret) => {
|
||
debug!("成功加载微信小程序secret");
|
||
secret
|
||
}
|
||
Err(_) => {
|
||
error!("环境变量中未配置WECHAT_SECRET");
|
||
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
|
||
);
|
||
debug!("准备调用微信API: {}", url.replace(&secret, "***")); // 日志中隐藏secret
|
||
|
||
// 调用微信API
|
||
let wechat_response = match http_client.get(&url).send().await {
|
||
Ok(response) => {
|
||
debug!("微信API请求成功,状态码: {}", response.status());
|
||
response
|
||
}
|
||
Err(e) => {
|
||
error!("微信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) => {
|
||
debug!("微信API响应解析成功");
|
||
data
|
||
}
|
||
Err(e) => {
|
||
error!("微信API响应解析失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "解析微信响应失败".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 检查微信API响应中的错误
|
||
if let Some(errcode) = wechat_data.errcode {
|
||
warn!(
|
||
"微信登录验证失败 - 错误码: {}, 错误信息: {:?}",
|
||
errcode, wechat_data.errmsg
|
||
);
|
||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||
error: "微信登录失败".to_string(),
|
||
errcode: Some(errcode),
|
||
errmsg: wechat_data.errmsg,
|
||
});
|
||
}
|
||
|
||
// 获取openid
|
||
let openid = match wechat_data.openid {
|
||
Some(id) => {
|
||
// 日志中只显示openid的前4位和后4位,保护用户隐私
|
||
let masked_openid = format!("{}***{}", &id[0..4], &id[id.len() - 4..]);
|
||
debug!("获取到用户openid: {}", masked_openid);
|
||
id
|
||
}
|
||
None => {
|
||
error!("微信API响应中未包含openid");
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "微信API未返回openid".to_string(),
|
||
errcode: None,
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 【核心修改】使用标准的 UPSERT 语法,原子性地创建或获取用户
|
||
let upsert_query = r#"
|
||
INSERT INTO users (openid, name, type)
|
||
VALUES ($1, left($1, 8), 2)
|
||
ON CONFLICT (openid) DO UPDATE SET
|
||
-- 这里可以更新一些需要变更的字段,例如最后登录时间
|
||
-- 如果没有需要更新的,可以使用一个技巧来确保 RETURNING 总是有效
|
||
id = users.id
|
||
RETURNING id
|
||
"#;
|
||
|
||
// 使用 fetch_one,因为我们确定这个查询总能返回一个 id
|
||
let user_id = match sqlx::query_as::<_, (i32,)>(upsert_query)
|
||
.bind(&openid)
|
||
.fetch_one(pool.get_ref()) // <--- 注意这里是 fetch_one
|
||
.await
|
||
{
|
||
Ok((id,)) => {
|
||
// 无论是新用户还是老用户,都成功获取了 ID
|
||
info!("用户登录/注册成功,用户ID: {}", id);
|
||
id
|
||
}
|
||
Err(e) => {
|
||
error!("用户信息处理失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "用户信息处理失败".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: Some(e.to_string()),
|
||
});
|
||
}
|
||
};
|
||
|
||
// 生成JWT token
|
||
let jwt_secret = match std::env::var("JWT_SECRET") {
|
||
Ok(secret) => secret,
|
||
Err(_) => {
|
||
error!("环境变量中未配置JWT_SECRET");
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "服务器配置错误:缺少JWT_SECRET".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 生成token
|
||
let token = match generate_token(user_id, &openid, 2, &jwt_secret) {
|
||
Ok(t) => {
|
||
// 日志中只显示token的前8位,保护安全
|
||
debug!("JWT生成成功: {}", &t[0..8]);
|
||
t
|
||
}
|
||
Err(e) => {
|
||
error!("JWT生成失败: {}", e);
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "生成身份令牌失败".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: Some(e.to_string()),
|
||
});
|
||
}
|
||
};
|
||
|
||
let token_response = TokenResponse::new(token);
|
||
HttpResponse::Ok().json(token_response)
|
||
}
|
||
|
||
#[post("/api/post-weather-data")]
|
||
async fn post_weather_data(
|
||
data: web::Json<WeatherData>,
|
||
pool: web::Data<PgPool>,
|
||
) -> impl Responder {
|
||
info!("Received weather data, preparing to insert into DB...");
|
||
|
||
// 调用 db.rs 中的函数来处理数据库逻辑
|
||
match insert_weather_data(pool.get_ref(), &data).await {
|
||
Ok(inserted_id) => {
|
||
info!(
|
||
"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) => {
|
||
error!("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
|
||
#[post("/api/generate-temp-token/{resource_id}")]
|
||
async fn generate_temp_token_handler(
|
||
path: web::Path<i32>,
|
||
pool: web::Data<PgPool>,
|
||
claims: web::ReqData<Claims>, // 依赖原有JWT验证,确保是合法用户
|
||
) -> impl Responder {
|
||
let resource_id = path.into_inner();
|
||
let openid: &String = &claims.openid;
|
||
|
||
// 校验用户是否有权限访问该资源(防止生成其他用户的资源token)
|
||
match db::get_weather_details(pool.get_ref(), resource_id).await {
|
||
Ok(weather_data) => {
|
||
if weather_data.openid != *openid {
|
||
return HttpResponse::Forbidden().json(ErrorResponse {
|
||
error: "无权限为该资源生成临时token".to_string(),
|
||
errcode: Some(403),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
}
|
||
Err(e) => {
|
||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||
error: format!("资源不存在: {}", e),
|
||
errcode: Some(404),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
}
|
||
|
||
// 获取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,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 生成临时token(有效期10分钟)
|
||
let temp_token = match auth::generate_temp_token(openid, resource_id, &jwt_secret, 10) {
|
||
Ok(token) => token,
|
||
Err(e) => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: format!("生成临时token失败: {}", e),
|
||
errcode: Some(500),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"temp_token": temp_token,
|
||
"expire_minutes": 10,
|
||
}))
|
||
}
|
||
|
||
// 获取天气数据详情
|
||
#[get("/weather/details")]
|
||
async fn get_weather_details(
|
||
pool: web::Data<PgPool>,
|
||
claims: Option<web::ReqData<Claims>>,
|
||
query: web::Query<serde_json::Value>,
|
||
) -> impl Responder {
|
||
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,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 标记是否使用临时token
|
||
let mut is_temp_token = false;
|
||
// 1. 优先检查临时token(URL参数)
|
||
let (openid, weather_id) =
|
||
if let Some(temp_token) = query.get("temp_token").and_then(|v| v.as_str()) {
|
||
is_temp_token = true; // 标记为临时token访问
|
||
// 验证临时token
|
||
let temp_claims = match auth::verify_temp_token(temp_token, &jwt_secret) {
|
||
Ok(c) => c,
|
||
Err(e) => {
|
||
return HttpResponse::Unauthorized().json(ErrorResponse {
|
||
error: format!("临时token无效: {}", e),
|
||
errcode: Some(401),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 从临时token中获取资源ID
|
||
(temp_claims.openid, temp_claims.resource_id)
|
||
}
|
||
// 2. 若没有临时token,使用原有JWT验证(需要查询参数id)
|
||
else if let Some(claims) = claims {
|
||
// 从查询参数获取id
|
||
let weather_id = match query.get("id").and_then(|v| v.as_i64()) {
|
||
Some(id) => id as i32,
|
||
None => {
|
||
return HttpResponse::BadRequest().json(ErrorResponse {
|
||
error: "缺少资源ID参数(id)".to_string(),
|
||
errcode: Some(400),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
(claims.openid.clone(), weather_id)
|
||
}
|
||
// 3. 两种验证方式都没有,返回未授权
|
||
else {
|
||
return HttpResponse::Unauthorized().json(ErrorResponse {
|
||
error: "缺少token(需提供JWT或临时token)".to_string(),
|
||
errcode: Some(401),
|
||
errmsg: None,
|
||
});
|
||
};
|
||
|
||
// 获取天气数据
|
||
let weather_data = match db::get_weather_details(pool.get_ref(), weather_id).await {
|
||
Ok(data) => {
|
||
if data.openid != openid {
|
||
return HttpResponse::Forbidden().json(ErrorResponse {
|
||
error: "无权限访问该数据".to_string(),
|
||
errcode: Some(403),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
data
|
||
}
|
||
Err(error_msg) => {
|
||
error!("获取天气数据详情失败: {}", error_msg);
|
||
let errcode = if error_msg.starts_with("未找到") {
|
||
404
|
||
} else {
|
||
500
|
||
};
|
||
return HttpResponse::Ok().json(serde_json::json!({
|
||
"success": false,
|
||
"errcode": errcode,
|
||
"errmsg": error_msg
|
||
}));
|
||
}
|
||
};
|
||
// 根据访问方式返回不同格式
|
||
if is_temp_token {
|
||
// 临时token访问:返回HTML页面,数据交由前端afterbody.js渲染
|
||
// 读取模板文件
|
||
let index_html = match TEMPLATES_DIR.get_file("index.html") {
|
||
Some(file) => file.contents_utf8().unwrap_or_default(),
|
||
None => {
|
||
return HttpResponse::InternalServerError().json(ErrorResponse {
|
||
error: "无法找到模板文件".to_string(),
|
||
errcode: Some(500),
|
||
errmsg: None,
|
||
});
|
||
}
|
||
};
|
||
|
||
// 将天气数据序列化为JSON字符串,供前端JS使用
|
||
let weather_data_json = serde_json::to_string(&weather_data).unwrap_or_default();
|
||
|
||
// 生成数据注入脚本:将数据挂载到window对象,供afterbody.js访问
|
||
let data_script = format!(
|
||
r#"
|
||
<script>
|
||
// 注入后端数据供前端渲染使用
|
||
window.weatherData = {};
|
||
</script>
|
||
"#,
|
||
weather_data_json
|
||
);
|
||
|
||
// 将数据脚本插入到模板中,同时显示详细信息容器
|
||
let rendered_html = index_html
|
||
.replace("<!-- 详细信息将在这里显示 -->", &data_script)
|
||
.replace(
|
||
"style=\"display: none;\"",
|
||
"", // 显示详细信息容器,供前端渲染内容
|
||
);
|
||
|
||
HttpResponse::Ok()
|
||
.content_type("text/html; charset=utf-8")
|
||
.body(rendered_html)
|
||
} else {
|
||
// JWT访问:返回JSON数据
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"success": true,
|
||
"data": weather_data
|
||
}))
|
||
}
|
||
}
|
||
|
||
// 修改main.rs中的get_weather_brief函数
|
||
#[get("/weather")]
|
||
async fn get_weather_brief(
|
||
query: web::Query<serde_json::Value>,
|
||
pool: web::Data<PgPool>,
|
||
claims: web::ReqData<Claims>,
|
||
) -> impl Responder {
|
||
// 同时处理数字和字符串类型的page参数
|
||
let page = match query.get("page") {
|
||
Some(v) => {
|
||
// 尝试直接解析为数字
|
||
if let Some(num) = v.as_i64() {
|
||
num as i32
|
||
}
|
||
// 尝试将字符串解析为数字
|
||
else if let Some(s) = v.as_str() {
|
||
s.parse().unwrap_or(1)
|
||
}
|
||
// 解析失败用默认值
|
||
else {
|
||
1
|
||
}
|
||
}
|
||
None => 1, // 无参数时用默认值
|
||
};
|
||
|
||
// 同理优化limit参数解析
|
||
let limit = match query.get("limit") {
|
||
Some(v) => {
|
||
if let Some(num) = v.as_i64() {
|
||
num as i32
|
||
} else if let Some(s) = v.as_str() {
|
||
s.parse().unwrap_or(10)
|
||
} else {
|
||
10
|
||
}
|
||
}
|
||
None => 10,
|
||
};
|
||
|
||
debug!("获取天气数据列表, 页码: {}, 每页条数: {}", page, limit);
|
||
|
||
match db::get_weather_list(pool.get_ref(), claims.user_id, page, limit).await {
|
||
Ok(response) => {
|
||
// 这里的response是WeatherListResponse类型
|
||
// 打印获取到的天气列表和总数
|
||
debug!(
|
||
"获取到的天气列表: {:?}, 总条数: {}",
|
||
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) => {
|
||
error!("获取天气数据列表失败: {}", 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();
|
||
info!("删除天气数据, 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) => {
|
||
error!("删除天气数据失败: {}", 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 中添加以下函数,用于处理静态文件请求
|
||
async fn serve_static_files(path: web::Path<String>) -> impl Responder {
|
||
// 获取请求的文件路径(例如 "css/style.css")
|
||
let file_path = path.into_inner();
|
||
|
||
// 【调试日志 1】打印服务器收到的文件路径
|
||
debug!("请求静态文件: {}", file_path);
|
||
|
||
// 从嵌入的 STATIC_DIR 中查找文件
|
||
match STATIC_DIR.get_file(&file_path) {
|
||
Some(file) => {
|
||
// 根据文件扩展名设置 Content-Type
|
||
let content_type = match file_path.split('.').last() {
|
||
Some("ico") => "image/x-icon",
|
||
Some("css") => "text/css",
|
||
Some("js") => "application/javascript",
|
||
Some("html") => "text/html",
|
||
Some("ttf") => "font/ttf",
|
||
Some("woff") => "font/woff",
|
||
Some("woff2") => "font/woff2",
|
||
_ => "application/octet-stream", // 默认类型
|
||
};
|
||
|
||
debug!(
|
||
"成功找到文件: {}, Content-Type: {}",
|
||
file_path, content_type
|
||
);
|
||
HttpResponse::Ok()
|
||
.content_type(content_type)
|
||
.body(file.contents())
|
||
}
|
||
None => {
|
||
// 文件不存在时返回 404
|
||
// 【调试日志 3】打印文件未找到的信息
|
||
error!("文件未找到: {}", file_path);
|
||
HttpResponse::NotFound().body("静态文件不存在")
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建服务器配置的函数
|
||
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))
|
||
// 公开接口(无需验证)
|
||
// 注册静态文件服务:处理 /static/* 路径的请求
|
||
.service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files)))
|
||
.service(login)
|
||
.service(get_weather_details) // 新增
|
||
// 需要验证的接口(使用 from_fn 包装中间件)
|
||
.service(
|
||
web::scope("")
|
||
.wrap(from_fn(jwt_middleware)) // 关键修改:用 from_fn 包装
|
||
.service(post_weather_data)
|
||
.service(get_weather_brief) // 新增
|
||
.service(generate_temp_token_handler)
|
||
.service(delete_weather),
|
||
)
|
||
}
|
||
|
||
#[actix_web::main]
|
||
async fn main() -> std::io::Result<()> {
|
||
if let Err(e) = dotenvy::dotenv() {
|
||
error!("警告:加载.env文件失败,使用系统环境变量: {}", e);
|
||
}
|
||
|
||
// 打印当前 RUST_LOG 的值
|
||
let rust_log = std::env::var("RUST_LOG").unwrap_or_else(|_| "未设置".to_string());
|
||
error!("当前 RUST_LOG 级别:{}", rust_log);
|
||
|
||
env_logger::init();
|
||
info!("logger init successful");
|
||
|
||
// 初始化数据库连接池
|
||
let pool = match create_pool().await {
|
||
Ok(pool) => pool,
|
||
Err(e) => {
|
||
error!("Failed to create database pool: {}", e);
|
||
error!("Please check your database connection configuration in .env file");
|
||
std::process::exit(1);
|
||
}
|
||
};
|
||
|
||
// 初始化HTTP客户端
|
||
let http_client = Client::new();
|
||
|
||
info!("Attempting to start server...");
|
||
|
||
// 尝试多个端口
|
||
let ports = vec![443, 8443, 8080, 3000, 8000, 8888];
|
||
let mut server: Option<
|
||
Pin<Box<dyn std::future::Future<Output = std::io::Result<()>> + Unpin>>,
|
||
> = None;
|
||
let mut bound_port = 0; // 记录绑定的端口
|
||
|
||
// 使用&ports创建引用迭代器,而不是获取所有权
|
||
for port in &ports {
|
||
let addr = format!("0.0.0.0:{}", port);
|
||
info!("Trying to bind to {}", addr);
|
||
|
||
// 为每个服务器创建克隆的连接池和HTTP客户端
|
||
let pool_clone = pool.clone();
|
||
let http_client_clone = http_client.clone();
|
||
|
||
// 根据端口选择是否使用SSL
|
||
if *port == 443 || *port == 8443 {
|
||
// 对于443和8443端口,使用HTTPS
|
||
// 为每个端口创建新的SSL构建器
|
||
let ssl_builder = match create_ssl_acceptor() {
|
||
Ok(builder) => builder,
|
||
Err(e) => {
|
||
error!("Failed to create SSL acceptor: {}", e);
|
||
continue;
|
||
}
|
||
};
|
||
|
||
match HttpServer::new(move || {
|
||
create_server_config(pool_clone.clone(), http_client_clone.clone())
|
||
})
|
||
.bind_openssl(&addr, ssl_builder) // 直接传递构建器
|
||
{
|
||
Ok(s) => {
|
||
info!("Successfully bound to {} with HTTPS", addr);
|
||
// 将服务器运行Future转换为正确的类型
|
||
let server_future = s.run();
|
||
// 使用Box::pin将Pin<Box<Server>>转换为Pin<Box<dyn Future<Output = Result<(), std::io::Error>> + Unpin>>
|
||
server = Some(Box::pin(server_future));
|
||
bound_port = *port; // 记录绑定的端口
|
||
break;
|
||
}
|
||
Err(e) => {
|
||
error!("Failed to bind to {} with HTTPS: {}", addr, e);
|
||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||
error!(" -> Permission denied. Try running with sudo or use a port > 1024");
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
} else {
|
||
// 对于其他端口,使用HTTP
|
||
match HttpServer::new(move || {
|
||
create_server_config(pool_clone.clone(), http_client_clone.clone())
|
||
})
|
||
.bind(&addr)
|
||
{
|
||
Ok(s) => {
|
||
info!("Successfully bound to {} with HTTP", addr);
|
||
// 将服务器运行Future转换为正确的类型
|
||
let server_future = s.run();
|
||
// 使用Box::pin将Pin<Box<Server>>转换为Pin<Box<dyn Future<Output = Result<(), std::io::Error>> + Unpin>>
|
||
server = Some(Box::pin(server_future));
|
||
bound_port = *port; // 记录绑定的端口
|
||
break;
|
||
}
|
||
Err(e) => {
|
||
error!("Failed to bind to {} with HTTP: {}", addr, e);
|
||
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||
error!(
|
||
" -> Permission denied. Try running with sudo or use a port > 1024"
|
||
);
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
match server {
|
||
Some(s) => {
|
||
// 使用记录的端口判断是否为HTTPS
|
||
if bound_port == 443 || bound_port == 8443 {
|
||
info!("Server started successfully with HTTPS");
|
||
} else {
|
||
info!("Server started successfully with HTTP");
|
||
}
|
||
s.await
|
||
}
|
||
None => {
|
||
error!("Failed to bind to any port. Please check your system configuration.");
|
||
std::process::exit(1);
|
||
}
|
||
}
|
||
}
|