feat: 所有页面添加ICP备案号 浙ICP备2026052792号
Some checks failed
Deploy Backend / deploy (push) Has been cancelled

This commit is contained in:
2026-07-03 21:59:38 +08:00
parent 178008496e
commit 38ee5ccbc6
8 changed files with 292 additions and 5 deletions

View File

@@ -283,13 +283,19 @@ pub async fn mock_login(
let user_id = match query.user_id {
Some(id) => {
// 验证用户存在
match sqlx::query_as::<_, (i32,)>("SELECT id FROM users WHERE id = $1" )
// 验证用户存在,同时检查是否管理员
match sqlx::query_as::<_, (i32, bool)>("SELECT id, is_admin FROM users WHERE id = $1" )
.bind(id)
.fetch_optional(pool.get_ref())
.await
{
Ok(Some((uid,))) => uid,
Ok(Some((uid, is_admin))) => {
if is_admin {
return HttpResponse::Forbidden()
.json(ErrorResponse::<()>::error("模拟登录不能用于管理员账户"));
}
uid
},
Ok(None) => {
return HttpResponse::BadRequest()
.json(ErrorResponse::<()>::error("用户不存在"));

View File

@@ -1,24 +1,90 @@
use actix_web::{web, get, HttpResponse, Responder};
use sqlx::postgres::PgPool;
use chrono::{NaiveDateTime, Utc};
use openssl::x509::X509;
use serde::Serialize;
use sqlx::postgres::PgPool;
use std::path::Path;
/// SSL 证书文件路径(生产环境优先)
const SSL_CERT_PATHS: &[&str] = &[
"/www/sites/xmclassmate.top/ssl/fullchain.pem",
"/etc/nginx/sites/xmclassmate.top/ssl/fullchain.pem",
];
#[derive(Serialize)]
pub struct HealthResponse {
pub status: String,
pub database: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub ssl_cert: Option<SslCertInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
}
#[derive(Serialize)]
pub struct SslCertInfo {
pub subject: String,
pub issuer: String,
pub not_before: String,
pub not_after: String,
pub days_remaining: i64,
pub valid: bool,
}
/// 读取 SSL 证书文件并提取有效期信息
fn check_ssl_cert() -> Option<SslCertInfo> {
let cert_path = SSL_CERT_PATHS.iter().find(|p| Path::new(p).exists())?;
let pem_bytes = std::fs::read(cert_path).ok()?;
let cert = X509::from_pem(&pem_bytes).ok()?;
let subject = cert
.subject_name()
.entries()
.next()
.and_then(|e| e.data().as_utf8().ok())
.map(|s| s.to_string())
.unwrap_or_default();
let issuer = cert
.issuer_name()
.entries()
.next()
.and_then(|e| e.data().as_utf8().ok())
.map(|s| s.to_string())
.unwrap_or_default();
// OpenSSL Display 格式: "Jun 17 02:39:23 2026 GMT"
let nb_str = format!("{}", cert.not_before());
let na_str = format!("{}", cert.not_after());
let not_before = NaiveDateTime::parse_from_str(&nb_str, "%b %e %H:%M:%S %Y GMT").ok()?;
let not_after = NaiveDateTime::parse_from_str(&na_str, "%b %e %H:%M:%S %Y GMT").ok()?;
let now = Utc::now().naive_utc();
let days_remaining = (not_after - now).num_days();
let valid = now >= not_before && now <= not_after;
Some(SslCertInfo {
subject,
issuer,
not_before: not_before.format("%Y-%m-%d %H:%M UTC").to_string(),
not_after: not_after.format("%Y-%m-%d %H:%M UTC").to_string(),
days_remaining,
valid,
})
}
#[get("/health")]
pub async fn health_check(pool: web::Data<PgPool>) -> impl Responder {
let ssl_cert = check_ssl_cert();
match sqlx::query("SELECT 1").fetch_one(pool.get_ref()).await {
Ok(_) => {
HttpResponse::Ok().json(HealthResponse {
status: "ok".to_string(),
database: "connected".to_string(),
ssl_cert,
error: None,
hint: None,
})
@@ -54,6 +120,7 @@ pub async fn health_check(pool: web::Data<PgPool>) -> impl Responder {
HttpResponse::ServiceUnavailable().json(HealthResponse {
status: "error".to_string(),
database: "disconnected".to_string(),
ssl_cert,
error: Some(error_msg),
hint: Some(hint_msg),
})

View File

@@ -209,6 +209,7 @@ const HTML_TEMPLATE: &str = r#"<!DOCTYPE html>
<div class="footer">
<p>环境计算助手 · v{version}</p>
<p style="margin-top:4px;"><a href="http://beian.miit.gov.cn/" target="_blank" style="color:#999;text-decoration:none;">浙ICP备2026052792号</a></p>
<p style="margin-top:4px;">如有问题请联系客服</p>
</div>

View File

@@ -143,6 +143,7 @@ function confirmMockPay() {{
}});
}}
</script>
<div style="text-align:center;margin-top:32px;padding:16px;color:#999;font-size:12px;"><a href="http://beian.miit.gov.cn/" target="_blank" style="color:#999;text-decoration:none;">浙ICP备2026052792号</a></div>
</body>
</html>"#, order_no, display_name, jwt, order_no)
}
@@ -234,6 +235,7 @@ fn build_alipay_form_html(
{}
</form>
<script>document.getElementById('alipay').submit();</script>
<div style="text-align:center;margin-top:16px;color:#999;font-size:12px;"><a href="http://beian.miit.gov.cn/" target="_blank" style="color:#999;text-decoration:none;">浙ICP备2026052792号</a></div>
</body>
</html>"##,
config.gateway, form_fields
@@ -467,6 +469,9 @@ pub async fn payment_index() -> Result<HttpResponse, AppError> {
<div class="footer-links">
<button class="btn-logout" onclick="logout()">退出登录</button>
</div>
<div style="text-align:center;margin-top:12px;padding:8px 16px;color:#aaa;font-size:11px;">
<a href="http://beian.miit.gov.cn/" target="_blank" style="color:#aaa;text-decoration:none;">浙ICP备2026052792号</a>
</div>
</div>
<script>
@@ -1014,6 +1019,9 @@ fn build_success_html(order_no: &str, jwt_token: &str) -> String {
}}
}}, 30000);
</script>
<div style="text-align:center;margin-top:24px;padding:12px;color:#999;font-size:11px;">
<a href="http://beian.miit.gov.cn/" target="_blank" style="color:#999;text-decoration:none;">浙ICP备2026052792号</a>
</div>
</body>
</html>"##,
green, white, order_no, orange, jwt_token
@@ -1031,6 +1039,9 @@ fn format_error_html(msg: &str, order_no: &str) -> String {
<p style="color:#666">{}</p>
<p style="color:#999;font-size:13px">订单号: {}</p>
<p><a href="/payment" style="color:#1677ff">返回重试</a></p>
<div style="text-align:center;margin-top:24px;padding:12px;color:#999;font-size:11px;">
<a href="http://beian.miit.gov.cn/" target="_blank" style="color:#999;text-decoration:none;">浙ICP备2026052792号</a>
</div>
</body>
</html>"##,
msg, order_no