fix(ios): 🐛 fix ios crash and auto login failure

closed #472
This commit is contained in:
Dawn
2026-01-17 22:38:36 +08:00
parent 7648f777fd
commit c860a140e3
12 changed files with 450 additions and 148 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,5 @@
use crate::AppData;
use crate::command::token_helper::{capture_token_snapshot_arc, persist_token_if_refreshed_arc};
use crate::error::CommonError;
use crate::im_request_client::{ImRequestClient, ImUrl};
use crate::repository::im_contact_repository::{
@@ -71,6 +72,8 @@ async fn fetch_and_update_contacts(
request_client: Arc<Mutex<ImRequestClient>>,
login_uid: String,
) -> Result<Vec<im_contact::Model>, CommonError> {
let old_tokens = capture_token_snapshot_arc(&request_client).await;
let resp: Option<Vec<im_contact::Model>> = request_client
.lock()
.await
@@ -81,6 +84,8 @@ async fn fetch_and_update_contacts(
)
.await?;
persist_token_if_refreshed_arc(&old_tokens, &request_client, &db_conn, &login_uid).await;
if let Some(data) = resp {
// 保存到本地数据库
save_contact_batch(&*db_conn.read().await, data.clone(), &login_uid)
@@ -122,6 +127,8 @@ pub async fn hide_contact_command(
user_info.uid.clone()
};
let old_tokens = capture_token_snapshot_arc(&state.rc).await;
let resp: Option<bool> = state
.rc
.lock()
@@ -133,6 +140,8 @@ pub async fn hide_contact_command(
)
.await?;
persist_token_if_refreshed_arc(&old_tokens, &state.rc, &state.db_conn, &login_uid).await;
if let Some(_) = resp {
// 更新本地数据库
update_contact_hide(

View File

@@ -1,4 +1,8 @@
use crate::AppData;
use crate::command::token_helper::{
capture_token_snapshot_arc, capture_token_snapshot_direct, persist_token_if_refreshed_arc,
persist_token_if_refreshed_direct,
};
use crate::error::CommonError;
use crate::im_request_client::{ImRequestClient, ImUrl};
use crate::pojo::common::{CursorPageParam, CursorPageResp};
@@ -382,10 +386,14 @@ pub async fn fetch_all_messages(
false => None,
};
let old_tokens = capture_token_snapshot_direct(client);
let messages: Option<Vec<MessageResp>> = client
.im_request(ImUrl::GetMsgList, body, None::<serde_json::Value>)
.await?;
persist_token_if_refreshed_direct(&old_tokens, client, db_conn, uid).await;
if let Some(mut messages) = messages {
// 排序消息(按发送时间)
messages.sort_by(|a, b| {
@@ -656,8 +664,11 @@ pub async fn send_msg(
let db_conn = state.db_conn.clone();
let request_client = state.rc.clone();
let mut record_for_send = message_record.clone();
let uid_for_token = login_uid.clone();
tokio::spawn(async move {
let old_tokens = capture_token_snapshot_arc(&request_client).await;
// 发送到后端接口
let result: Result<Option<MessageResp>, anyhow::Error> = {
let mut client = request_client.lock().await;
@@ -666,6 +677,9 @@ pub async fn send_msg(
.await
};
persist_token_if_refreshed_arc(&old_tokens, &request_client, &db_conn, &uid_for_token)
.await;
let mut id = None;
// 根据发送结果更新消息状态

View File

@@ -15,6 +15,7 @@ pub mod oauth_command;
pub mod request_command;
pub mod room_member_command;
pub mod setting_command;
pub mod token_helper;
pub mod upload_command;
pub mod user_command;

View File

@@ -1,14 +1,14 @@
use migration::{Migrator, MigratorTrait};
use tauri::{AppHandle, Emitter, State};
use tracing::{error, info};
use tauri::{AppHandle, Emitter, Manager, State};
use crate::{
AppData,
command::message_command::check_user_init_and_fetch_messages,
command::token_helper::capture_token_snapshot_direct,
configuration::get_configuration,
im_request_client::{ImRequest, ImUrl},
repository::{im_message_repository::reset_table_initialization_flags, im_user_repository},
vo::vo::{LoginReq, LoginResp, RefreshTokenReq},
vo::vo::{LoginReq, LoginResp},
};
#[tauri::command]
@@ -20,72 +20,88 @@ pub async fn login_command(
if data.is_auto_login {
// 自动登录逻辑
if let Some(uid) = &data.uid {
info!("Attempting auto login, user ID: {}", uid);
// 先切换到用户专属数据库
switch_to_user_database(&state, &app_handle, uid).await?;
// 从数据库获取用户的 refresh_token
match im_user_repository::get_user_tokens(&*state.db_conn.read().await, uid).await {
Ok(Some((_, refresh_token))) => {
info!(
"Found refresh_token for user {}, attempting to refresh login",
uid
);
let db_result =
im_user_repository::get_user_tokens(&*state.db_conn.read().await, uid).await;
match db_result {
Ok(Some((token, refresh_token))) => {
if refresh_token.is_empty() {
let check_result: Result<Option<serde_json::Value>, anyhow::Error> = {
let mut rc = state.rc.lock().await;
rc.token = Some(token.clone());
rc.im_request(
ImUrl::CheckToken,
None::<serde_json::Value>,
None::<serde_json::Value>,
)
.await
};
// 使用 refresh_token 刷新登录
let refresh_req = RefreshTokenReq {
refresh_token: refresh_token.clone(),
};
if check_result.is_ok() {
let login_resp = LoginResp {
token,
client: "".to_string(),
refresh_token: refresh_token.clone(),
expire: "".to_string(),
uid: uid.clone(),
};
handle_login_success(&login_resp, &state, data.async_data).await?;
return Ok(Some(login_resp));
}
}
// 使用 start_refresh_token 刷新登录(不会添加 token 头,适合自动登录场景)
let refresh_result = {
let mut rc = state.rc.lock().await;
rc.refresh_token(refresh_req).await
// 设置 ImRequestClient 内部的 refresh_token
rc.refresh_token = Some(refresh_token.clone());
rc.start_refresh_token().await
};
match refresh_result {
Ok(Some(refresh_resp)) => {
info!("Auto login successful, user ID: {}", uid);
Ok(()) => {
// 从 ImRequestClient 中获取刷新后的 token
let rc = state.rc.lock().await;
let new_token = rc.token.clone().unwrap_or_default();
let new_refresh_token = rc.refresh_token.clone().unwrap_or_default();
drop(rc);
// 保存新的 token 信息到数据库
if let Err(e) = im_user_repository::save_user_tokens(
im_user_repository::save_user_tokens(
&*state.db_conn.read().await,
uid,
&refresh_resp.token,
&refresh_resp.refresh_token,
&new_token,
&new_refresh_token,
)
.await
{
error!("Failed to save new token info: {}", e);
}
.ok();
// 转换为 LoginResp 格式返回
let login_resp = LoginResp {
token: refresh_resp.token,
client: "".to_string(), // refresh_token 响应通常不包含 client
refresh_token: refresh_resp.refresh_token,
expire: refresh_resp.expire,
uid: refresh_resp.uid,
token: new_token,
client: "".to_string(),
refresh_token: new_refresh_token,
expire: "".to_string(),
uid: uid.clone(),
};
handle_login_success(&login_resp, &state, data.async_data).await?;
return Ok(Some(login_resp));
}
Ok(None) => {
error!("Auto login failed: refresh token returned empty result");
}
Err(e) => {
error!("Auto login failed: refresh token request failed: {}", e);
let err_str = e.to_string();
if err_str.contains("network_error") {
return Err("网络连接失败,请检查网络后重试".to_string());
}
}
}
}
Ok(None) => {
info!("User {} has no saved token info, cannot auto login", uid);
}
Err(e) => {
error!("Failed to get token info for user {}: {}", uid, e);
}
Ok(None) => {}
Err(_) => {}
};
// 自动登录失败,返回错误让前端切换到手动登录
return Err("自动登录失败,请手动登录".to_string());
@@ -94,8 +110,6 @@ pub async fn login_command(
}
} else {
// 手动登录逻辑
info!("Performing manual login");
let async_data = data.async_data;
let res = {
let mut rc = state.rc.lock().await;
@@ -109,7 +123,6 @@ pub async fn login_command(
handle_login_success(login_resp, &state, async_data).await?;
}
info!("Manual login successful");
Ok(res)
}
}
@@ -120,8 +133,6 @@ async fn switch_to_user_database(
app_handle: &AppHandle,
uid: &str,
) -> Result<(), String> {
info!("Switching to user database for uid: {}", uid);
// 获取配置
let configuration = get_configuration(app_handle)
.map_err(|e| format!("Failed to load configuration: {}", e))?;
@@ -134,13 +145,8 @@ async fn switch_to_user_database(
.map_err(|e| e.to_string())?;
// 执行数据库迁移
match Migrator::up(&new_db, None).await {
Ok(_) => {
info!("Database migration completed for user: {}", uid);
}
Err(e) => {
tracing::warn!("Database migration warning for user {}: {}", uid, e);
}
if let Err(e) = Migrator::up(&new_db, None).await {
tracing::warn!("Database migration warning for user {}: {}", uid, e);
}
// 重置表初始化标志,确保新数据库会创建必要的表
@@ -152,7 +158,6 @@ async fn switch_to_user_database(
*db_guard = new_db;
}
info!("Successfully switched to database for user: {}", uid);
Ok(())
}
@@ -161,7 +166,6 @@ async fn handle_login_success(
state: &State<'_, AppData>,
async_data: bool,
) -> Result<(), String> {
info!("handle_login_success, login_resp: {:?}", login_resp);
// 从登录响应中获取用户标识,这里使用 uid 作为 uid
let uid = &login_resp.uid;
@@ -170,7 +174,6 @@ async fn handle_login_success(
user_info.uid = login_resp.uid.clone();
user_info.token = login_resp.token.clone();
user_info.refresh_token = login_resp.refresh_token.clone();
info!("handle_login_success, user_info: {:?}", user_info);
// 保存 token 信息到数据库
im_user_repository::save_user_tokens(
&*state.db_conn.read().await,
@@ -204,26 +207,62 @@ pub async fn im_request_command(
params: Option<serde_json::Value>,
app_handle: tauri::AppHandle,
) -> Result<Option<serde_json::Value>, String> {
let user_uid = state.user_info.lock().await.uid.clone();
let mut rc = state.rc.lock().await;
// 记录请求前的 token用于检测是否被刷新
let old_tokens = capture_token_snapshot_direct(&rc);
if let Ok(url) = url.parse::<ImUrl>() {
let result: Result<Option<serde_json::Value>, anyhow::Error> =
rc.im_request(url, body, params).await;
// 无论请求成功还是失败,都检查 token 是否被刷新,如果是则保存到数据库
// 这确保了即使请求重试后失败,刷新后的 token 也能被持久化
if let (Some(new_token), Some(new_refresh_token)) =
(rc.token.clone(), rc.refresh_token.clone())
{
if old_tokens.token != rc.token || old_tokens.refresh_token != rc.refresh_token {
if !user_uid.is_empty() {
im_user_repository::save_user_tokens(
&*state.db_conn.read().await,
&user_uid,
&new_token,
&new_refresh_token,
)
.await
.ok();
}
}
}
match result {
Ok(data) => {
return Ok(data);
}
Err(e) => {
tracing::error!("Request error: {}", e);
if e.to_string().contains("请重新登录") {
app_handle.emit_to("home", "relogin", ()).unwrap();
if user_uid.is_empty() {
} else {
if app_handle.get_webview_window("home").is_some() {
if let Err(err) = app_handle.emit_to("home", "relogin", ()) {
let _ = err;
}
} else if app_handle.get_webview_window("mobile-home").is_some() {
if let Err(err) = app_handle.emit_to("mobile-home", "relogin", ()) {
let _ = err;
}
} else {
if let Err(err) = app_handle.emit("relogin", ()) {
let _ = err;
}
}
}
}
return Err(e.to_string());
}
}
} else {
tracing::error!("Invalid URL: {}", url);
return Err(format!("Invalid URL: {}", url));
}
}

View File

@@ -1,4 +1,5 @@
use crate::AppData;
use crate::command::token_helper::{capture_token_snapshot_arc, persist_token_if_refreshed_arc};
use crate::error::CommonError;
use crate::pojo::common::{CursorPageParam, CursorPageResp, Page, PageParam};
use crate::repository::im_room_member_repository::update_my_room_info as update_my_room_info_db;
@@ -51,6 +52,8 @@ pub async fn update_my_room_info(
let uid = user_info.uid.clone();
drop(user_info);
let old_tokens = capture_token_snapshot_arc(&state.rc).await;
// 调用后端接口更新房间信息
let _resp: Option<bool> = state
.rc
@@ -63,6 +66,8 @@ pub async fn update_my_room_info(
)
.await?;
persist_token_if_refreshed_arc(&old_tokens, &state.rc, &state.db_conn, &uid).await;
// 更新本地数据库
update_my_room_info_db(
&*state.db_conn.read().await,
@@ -100,8 +105,15 @@ pub async fn get_room_members(
state: State<'_, AppData>,
) -> Result<Vec<RoomMemberResponse>, String> {
info!("Calling to get all member list of room with room_id");
let uid = state.user_info.lock().await.uid.clone();
let result: Result<Vec<RoomMemberResponse>, CommonError> = async {
let mut members = fetch_and_update_room_members(room_id.clone(), state.rc.clone()).await?;
let mut members = fetch_and_update_room_members(
room_id.clone(),
state.rc.clone(),
state.db_conn.clone(),
&uid,
)
.await?;
sort_room_members(&mut members);
@@ -155,9 +167,12 @@ pub async fn page_room(
page_param: PageParam,
state: State<'_, AppData>,
) -> Result<Page<im_room::Model>, String> {
let uid = state.user_info.lock().await.uid.clone();
let result: Result<Page<im_room::Model>, CommonError> = async {
// 直接调用后端接口获取数据,不保存到数据库
let data = fetch_rooms_from_backend(page_param, state.rc.clone()).await?;
let data =
fetch_rooms_from_backend(page_param, state.rc.clone(), state.db_conn.clone(), &uid)
.await?;
Ok(data)
}
@@ -176,16 +191,23 @@ pub async fn page_room(
async fn fetch_rooms_from_backend(
page_param: PageParam,
request_client: Arc<Mutex<ImRequestClient>>,
db_conn: Arc<tokio::sync::RwLock<sea_orm::DatabaseConnection>>,
uid: &str,
) -> Result<Page<im_room::Model>, CommonError> {
let mut client = request_client.lock().await;
let old_tokens = capture_token_snapshot_arc(&request_client).await;
let resp: Option<Page<im_room::Model>> = client
.im_request(
ImUrl::GroupList,
None::<serde_json::Value>,
Some(page_param),
)
.await?;
let resp: Option<Page<im_room::Model>> = {
let mut client = request_client.lock().await;
client
.im_request(
ImUrl::GroupList,
None::<serde_json::Value>,
Some(page_param),
)
.await?
};
persist_token_if_refreshed_arc(&old_tokens, &request_client, &db_conn, uid).await;
if let Some(data) = resp {
Ok(data)
@@ -223,7 +245,11 @@ fn sort_room_members(members: &mut Vec<RoomMemberResponse>) {
async fn fetch_and_update_room_members(
room_id: String,
request_client: Arc<Mutex<ImRequestClient>>,
db_conn: Arc<tokio::sync::RwLock<sea_orm::DatabaseConnection>>,
uid: &str,
) -> Result<Vec<RoomMemberResponse>, CommonError> {
let old_tokens = capture_token_snapshot_arc(&request_client).await;
let resp: Option<Vec<RoomMemberResponse>> = request_client
.lock()
.await
@@ -236,6 +262,8 @@ async fn fetch_and_update_room_members(
)
.await?;
persist_token_if_refreshed_arc(&old_tokens, &request_client, &db_conn, uid).await;
if let Some(data) = resp {
return Ok(data);
}

View File

@@ -0,0 +1,202 @@
use crate::im_request_client::ImRequestClient;
use crate::repository::im_user_repository;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tracing::{info, warn};
#[derive(Clone, Debug)]
pub struct TokenSnapshot {
pub token: Option<String>,
pub refresh_token: Option<String>,
}
/// Token 刷新检测器
/// 用于在 im_request 调用前后检测 token 是否被刷新,并自动持久化到数据库
pub struct TokenRefreshGuard {
old_tokens: TokenSnapshot,
}
impl TokenRefreshGuard {
/// 在 im_request 调用前创建,记录当前的 token 和 refresh_token
pub async fn before_request(client: &Mutex<ImRequestClient>) -> Self {
let old_tokens = capture_token_snapshot(client).await;
Self { old_tokens }
}
/// 在 im_request 调用后检查 token 是否被刷新,如果是则持久化到数据库
pub async fn persist_if_refreshed(
&self,
client: &Mutex<ImRequestClient>,
db_conn: &RwLock<DatabaseConnection>,
uid: &str,
) {
if uid.is_empty() {
return;
}
let (new_token, new_refresh_token, token_changed) = {
let c = client.lock().await;
let changed = self.old_tokens.token != c.token
|| self.old_tokens.refresh_token != c.refresh_token;
(c.token.clone(), c.refresh_token.clone(), changed)
};
if token_changed {
if let (Some(token), Some(refresh_token)) = (new_token, new_refresh_token) {
match im_user_repository::save_user_tokens(
&*db_conn.read().await,
uid,
&token,
&refresh_token,
)
.await
{
Ok(_) => {
info!(
"[TOKEN_PERSIST] SUCCESS: tokens saved for uid: {}, token_len: {}, refresh_len: {}",
uid,
token.len(),
refresh_token.len()
);
}
Err(e) => {
warn!(
"[TOKEN_PERSIST] FAILED: error saving tokens for uid {}: {}",
uid, e
);
}
}
}
}
}
}
/// 便捷函数:在 im_request 后检查并持久化刷新的 token
///
/// # Arguments
/// * `old_tokens` - 请求前的 token 快照
/// * `client` - ImRequestClient 的锁
/// * `db_conn` - 数据库连接的锁
/// * `uid` - 用户 ID
pub async fn persist_token_if_refreshed(
old_tokens: &TokenSnapshot,
client: &Mutex<ImRequestClient>,
db_conn: &RwLock<DatabaseConnection>,
uid: &str,
) {
if uid.is_empty() {
return;
}
let (new_token, new_refresh_token, token_changed) = {
let c = client.lock().await;
let changed = old_tokens.token != c.token || old_tokens.refresh_token != c.refresh_token;
(c.token.clone(), c.refresh_token.clone(), changed)
};
if token_changed {
if let (Some(token), Some(refresh_token)) = (new_token, new_refresh_token) {
match im_user_repository::save_user_tokens(
&*db_conn.read().await,
uid,
&token,
&refresh_token,
)
.await
{
Ok(_) => {
info!(
"[TOKEN_PERSIST] SUCCESS: tokens saved for uid: {}, token_len: {}, refresh_len: {}",
uid,
token.len(),
refresh_token.len()
);
}
Err(e) => {
warn!(
"[TOKEN_PERSIST] FAILED: error saving tokens for uid {}: {}",
uid, e
);
}
}
}
}
}
/// 便捷函数:获取当前的 token 快照用于后续比较
pub async fn capture_token_snapshot(client: &Mutex<ImRequestClient>) -> TokenSnapshot {
let c = client.lock().await;
TokenSnapshot {
token: c.token.clone(),
refresh_token: c.refresh_token.clone(),
}
}
pub async fn capture_token_snapshot_arc(client: &Arc<Mutex<ImRequestClient>>) -> TokenSnapshot {
capture_token_snapshot(client.as_ref()).await
}
pub fn capture_token_snapshot_direct(client: &ImRequestClient) -> TokenSnapshot {
TokenSnapshot {
token: client.token.clone(),
refresh_token: client.refresh_token.clone(),
}
}
/// 便捷函数:获取当前的 refresh_token 用于后续比较
pub async fn capture_refresh_token(client: &Mutex<ImRequestClient>) -> Option<String> {
client.lock().await.refresh_token.clone()
}
/// 便捷函数:使用 Arc 包装的类型
pub async fn persist_token_if_refreshed_arc(
old_tokens: &TokenSnapshot,
client: &Arc<Mutex<ImRequestClient>>,
db_conn: &Arc<RwLock<DatabaseConnection>>,
uid: &str,
) {
persist_token_if_refreshed(old_tokens, client.as_ref(), db_conn.as_ref(), uid).await
}
pub async fn capture_refresh_token_arc(client: &Arc<Mutex<ImRequestClient>>) -> Option<String> {
capture_refresh_token(client.as_ref()).await
}
/// 便捷函数:使用直接引用的 ImRequestClient不需要 Mutex
pub async fn persist_token_if_refreshed_direct(
old_tokens: &TokenSnapshot,
client: &ImRequestClient,
db_conn: &DatabaseConnection,
uid: &str,
) {
if uid.is_empty() {
return;
}
let token_changed =
old_tokens.token != client.token || old_tokens.refresh_token != client.refresh_token;
if token_changed {
if let (Some(token), Some(refresh_token)) =
(client.token.clone(), client.refresh_token.clone())
{
match im_user_repository::save_user_tokens(db_conn, uid, &token, &refresh_token).await {
Ok(_) => {
info!(
"[TOKEN_PERSIST] SUCCESS: tokens saved for uid: {}, token_len: {}, refresh_len: {}",
uid,
token.len(),
refresh_token.len()
);
}
Err(e) => {
warn!(
"[TOKEN_PERSIST] FAILED: error saving tokens for uid {}: {}",
uid, e
);
}
}
}
}
}

View File

@@ -136,22 +136,34 @@ pub async fn update_token(
state: State<'_, AppData>,
) -> Result<(), String> {
info!("Updating user token");
let refresh_token = if req.refresh_token.is_empty() {
let current_refresh = state.user_info.lock().await.refresh_token.clone();
if current_refresh.is_empty() {
"".to_string()
} else {
current_refresh
}
} else {
req.refresh_token.clone()
};
{
let mut user_info = state.user_info.lock().await;
user_info.uid = req.uid.clone();
user_info.token = req.token.clone();
user_info.refresh_token = req.refresh_token.clone();
user_info.refresh_token = refresh_token.clone();
}
{
let mut rc = state.rc.lock().await;
rc.token = Some(req.token.clone());
rc.refresh_token = Some(req.refresh_token.clone());
if !refresh_token.is_empty() {
rc.refresh_token = Some(refresh_token.clone());
}
}
im_user_repository::save_user_tokens(
&*state.db_conn.read().await,
&req.uid,
&req.token,
&req.refresh_token,
&refresh_token,
)
.await
.map_err(|e| e.to_string())?;

View File

@@ -1,14 +1,13 @@
use std::str::FromStr;
use anyhow::Ok;
use base64::{Engine, prelude::BASE64_STANDARD};
use reqwest::header;
use serde_json::json;
use tracing::{error, info};
use tracing::error;
use crate::{
pojo::common::ApiResult,
vo::vo::{LoginReq, LoginResp, RefreshTokenReq, RefreshTokenResp},
vo::vo::{LoginReq, LoginResp},
};
#[derive(Debug)]
@@ -67,8 +66,6 @@ impl ImRequestClient {
extra_headers: Option<Vec<(&str, &str)>>,
) -> reqwest::RequestBuilder {
let url = format!("{}/{}", self.base_url, path);
info!("Request URL: {}, Method: {}", &url, method);
let mut request_builder = self.client.request(method, &url);
// 设置 token 请求头
@@ -116,9 +113,15 @@ impl ImRequestClient {
// 使用 build_request 构建请求
let request_builder = self.build_request(method.clone(), path, &body, &params, None);
// 发送请求
let response = request_builder.send().await?;
let result: ApiResult<T> = response.json().await?;
// 发送请求,区分网络错误和业务错误
let response = request_builder
.send()
.await
.map_err(|e| anyhow::anyhow!("network_error: {}", e))?;
let result: ApiResult<T> = response
.json()
.await
.map_err(|e| anyhow::anyhow!("network_error: {}", e))?;
let url = format!("{}/{}", self.base_url, path);
@@ -127,8 +130,6 @@ impl ImRequestClient {
if retry_count >= MAX_RETRY_COUNT {
return Err(anyhow::anyhow!("token过期刷新token失败"));
}
error!("Token expired, starting token refresh");
self.start_refresh_token().await?;
retry_count += 1;
continue;
@@ -143,7 +144,6 @@ impl ImRequestClient {
return Err(anyhow::anyhow!("请重新登录"));
}
Some(200) => {
info!("Request successful: {}, Method: {}", &url, method.clone());
return Ok(result);
}
_ => {
@@ -217,44 +217,60 @@ impl ImRequestClient {
}
}
info!("流式请求成功,开始接收流式数据");
Ok(response)
}
pub async fn start_refresh_token(&mut self) -> Result<(), anyhow::Error> {
info!("Starting token refresh");
let url = format!("{}/{}", self.base_url, ImUrl::RefreshToken.get_url().1);
let refresh_token = match self.refresh_token.clone() {
Some(token) if !token.is_empty() => token,
_ => return Err(anyhow::anyhow!("请重新登录")),
};
let old_refresh_token = refresh_token.clone();
let body = json!({
"refreshToken": self.refresh_token.clone().unwrap()
"refreshToken": refresh_token
});
let request_builder = self.client.request(http::Method::POST, &url);
let response = request_builder.json(&body).send().await?;
let result: ApiResult<serde_json::Value> = response.json().await?;
let response = request_builder
.json(&body)
.send()
.await
.map_err(|e| anyhow::anyhow!("network_error: {}", e))?;
let result: ApiResult<serde_json::Value> = response
.json()
.await
.map_err(|e| anyhow::anyhow!("network_error: {}", e))?;
if !result.success {
error!("刷新token失败: {}", result.msg.clone().unwrap_or_default());
return Err(anyhow::anyhow!(
"刷新token失败: {}",
result.msg.clone().unwrap_or_default()
));
// 服务器明确拒绝 refresh token需要重新登录
return Err(anyhow::anyhow!("请重新登录"));
}
if let Some(token) = result.data.clone().unwrap().get("token").unwrap().as_str() {
self.token = Some(token.to_owned());
}
let data = match result.data {
Some(data) => data,
None => {
error!("刷新token失败: 响应缺少 data 字段");
return Err(anyhow::anyhow!("请重新登录"));
}
};
if let Some(refresh_token) = result
.data
.clone()
.unwrap()
.get("refreshToken")
.unwrap()
.as_str()
{
self.refresh_token = Some(refresh_token.to_owned());
}
let token = match data.get("token").and_then(|value| value.as_str()) {
Some(value) => value,
None => {
error!("刷新token失败: 响应缺少 token 字段");
return Err(anyhow::anyhow!("请重新登录"));
}
};
self.token = Some(token.to_owned());
let refresh_token = match data.get("refreshToken").and_then(|value| value.as_str()) {
Some(value) if !value.is_empty() => value.to_owned(),
_ => old_refresh_token,
};
self.refresh_token = Some(refresh_token);
Ok(())
}
@@ -288,26 +304,6 @@ impl ImRequest for ImRequestClient {
Ok(result)
}
async fn refresh_token(
&mut self,
refresh_token_req: RefreshTokenReq,
) -> Result<Option<RefreshTokenResp>, anyhow::Error> {
let result: Option<RefreshTokenResp> = self
.im_request(
ImUrl::RefreshToken,
Some(refresh_token_req),
None::<serde_json::Value>,
)
.await?;
if let Some(data) = result.clone() {
self.token = Some(data.token.clone());
self.refresh_token = Some(data.refresh_token.clone());
}
Ok(result)
}
}
pub enum ImUrl {
@@ -1145,8 +1141,4 @@ impl FromStr for ImUrl {
pub trait ImRequest {
async fn login(&mut self, login_req: LoginReq) -> Result<Option<LoginResp>, anyhow::Error>;
async fn refresh_token(
&mut self,
refresh_token_req: RefreshTokenReq,
) -> Result<Option<RefreshTokenResp>, anyhow::Error>;
}

View File

@@ -184,7 +184,7 @@ async fn initialize_app_data(
let rc: im_request_client::ImRequestClient = im_request_client::ImRequestClient::new(
configuration.lock().await.backend.base_url.clone(),
)
.unwrap();
.map_err(|e| anyhow::anyhow!("Failed to create request client: {}", e))?;
// 创建用户信息
let user_info = UserInfo {
@@ -343,7 +343,9 @@ fn setup_mobile() {
// 公共的 setup 函数
fn common_setup(app_handle: AppHandle) -> Result<(), Box<dyn std::error::Error>> {
let scope = app_handle.fs_scope();
scope.allow_directory("configuration", false).unwrap();
if let Err(e) = scope.allow_directory("configuration", false) {
tracing::warn!("Failed to allow configuration directory: {}", e);
}
#[cfg(desktop)]
setup_logout_listener(app_handle.clone());

View File

@@ -51,12 +51,30 @@ where
CommonError::DatabaseError(e)
})?;
let refresh_token_to_save = if refresh_token.is_empty() {
if let Some(user) = &existing_user {
if let Some(saved_refresh) = user.refresh_token.clone() {
if !saved_refresh.is_empty() {
saved_refresh
} else {
"".to_string()
}
} else {
"".to_string()
}
} else {
"".to_string()
}
} else {
refresh_token.to_string()
};
let user_update = if existing_user.is_some() {
// 用户存在,更新 token 信息
im_user::ActiveModel {
id: Set(login_uid.to_string()),
token: Set(Some(token.to_string())),
refresh_token: Set(Some(refresh_token.to_string())),
refresh_token: Set(Some(refresh_token_to_save.clone())),
..Default::default()
}
} else {
@@ -64,7 +82,7 @@ where
im_user::ActiveModel {
id: Set(login_uid.to_string()),
token: Set(Some(token.to_string())),
refresh_token: Set(Some(refresh_token.to_string())),
refresh_token: Set(Some(refresh_token_to_save.clone())),
is_init: Set(true), // 新用户默认未初始化
..Default::default()
}

View File

@@ -47,18 +47,3 @@ pub struct LoginResp {
pub uid: String,
pub expire: String,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RefreshTokenReq {
pub refresh_token: String,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RefreshTokenResp {
pub token: String,
pub refresh_token: String,
pub expire: String,
pub uid: String,
}