This commit is contained in:
Yukino_fox
2026-03-29 19:36:39 +08:00
21 changed files with 1425 additions and 548 deletions
+5 -2
View File
@@ -1,6 +1,6 @@
use parking_lot::RwLock;
use serde::Deserialize;
use sea_orm::{ConnectionTrait, DatabaseConnection, Statement};
use serde::Deserialize;
use serde_json::{Map, Value as JsonValue};
use sqlx::{Column, Row, SqlitePool};
use std::collections::HashSet;
@@ -155,7 +155,10 @@ async fn get_board_configs_raw(conn: &DatabaseConnection) -> Result<Option<Strin
}
}
async fn upsert_board_configs_raw(conn: &DatabaseConnection, config_json: &str) -> Result<(), String> {
async fn upsert_board_configs_raw(
conn: &DatabaseConnection,
config_json: &str,
) -> Result<(), String> {
let config_escaped = escape_sql(config_json);
let now_escaped = escape_sql(&now_iso());
let sql = format!(
+13 -12
View File
@@ -1150,7 +1150,10 @@ pub async fn db_switch_connection(
let logger = state_guard.logger.read();
logger.log(
LogLevel::Info,
&format!("Database switch requested, connection_string length: {}", connection_string.len()),
&format!(
"Database switch requested, connection_string length: {}",
connection_string.len()
),
Some("database"),
None,
);
@@ -1225,17 +1228,15 @@ pub async fn db_switch_connection(
connection_string
};
let conn = create_sqlite_connection(&path)
.await
.map_err(|e| {
logger.log(
LogLevel::Error,
&format!("SQLite connection failed: {}", e),
Some("database"),
None,
);
e.to_string()
})?;
let conn = create_sqlite_connection(&path).await.map_err(|e| {
logger.log(
LogLevel::Error,
&format!("SQLite connection failed: {}", e),
Some("database"),
None,
);
e.to_string()
})?;
run_migration(&conn, DatabaseType::SQLite)
.await
.map_err(|e| {
+15 -4
View File
@@ -313,7 +313,9 @@ async fn post_banyou_action<T: DeserializeOwned>(
params: serde_json::Value,
) -> Result<T, String> {
let timestamp = chrono::Utc::now().timestamp_millis();
let url = format!("https://care.seewo.com/app/apis.json?action={action}&timestamp={timestamp}&isAjax=1");
let url = format!(
"https://care.seewo.com/app/apis.json?action={action}&timestamp={timestamp}&isAjax=1"
);
let payload = serde_json::json!({
"action": action,
"params": params,
@@ -371,7 +373,11 @@ async fn post_banyou_action<T: DeserializeOwned>(
"body_preview": first_n_chars(&body, 500),
})),
);
return Err(format!("班优接口请求失败({} HTTP {}", action, status.as_u16()));
return Err(format!(
"班优接口请求失败({} HTTP {}",
action,
status.as_u16()
));
}
let parsed: BanYouApiResponse<T> = serde_json::from_str(&body).map_err(|e| {
@@ -993,7 +999,9 @@ pub async fn student_fetch_banyou_classroom_detail(
(Vec::new(), None)
};
let final_team_plan_id = params.team_plan_id.or_else(|| team_plans.first().map(|p| p.team_plan_id));
let final_team_plan_id = params
.team_plan_id
.or_else(|| team_plans.first().map(|p| p.team_plan_id));
let group_data = if let Some(team_plan_id) = final_team_plan_id {
match post_banyou_action::<BanYouGroupFetchData>(
@@ -1032,7 +1040,10 @@ pub async fn student_fetch_banyou_classroom_detail(
let detail = BanYouClassroomDetailData {
medals,
students: students_data.students,
teams: group_data.as_ref().map(|d| d.teams.clone()).unwrap_or_default(),
teams: group_data
.as_ref()
.map(|d| d.teams.clone())
.unwrap_or_default(),
ungrouped_students: group_data
.as_ref()
.map(|d| d.students.clone())
+4 -3
View File
@@ -318,9 +318,10 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
*db_guard = Some(active_conn);
}
state_guard.initialize().await.map_err(|e| {
format!("Failed to initialize app state: {}", e)
})?;
state_guard
.initialize()
.await
.map_err(|e| format!("Failed to initialize app state: {}", e))?;
Ok::<_, Box<dyn std::error::Error>>(())
});
+64 -16
View File
@@ -1,4 +1,4 @@
use chrono::{DateTime, Utc};
use chrono::{DateTime, Months, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tauri::{AppHandle, Emitter};
@@ -15,6 +15,20 @@ pub struct AutoScoreAction {
pub value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum IntervalUnit {
Minute,
Day,
Month,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct IntervalTriggerValue {
amount: i64,
unit: IntervalUnit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoScoreRule {
pub id: i32,
@@ -171,23 +185,17 @@ impl AutoScoreService {
for trigger in &rule.triggers {
if trigger.event == "interval_time_passed" {
let minutes = trigger
.value
let interval = parse_interval_trigger_value(trigger.value.as_ref());
let base_time = rule
.last_executed
.as_ref()
.and_then(|v| v.parse::<i64>().ok())
.unwrap_or(30);
let interval_ms = minutes * 60 * 1000;
.and_then(|value| DateTime::parse_from_rfc3339(value).ok())
.map(|value| value.with_timezone(&Utc))
.unwrap_or(now);
if let Some(last_executed_str) = &rule.last_executed {
if let Ok(last_executed) = DateTime::parse_from_rfc3339(last_executed_str) {
let last_executed_utc: DateTime<Utc> = last_executed.with_timezone(&Utc);
let next_execute_time =
last_executed_utc + chrono::Duration::milliseconds(interval_ms);
let delay_ms = (next_execute_time - now).num_milliseconds();
return Some(delay_ms.max(0));
}
}
return Some(interval_ms);
let next_execute_time = add_interval_to_time(base_time, &interval)?;
let delay_ms = (next_execute_time - now).num_milliseconds();
return Some(delay_ms.max(0));
}
}
None
@@ -197,3 +205,43 @@ impl AutoScoreService {
let _ = app_handle.emit("auto-score:rulesChanged", &self.rules);
}
}
fn parse_interval_trigger_value(value: Option<&String>) -> IntervalTriggerValue {
if let Some(raw_value) = value {
if let Ok(minutes) = raw_value.parse::<i64>() {
if minutes > 0 {
return IntervalTriggerValue {
amount: minutes,
unit: IntervalUnit::Minute,
};
}
}
if let Ok(parsed_value) = serde_json::from_str::<IntervalTriggerValue>(raw_value) {
if parsed_value.amount > 0 {
return parsed_value;
}
}
}
IntervalTriggerValue {
amount: 30,
unit: IntervalUnit::Minute,
}
}
fn add_interval_to_time(
base_time: DateTime<Utc>,
interval: &IntervalTriggerValue,
) -> Option<DateTime<Utc>> {
match interval.unit {
IntervalUnit::Minute => {
base_time.checked_add_signed(chrono::Duration::minutes(interval.amount))
}
IntervalUnit::Day => base_time.checked_add_signed(chrono::Duration::days(interval.amount)),
IntervalUnit::Month => {
let months = u32::try_from(interval.amount).ok()?;
base_time.checked_add_months(Months::new(months))
}
}
}
+1 -3
View File
@@ -431,9 +431,7 @@ impl SettingsService {
"settings",
];
return items.iter().all(|item| {
item.as_str()
.map(|s| allowed.contains(&s))
.unwrap_or(false)
item.as_str().map(|s| allowed.contains(&s)).unwrap_or(false)
});
}
false