Compare commits
7 Commits
51752f2d4d
...
2c86219c82
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c86219c82 | |||
| 4c5c879583 | |||
| 70bb63be8d | |||
| 9fc5c2429e | |||
| 1a359f8d84 | |||
| d9c9d1dca0 | |||
| 941a8e5350 |
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -544,15 +544,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
@@ -2358,7 +2349,6 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
@@ -3304,18 +3294,6 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.30"
|
||||
|
||||
@@ -9,7 +9,6 @@ chrono = {version = "0.4.41", features=["serde"]}
|
||||
dotenvy = "0.15.7"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||
tracing-appender = "0.2"
|
||||
sentry = "0.48"
|
||||
sentry-actix = "0.48"
|
||||
sentry-tracing = "0.48"
|
||||
|
||||
@@ -24,10 +24,7 @@ Environment=RUST_LOG=info
|
||||
Environment=PAYMENT_MAINTENANCE_MODE=true
|
||||
Environment=APP_VERSION=0.3.0
|
||||
Environment=FREE_USER_DATA_LIMIT=20
|
||||
Environment=ALIPAY_APP_ID=2021006148681106
|
||||
Environment=ALIPAY_ALIPAY_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkhLqABUN1E2PCmOznQp+53T3x4j27I96hdatl6lWbCBPwyXnZVW4uYZ4kV+34+i2wCzEtMpUiNQaIsQXx2qD8x0d8szsj7pEZuY+xUUJTzm95fYO2nq4aaQ38T/kxdYrW12is8vwY83rlghdlkvVP2XZCuYoFq9UVieTu6LTDk6v91Mt2xr1Zw1Oj+yg8VpBFXkcS7ORJjiufWzCEoxzlb2KK2AGOLhdc8BJsBfrQ0JAqFB6b0uteUF21DDdPN/sblD8GoRAGuSpDhyQLG+0fDNdcIMyHpsq7bIDNjgYGS8RbKAtb6WyV6vriIemZ0fMvzN5Mpqv2jwCe/31ar//PwIDAQAB
|
||||
Environment=ALIPAY_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCImX4Zj18whXYi6l29caq+ShjqT7JwWDbOoz/hEqZqPxQNu6KF59OswcUIv3It4CxJ0/kXsiC/pc6mrN9GedOAPIaz6YydgII/nwHl4OdPPxW8jeomBuiSmcbPcZ4AJTi1YTxrj//8YDIQInVc5/I4HPg+osN4qkqy+xrJ4vGrmyy+ClwHh3OxglGDLhbprH4WCgxKjwTDfR1A84w+pbrST1YaWI7sqqPhVxuYQQPjaLOpXMgrJu5ctnlFVTC27mY2wSIrxajB/6w1SQ+Z+Z149frk47PBOwL+xfKT4gwy3yj/t7WZStzH0gEhG7mXjc6qIjZ8uH5OxUZ+f6gMDpKBAgMBAAECggEAYnI87y7k65NEEOXXqTDCXWIJktTdRRpOZZ5B63QFe4z6ZutTti3d4CPGXQecwnNNd+fNHcbC01wsEvOD9sbWJxV3/zHSpbB4LmHqfEIacoKEp9+fYMiZ1Q3+uoKn3HL268GoSdz+IGUzr20mKE8Bwm8eegXT2qLgMx3FsLpecc3WXmwgMDI/alknO81+xZenLH1Y6+XdsoPVWsS3OY3gnzfYz40n1LaLCQiklBu6AUjSQupSUJ53rGtYpZnp+ImYwBVcYeFL0mZZVId/lzDI2K8eaM/RGVZYsU1vkP6CeSJzWhpr6ftSRRl3VJQou0ytLLX/YeUoXIhsfZ+x3cPKAQKBgQC9ub1z+T/+0lHZwm360otNHQNeEq3FYjnx1x7WOWTXCsAqVZizfmpUYSHoNZeNlyLpbXVsYp22xJmLBArB9LBMvjY1z2iOQJW7F46O6cY38AcgWC41YalLcrk2y4jny6rJZRMjvmDOM67SH01pzZQ6eGJpzSZpTj2Jm/tBsH7gGwKBgQC4UPK5a32ov9YuyzfQXcW341JFlTK2k2D1ewilYSZifpTDqdVpH1gSzGpfoXI7K6Z26xkx1n5rOIe+SxOKaNBj/5PJuEzmMVvUWkFU0IutoqVc2R6h4Rb+9q7llzAavFZ5Ssj1u9Q5Pjhc5Jr8zM6xXk55/4CuqYuBMCcte4fZkwKBgF/kVSEcxX+iBcSuRLAVYlrjYyRVBIdTEj5toCRedd1Kjb3X7YorRsrHHUAkdikWTor0y/iwD06lJ4Xf0SAe8EyZhwTDg/ECK6uYSHFrLshvBOsQJqrMZajAn2RWkye1BOktsPLL8lnGPYHuWE41ncm/tt4K1nM7HFdFA0U8U6QpAoGAe0gRgboG+6cMoJHmjCUciUmwDsCjggEDuk4H024umXSnFlRzXPPNGg3GHJ7E5BTnK+VGHdKlDhxqlvpqYIqI0EnzVNpD/TgIfvm7dvpiYspGJttGpOoEUpc3FnPQhiFxCYHe9pYidgNK+b8id3KZ0YMNpIYkUS8wE0HdAov8qqcCgYEAtRjXzj4JZ5we6aOqv4bcr7xLw4a7wzS99K5EcrbYRxvibUhelooQdgdY40FmdtfuY/4rpHC3R9/13aqcD/WSTFOdOsZAcfTi738y13h3dCy3FZisLYM7rgjUSnQnEGlyR0TYE4Yda490rpYJBjyQIE3EcIlWPerm8vh1wx1Rh90=
|
||||
Environment=ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
|
||||
EnvironmentFile=/root/rust/rust_backend_blue/.env
|
||||
Environment=APP_BASE_URL=https://xmclassmate.top
|
||||
Environment=SERVER_PORT=4433
|
||||
Environment=SENTRY_DSN=http://f02c7cce1dc746e2ad41b11397dab0db@127.0.0.1:9000/1
|
||||
|
||||
@@ -24,13 +24,8 @@ Environment=RUST_LOG=info,rust_backend=debug
|
||||
Environment=APP_VERSION=0.2.0
|
||||
Environment=FREE_USER_DATA_LIMIT=50
|
||||
Environment=SENTRY_DSN=http://f02c7cce1dc746e2ad41b11397dab0db@127.0.0.1:9000/1
|
||||
Environment=APP_BASE_URL=https://xmclassmate.top
|
||||
Environment=ALIPAY_APP_ID=2021006148681106
|
||||
Environment=ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
|
||||
Environment=ALIPAY_ALIPAY_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkhLqABUN1E2PCmOznQp+53T3x4j27I96hdatl6lWbCBPwyXnZVW4uYZ4kV+34+i2wCzEtMpUiNQaIsQXx2qD8x0d8szsj7pEZuY+xUUJTzm95fYO2nq4aaQ38T/kxdYrW12is8vwY83rlghdlkvVP2XZCuYoFq9UVieTu6LTDk6v91Mt2xr1Zw1Oj+yg8VpBFXkcS7ORJjiufWzCEoxzlb2KK2AGOLhdc8BJsBfrQ0JAqFB6b0uteUF21DDdPN/sblD8GoRAGuSpDhyQLG+0fDNdcIMyHpsq7bIDNjgYGS8RbKAtb6WyV6vriIemZ0fMvzN5Mpqv2jwCe/31ar//PwIDAQAB
|
||||
Environment=MOCK_LOGIN_ENABLED=true
|
||||
Environment=MOCK_LOGIN_KEY=d3v-t35t-k3y-2026
|
||||
Environment=ALIPAY_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCImX4Zj18whXYi6l29caq+ShjqT7JwWDbOoz/hEqZqPxQNu6KF59OswcUIv3It4CxJ0/kXsiC/pc6mrN9GedOAPIaz6YydgII/nwHl4OdPPxW8jeomBuiSmcbPcZ4AJTi1YTxrj//8YDIQInVc5/I4HPg+osN4qkqy+xrJ4vGrmyy+ClwHh3OxglGDLhbprH4WCgxKjwTDfR1A84w+pbrST1YaWI7sqqPhVxuYQQPjaLOpXMgrJu5ctnlFVTC27mY2wSIrxajB/6w1SQ+Z+Z149frk47PBOwL+xfKT4gwy3yj/t7WZStzH0gEhG7mXjc6qIjZ8uH5OxUZ+f6gMDpKBAgMBAAECggEAYnI87y7k65NEEOXXqTDCXWIJktTdRRpOZZ5B63QFe4z6ZutTti3d4CPGXQecwnNNd+fNHcbC01wsEvOD9sbWJxV3/zHSpbB4LmHqfEIacoKEp9+fYMiZ1Q3+uoKn3HL268GoSdz+IGUzr20mKE8Bwm8eegXT2qLgMx3FsLpecc3WXmwgMDI/alknO81+xZenLH1Y6+XdsoPVWsS3OY3gnzfYz40n1LaLCQiklBu6AUjSQupSUJ53rGtYpZnp+ImYwBVcYeFL0mZZVId/lzDI2K8eaM/RGVZYsU1vkP6CeSJzWhpr6ftSRRl3VJQou0ytLLX/YeUoXIhsfZ+x3cPKAQKBgQC9ub1z+T/+0lHZwm360otNHQNeEq3FYjnx1x7WOWTXCsAqVZizfmpUYSHoNZeNlyLpbXVsYp22xJmLBArB9LBMvjY1z2iOQJW7F46O6cY38AcgWC41YalLcrk2y4jny6rJZRMjvmDOM67SH01pzZQ6eGJpzSZpTj2Jm/tBsH7gGwKBgQC4UPK5a32ov9YuyzfQXcW341JFlTK2k2D1ewilYSZifpTDqdVpH1gSzGpfoXI7K6Z26xkx1n5rOIe+SxOKaNBj/5PJuEzmMVvUWkFU0IutoqVc2R6h4Rb+9q7llzAavFZ5Ssj1u9Q5Pjhc5Jr8zM6xXk55/4CuqYuBMCcte4fZkwKBgF/kVSEcxX+iBcSuRLAVYlrjYyRVBIdTEj5toCRedd1Kjb3X7YorRsrHHUAkdikWTor0y/iwD06lJ4Xf0SAe8EyZhwTDg/ECK6uYSHFrLshvBOsQJqrMZajAn2RWkye1BOktsPLL8lnGPYHuWE41ncm/tt4K1nM7HFdFA0U8U6QpAoGAe0gRgboG+6cMoJHmjCUciUmwDsCjggEDuk4H024umXSnFlRzXPPNGg3GHJ7E5BTnK+VGHdKlDhxqlvpqYIqI0EnzVNpD/TgIfvm7dvpiYspGJttGpOoEUpc3FnPQhiFxCYHe9pYidgNK+b8id3KZ0YMNpIYkUS8wE0HdAov8qqcCgYEAtRjXzj4JZ5we6aOqv4bcr7xLw4a7wzS99K5EcrbYRxvibUhelooQdgdY40FmdtfuY/4rpHC3R9/13aqcD/WSTFOdOsZAcfTi738y13h3dCy3FZisLYM7rgjUSnQnEGlyR0TYE4Yda490rpYJBjyQIE3EcIlWPerm8vh1wx1Rh90=
|
||||
Environment=APP_BASE_URL=https://dev.xmclassmate.top
|
||||
EnvironmentFile=/root/rust/rust_backend_dev/.env
|
||||
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
|
||||
@@ -24,10 +24,7 @@ Environment=RUST_LOG=info
|
||||
Environment=PAYMENT_MAINTENANCE_MODE=true
|
||||
Environment=APP_VERSION=0.3.0
|
||||
Environment=FREE_USER_DATA_LIMIT=20
|
||||
Environment=ALIPAY_APP_ID=2021006148681106
|
||||
Environment=ALIPAY_ALIPAY_PUBLIC_KEY=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkhLqABUN1E2PCmOznQp+53T3x4j27I96hdatl6lWbCBPwyXnZVW4uYZ4kV+34+i2wCzEtMpUiNQaIsQXx2qD8x0d8szsj7pEZuY+xUUJTzm95fYO2nq4aaQ38T/kxdYrW12is8vwY83rlghdlkvVP2XZCuYoFq9UVieTu6LTDk6v91Mt2xr1Zw1Oj+yg8VpBFXkcS7ORJjiufWzCEoxzlb2KK2AGOLhdc8BJsBfrQ0JAqFB6b0uteUF21DDdPN/sblD8GoRAGuSpDhyQLG+0fDNdcIMyHpsq7bIDNjgYGS8RbKAtb6WyV6vriIemZ0fMvzN5Mpqv2jwCe/31ar//PwIDAQAB
|
||||
Environment=ALIPAY_PRIVATE_KEY=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCImX4Zj18whXYi6l29caq+ShjqT7JwWDbOoz/hEqZqPxQNu6KF59OswcUIv3It4CxJ0/kXsiC/pc6mrN9GedOAPIaz6YydgII/nwHl4OdPPxW8jeomBuiSmcbPcZ4AJTi1YTxrj//8YDIQInVc5/I4HPg+osN4qkqy+xrJ4vGrmyy+ClwHh3OxglGDLhbprH4WCgxKjwTDfR1A84w+pbrST1YaWI7sqqPhVxuYQQPjaLOpXMgrJu5ctnlFVTC27mY2wSIrxajB/6w1SQ+Z+Z149frk47PBOwL+xfKT4gwy3yj/t7WZStzH0gEhG7mXjc6qIjZ8uH5OxUZ+f6gMDpKBAgMBAAECggEAYnI87y7k65NEEOXXqTDCXWIJktTdRRpOZZ5B63QFe4z6ZutTti3d4CPGXQecwnNNd+fNHcbC01wsEvOD9sbWJxV3/zHSpbB4LmHqfEIacoKEp9+fYMiZ1Q3+uoKn3HL268GoSdz+IGUzr20mKE8Bwm8eegXT2qLgMx3FsLpecc3WXmwgMDI/alknO81+xZenLH1Y6+XdsoPVWsS3OY3gnzfYz40n1LaLCQiklBu6AUjSQupSUJ53rGtYpZnp+ImYwBVcYeFL0mZZVId/lzDI2K8eaM/RGVZYsU1vkP6CeSJzWhpr6ftSRRl3VJQou0ytLLX/YeUoXIhsfZ+x3cPKAQKBgQC9ub1z+T/+0lHZwm360otNHQNeEq3FYjnx1x7WOWTXCsAqVZizfmpUYSHoNZeNlyLpbXVsYp22xJmLBArB9LBMvjY1z2iOQJW7F46O6cY38AcgWC41YalLcrk2y4jny6rJZRMjvmDOM67SH01pzZQ6eGJpzSZpTj2Jm/tBsH7gGwKBgQC4UPK5a32ov9YuyzfQXcW341JFlTK2k2D1ewilYSZifpTDqdVpH1gSzGpfoXI7K6Z26xkx1n5rOIe+SxOKaNBj/5PJuEzmMVvUWkFU0IutoqVc2R6h4Rb+9q7llzAavFZ5Ssj1u9Q5Pjhc5Jr8zM6xXk55/4CuqYuBMCcte4fZkwKBgF/kVSEcxX+iBcSuRLAVYlrjYyRVBIdTEj5toCRedd1Kjb3X7YorRsrHHUAkdikWTor0y/iwD06lJ4Xf0SAe8EyZhwTDg/ECK6uYSHFrLshvBOsQJqrMZajAn2RWkye1BOktsPLL8lnGPYHuWE41ncm/tt4K1nM7HFdFA0U8U6QpAoGAe0gRgboG+6cMoJHmjCUciUmwDsCjggEDuk4H024umXSnFlRzXPPNGg3GHJ7E5BTnK+VGHdKlDhxqlvpqYIqI0EnzVNpD/TgIfvm7dvpiYspGJttGpOoEUpc3FnPQhiFxCYHe9pYidgNK+b8id3KZ0YMNpIYkUS8wE0HdAov8qqcCgYEAtRjXzj4JZ5we6aOqv4bcr7xLw4a7wzS99K5EcrbYRxvibUhelooQdgdY40FmdtfuY/4rpHC3R9/13aqcD/WSTFOdOsZAcfTi738y13h3dCy3FZisLYM7rgjUSnQnEGlyR0TYE4Yda490rpYJBjyQIE3EcIlWPerm8vh1wx1Rh90=
|
||||
Environment=ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
|
||||
EnvironmentFile=/root/rust/rust_backend_green/.env
|
||||
Environment=APP_BASE_URL=https://xmclassmate.top
|
||||
Environment=SERVER_PORT=4434
|
||||
Environment=SENTRY_DSN=http://f02c7cce1dc746e2ad41b11397dab0db@127.0.0.1:9000/1
|
||||
|
||||
@@ -25,10 +25,7 @@ Environment=RUST_LOG=info
|
||||
Environment=APP_VERSION=0.3.0
|
||||
Environment=FREE_USER_DATA_LIMIT=20
|
||||
Environment=APP_BASE_URL=https://xmclassmate.top
|
||||
Environment=ALIPAY_APP_ID=your_alipay_app_id
|
||||
Environment=ALIPAY_GATEWAY=https://openapi.alipay.com/gateway.do
|
||||
Environment=ALIPAY_ALIPAY_PUBLIC_KEY=your_alipay_public_key
|
||||
Environment=ALIPAY_PRIVATE_KEY=your_private_key_base64
|
||||
EnvironmentFile=/root/rust/rust_backend/.env
|
||||
|
||||
NoNewPrivileges=yes
|
||||
PrivateTmp=yes
|
||||
|
||||
@@ -90,7 +90,7 @@ check_service_file() {
|
||||
|
||||
local missing=0 ph=0
|
||||
local required_vars=("DATABASE_URL" "JWT_SECRET" "WECHAT_APPID" "WECHAT_SECRET")
|
||||
[ "${APP_ENV}" = "production" ] && required_vars+=("SSL_KEY_PATH" "SSL_CERT_PATH" "ALIPAY_APP_ID")
|
||||
[ "${APP_ENV}" = "production" ] && required_vars+=("SSL_KEY_PATH" "SSL_CERT_PATH")
|
||||
|
||||
for var in "${required_vars[@]}"; do
|
||||
local val; val=$(echo "$content" | grep -E "^Environment=${var}=" | head -1 | sed "s/^Environment=${var}=//")
|
||||
@@ -205,6 +205,8 @@ Environment=SSL_CERT_PATH=
|
||||
Environment=RUST_LOG=info
|
||||
Environment=APP_VERSION=0.3.0
|
||||
Environment=FREE_USER_DATA_LIMIT=20
|
||||
# 支付宝密钥存储在 .env 文件(不在 Git 中)
|
||||
EnvironmentFile=${REMOTE_DIR}/.env
|
||||
SERVEOF
|
||||
|
||||
chmod 600 "$(get_service_file_local)"
|
||||
|
||||
12
lib/test.sh
12
lib/test.sh
@@ -456,15 +456,13 @@ test_payment_verify_detection() {
|
||||
|
||||
test_nginx_proxy_temp() {
|
||||
log_info "测试 nginx proxy temp(POST body 权限)..."
|
||||
# 发送 POST 请求验证 nginx 能正确代理带 body 的请求(AppArmor proxy temp 权限检查)
|
||||
local body; body=$(python3 -c "print('x'*50000)" 2>/dev/null || printf 'x%.0s' {1..50000})
|
||||
# 发送带 POST body 的请求到公开 POST 端点,验证 nginx 能正常代理
|
||||
# 如果 proxy temp 无权限,nginx 返回 502/403 或超时
|
||||
local resp
|
||||
resp=$(curl -sk --max-time 10 -X POST "${BASE_URL}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"test_body_size\":${#body}}" 2>&1) || true
|
||||
resp=$(remote "curl -sk --max-time 10 -X POST '${BASE_URL}/api/login' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{\"test\":\"x\"}'" 2>/dev/null || true)
|
||||
|
||||
# 只要能返回响应(包括错误),就说明 nginx 能正常处理 POST body
|
||||
# 如果 proxy temp 无权限,nginx 会返回 502/403 或超时
|
||||
if [ -n "$resp" ]; then
|
||||
log_info " nginx POST body 代理正常 ✅"
|
||||
return 0
|
||||
|
||||
116
scripts/daily-report.sh
Normal file
116
scripts/daily-report.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# 每日健康报告 — 发送格式化摘要邮件到管理员
|
||||
# 部署:cp 到服务器 /root/rust/scripts/daily-report.sh,cron 每日执行
|
||||
|
||||
set -e
|
||||
|
||||
TO="m943790568@163.com"
|
||||
DATE=$(date "+%Y-%m-%d")
|
||||
HOST=$(hostname)
|
||||
|
||||
# ---------- 采集数据 ----------
|
||||
|
||||
UPTIME=$(uptime -p | sed 's/up //')
|
||||
LOAD=$(uptime | awk -F'load average:' '{print $2}' | xargs)
|
||||
DISK_USED=$(df -h / | awk 'NR==2{print $3}')
|
||||
DISK_TOTAL=$(df -h / | awk 'NR==2{print $2}')
|
||||
DISK_PCT=$(df -h / | awk 'NR==2{print $5}')
|
||||
MEM_USED=$(free -h | awk '/Mem/{print $3}')
|
||||
MEM_TOTAL=$(free -h | awk '/Mem/{print $2}')
|
||||
MEM_PCT=$(free | awk '/Mem/{printf "%.0f", $3/$2 * 100}')
|
||||
CPU_TEMP=$(cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null | awk '{printf "%.1f°C", $1/1000}' || echo "N/A")
|
||||
|
||||
# Docker
|
||||
DOCKER_STATUS=$(docker ps --format '{{.Names}} {{.Status}}' 2>/dev/null || echo "Docker 不可用")
|
||||
|
||||
# SSL 证书
|
||||
SSL_EXPIRY=$(echo "" | openssl s_client -connect xmclassmate.top:443 -servername xmclassmate.top 2>&1 | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || echo "检测失败")
|
||||
SSL_DAYS=$(( ($(date -d "$SSL_EXPIRY" +%s 2>/dev/null) - $(date +%s)) / 86400 )) || SSL_DAYS="?"
|
||||
|
||||
# 服务状态
|
||||
GREEN_STATUS=$(systemctl is-active rust-backend-green.service 2>/dev/null || echo "unknown")
|
||||
BLUE_STATUS=$(systemctl is-active rust-backend-blue.service 2>/dev/null || echo "unknown")
|
||||
NGINX_STATUS=$(systemctl is-active nginx 2>/dev/null || echo "unknown")
|
||||
|
||||
# 最近备份
|
||||
LAST_BACKUP=$(ls -1t /root/rust/backups/milkydata/ 2>/dev/null | head -1)
|
||||
BACKUP_AGE="未知"
|
||||
if [ -n "$LAST_BACKUP" ]; then
|
||||
BACKUP_TIME=$(stat -c %Y "/root/rust/backups/milkydata/$LAST_BACKUP" 2>/dev/null)
|
||||
NOW=$(date +%s)
|
||||
AGE_HOURS=$(( (NOW - BACKUP_TIME) / 3600 ))
|
||||
if [ "$AGE_HOURS" -lt 24 ]; then
|
||||
BACKUP_AGE="✅ ${AGE_HOURS}小时前"
|
||||
elif [ "$AGE_HOURS" -lt 48 ]; then
|
||||
BACKUP_AGE="⚠️ ${AGE_HOURS}小时前"
|
||||
else
|
||||
BACKUP_AGE="❌ ${AGE_HOURS}小时前"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fail2Ban 封禁统计
|
||||
SSH_BANS=$(sudo fail2ban-client status sshd 2>/dev/null | grep "Currently banned" | awk '{print $NF}' || echo "0")
|
||||
NGINX_BANS=$(sudo fail2ban-client status nginx-botsearch 2>/dev/null | grep "Currently banned" | awk '{print $NF}' || echo "0")
|
||||
|
||||
# 近期错误数(昨天 journalctl)
|
||||
YESTERDAY_ERRORS=$(journalctl -u rust-backend-green.service --since "1 day ago" --priority err --no-pager 2>/dev/null | wc -l)
|
||||
YESTERDAY_WARNS=$(journalctl -u rust-backend-green.service --since "1 day ago" --priority warning --no-pager 2>/dev/null | wc -l)
|
||||
|
||||
# 应用统计(昨天活跃用户)
|
||||
ACTIVE_USERS=$(journalctl -u rust-backend-green.service --since "1 day ago" --no-pager 2>/dev/null | grep -c "user_id=\|登录成功\|上传成功" || echo "0")
|
||||
API_COUNT=$(journalctl -u rust-backend-green.service --since "1 day ago" --no-pager 2>/dev/null | grep -c "INFO\|WARN\|ERROR" || echo "0")
|
||||
|
||||
# ---------- 构建邮件正文 ----------
|
||||
|
||||
SUBJECT="[ASD 服务器报告] ${DATE} — ${HOST}"
|
||||
|
||||
MAIL=$(cat <<EOF
|
||||
===========================================
|
||||
ASD 服务器健康报告
|
||||
${DATE}
|
||||
主机: ${HOST}
|
||||
===========================================
|
||||
|
||||
🖥 系统状态
|
||||
- 运行时长: ${UPTIME}
|
||||
- 负载: ${LOAD}
|
||||
- 磁盘: ${DISK_USED} / ${DISK_TOTAL} (${DISK_PCT})
|
||||
- 内存: ${MEM_USED} / ${MEM_TOTAL} (${MEM_PCT})
|
||||
- CPU 温度: ${CPU_TEMP}
|
||||
|
||||
📦 容器
|
||||
$(echo "$DOCKER_STATUS" | sed 's/^/ /')
|
||||
|
||||
🔐 SSL 证书
|
||||
域名: xmclassmate.top
|
||||
到期: ${SSL_EXPIRY}
|
||||
剩余: ${SSL_DAYS} 天
|
||||
|
||||
🔄 服务状态
|
||||
- 后端 (green): ${GREEN_STATUS}
|
||||
- 后端 (blue): ${BLUE_STATUS}
|
||||
- nginx: ${NGINX_STATUS}
|
||||
|
||||
🛡 安全
|
||||
- SSH 封禁: ${SSH_BANS} 个 IP
|
||||
- nginx 封禁: ${NGINX_BANS} 个 IP
|
||||
- 昨日错误: ${YESTERDAY_ERRORS}
|
||||
- 昨日警告: ${YESTERDAY_WARNS}
|
||||
|
||||
📊 应用概况(昨日)
|
||||
- 日志事件数: ${API_COUNT}
|
||||
- 活跃用户操作: ${ACTIVE_USERS}
|
||||
|
||||
💾 备份
|
||||
- 最新备份: ${LAST_BACKUP:-无}
|
||||
- 备份时效: ${BACKUP_AGE}
|
||||
|
||||
---
|
||||
ASD 自动监控 | $(date "+%Y-%m-%d %H:%M")
|
||||
EOF
|
||||
)
|
||||
|
||||
# ---------- 发送邮件 ----------
|
||||
|
||||
echo "$MAIL" | msmtp -a default "$TO"
|
||||
echo "✅ 健康报告已发送到 ${TO}"
|
||||
15
src/db.rs
15
src/db.rs
@@ -11,17 +11,18 @@ use crate::error::AppError;
|
||||
pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user_id: i32) -> Result<i32, AppError> {
|
||||
// 单次查询获取用户会员状态和数据条数(原 N+1:get_user_by_id + count_user_weather_data → 合并为 1 次)
|
||||
#[derive(FromRow)]
|
||||
struct UserQuota {
|
||||
is_member: bool,
|
||||
paid_expires_at: Option<chrono::DateTime<Utc>>,
|
||||
data_count: i64,
|
||||
}
|
||||
struct UserQuota {
|
||||
is_member: bool,
|
||||
#[sqlx(rename = "membership_expires_at")]
|
||||
membership_expires_at: Option<chrono::DateTime<Utc>>,
|
||||
data_count: i64,
|
||||
}
|
||||
|
||||
let quota = sqlx::query_as::<_, UserQuota>(
|
||||
r#"
|
||||
SELECT
|
||||
u.is_member,
|
||||
u.paid_expires_at,
|
||||
u.membership_expires_at,
|
||||
(SELECT COUNT(*) FROM weather_data w WHERE w.user_id = $1)::BIGINT AS data_count
|
||||
FROM users u
|
||||
WHERE u.id = $1
|
||||
@@ -33,7 +34,7 @@ pub async fn insert_weather_data(pool: &PgPool, weather_data: &WeatherData, user
|
||||
.map_err(|e| AppError::Database(format!("查询用户配额失败: {}", e)))?
|
||||
.ok_or_else(|| AppError::NotFound("用户不存在".to_string()))?;
|
||||
|
||||
let is_active_member = quota.is_member && quota.paid_expires_at.is_none_or(|expires| expires > Utc::now());
|
||||
let is_active_member = quota.is_member && quota.membership_expires_at.is_none_or(|expires| expires > Utc::now());
|
||||
|
||||
// 维护模式:非会员使用更高的临时限额,防止资源滥用
|
||||
let is_maintenance = std::env::var("PAYMENT_MAINTENANCE_MODE").ok() == Some("true".to_string());
|
||||
|
||||
@@ -179,7 +179,7 @@ fn build_alipay_form_html(
|
||||
"out_trade_no": out_trade_no,
|
||||
"total_amount": total_amount,
|
||||
"subject": subject,
|
||||
"product_code": "FAST_INSTANT_TRADE_PAY",
|
||||
"product_code": "QUICK_WAP_PAY",
|
||||
});
|
||||
|
||||
let biz_content_str =
|
||||
@@ -188,7 +188,7 @@ fn build_alipay_form_html(
|
||||
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
||||
params.insert("app_id", &config.app_id);
|
||||
params.insert("method", "alipay.trade.page.pay");
|
||||
params.insert("method", "alipay.trade.wap.pay");
|
||||
params.insert("format", "JSON");
|
||||
params.insert("charset", "utf-8");
|
||||
params.insert("sign_type", "RSA2");
|
||||
|
||||
@@ -17,6 +17,7 @@ pub async fn serve_static_files(path: web::Path<String>) -> impl Responder {
|
||||
Some("gif") => "image/gif",
|
||||
Some("svg") => "image/svg+xml",
|
||||
Some("webp") => "image/webp",
|
||||
Some("txt") => "text/plain",
|
||||
Some("css") => "text/css",
|
||||
Some("js") => "application/javascript",
|
||||
Some("html") => "text/html",
|
||||
|
||||
34
src/main.rs
34
src/main.rs
@@ -85,6 +85,8 @@ fn create_server_config(
|
||||
.service(alipay_notify) // POST /payment/notify(支付宝异步回调)
|
||||
.service(alipay_refund_notify) // POST /payment/refund-notify(支付宝退款回调)
|
||||
.service(payment_login_status) // GET /payment/login-status(网页轮询)
|
||||
// robots.txt(无需认证)
|
||||
.route("/robots.txt", web::get().to(|| async { serve_static_files(web::Path::from("robots.txt".to_string())).await }))
|
||||
// 静态文件(无需认证)
|
||||
.service(web::resource("/static/{tail:.*}").route(web::get().to(serve_static_files)))
|
||||
// API 接口
|
||||
@@ -154,7 +156,7 @@ async fn main() -> std::io::Result<()> {
|
||||
// Sentry 初始化(仅在 SENTRY_DSN 环境变量已设置时启用)
|
||||
let _sentry_guard = if let Ok(dsn) = std::env::var("SENTRY_DSN") {
|
||||
if !dsn.is_empty() {
|
||||
eprintln!("[SENTRY] Sentry 已初始化 (DSN: {})", dsn);
|
||||
info!("Sentry 已初始化");
|
||||
Some(sentry::init((
|
||||
dsn,
|
||||
sentry::ClientOptions {
|
||||
@@ -167,30 +169,14 @@ async fn main() -> std::io::Result<()> {
|
||||
},
|
||||
)))
|
||||
} else {
|
||||
eprintln!("[SENTRY] SENTRY_DSN 为空,跳过 Sentry 初始化");
|
||||
info!("SENTRY_DSN 为空,跳过 Sentry 初始化");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
eprintln!("[SENTRY] 未设置 SENTRY_DSN,跳过 Sentry 初始化");
|
||||
info!("未设置 SENTRY_DSN,跳过 Sentry 初始化");
|
||||
None
|
||||
};
|
||||
|
||||
// 初始化文件日志(JSON 格式,带轮转)
|
||||
let log_dir = std::path::Path::new("./logs");
|
||||
std::fs::create_dir_all(log_dir).ok(); // 确保日志目录存在
|
||||
|
||||
let file_appender = tracing_appender::rolling::Builder::new()
|
||||
.rotation(tracing_appender::rolling::Rotation::DAILY)
|
||||
.filename_prefix("rust-backend")
|
||||
.filename_suffix("log")
|
||||
.build(log_dir)
|
||||
.expect("无法创建日志文件");
|
||||
|
||||
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||
|
||||
// 保持文件 guard 存活(使用 Box 泄漏)
|
||||
std::mem::forget(_guard);
|
||||
|
||||
use std::str::FromStr;
|
||||
let default_directive = tracing_subscriber::filter::Directive::from_str("info").unwrap();
|
||||
|
||||
@@ -213,14 +199,14 @@ async fn main() -> std::io::Result<()> {
|
||||
)
|
||||
)
|
||||
.with_target(true)
|
||||
.with_thread_ids(false) // 生产环境可开启
|
||||
.with_thread_ids(false)
|
||||
.with_file(true)
|
||||
.with_line_number(true)
|
||||
.with_writer(non_blocking)
|
||||
.with_ansi(false) // 文件中不使用 ANSI 颜色
|
||||
.json() // JSON 格式便于 ELK 解析
|
||||
.with_ansi(true)
|
||||
.compact()
|
||||
.init();
|
||||
info!("日志系统初始化成功,JSON格式,输出到 ./logs/");
|
||||
info!("日志系统初始化成功,输出到 systemd journal (journalctl)");
|
||||
info!("日志级别: {}", app_config.rust_log);
|
||||
|
||||
let app_state = match AppState::load() {
|
||||
Ok(state) => state,
|
||||
|
||||
11
static/robots.txt
Normal file
11
static/robots.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
User-agent: *
|
||||
Disallow: /payment
|
||||
Disallow: /api
|
||||
Disallow: /weather
|
||||
Disallow: /static
|
||||
|
||||
# Allow root (status page)
|
||||
Allow: /$
|
||||
|
||||
# Allow health check
|
||||
Allow: /health$
|
||||
Reference in New Issue
Block a user