新增数据模型和网络层

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
wchino
2026-04-05 22:29:24 +08:00
parent 9c2c5d6f5f
commit e11841f19d
9 changed files with 839 additions and 0 deletions

View File

@@ -0,0 +1,53 @@
package com.miraclegarden.smsmessage.model;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* API错误响应
* **********************
*/
public class ApiError {
private int statusCode;
private String error;
private String message;
public ApiError(int statusCode, String error, String message) {
this.statusCode = statusCode;
this.error = error;
this.message = message;
}
public int getStatusCode() {
return statusCode;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
/**
* 判断是否因token失效导致的错误
*/
public boolean isTokenExpired() {
return statusCode == 401 && message != null &&
(message.contains("登录已失效") || message.contains("token") || message.contains("Token"));
}
}

View File

@@ -0,0 +1,21 @@
package com.miraclegarden.smsmessage.model;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* App通知设置响应
* **********************
*/
public class AppNotificationSettings {
private boolean appNotificationAutoProcessEnabled;
public boolean isAppNotificationAutoProcessEnabled() {
return appNotificationAutoProcessEnabled;
}
public void setAppNotificationAutoProcessEnabled(boolean enabled) {
this.appNotificationAutoProcessEnabled = enabled;
}
}

View File

@@ -0,0 +1,94 @@
package com.miraclegarden.smsmessage.model;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* 银行账户信息数据模型
* **********************
*/
public class BankInfo {
private String id; // 银行ID (bankInfoId)
private String bankName; // 银行名称
private String account; // 账户
private String remark; // 备注
private String userId; // 用户ID
private String createdAt; // 创建时间
private String updatedAt; // 更新时间
public BankInfo() {
}
public BankInfo(String bankName, String account, String remark) {
this.bankName = bankName;
this.account = account;
this.remark = remark;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(String updatedAt) {
this.updatedAt = updatedAt;
}
/**
* 获取显示名称:银行名 + 账户
*/
public String getDisplayName() {
if (account != null && !account.isEmpty()) {
return bankName + " (" + account + ")";
}
return bankName;
}
}

View File

@@ -0,0 +1,23 @@
package com.miraclegarden.smsmessage.model;
import java.util.List;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* 银行账户列表响应
* **********************
*/
public class BankListResponse {
private List<BankInfo> banks;
public List<BankInfo> getBanks() {
return banks;
}
public void setBanks(List<BankInfo> banks) {
this.banks = banks;
}
}

View File

@@ -0,0 +1,37 @@
package com.miraclegarden.smsmessage.model;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* 登录响应数据模型
* **********************
*/
public class LoginResponse {
private String token; // JWT token
private UserInfo user; // 用户信息
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public UserInfo getUser() {
return user;
}
public void setUser(UserInfo user) {
this.user = user;
}
/**
* 判断是否为首次登录
*/
public boolean isFirstLogin() {
return user != null && user.isFirstLogin();
}
}

View File

@@ -0,0 +1,75 @@
package com.miraclegarden.smsmessage.model;
/**
* **********************
*
* @Author MiracleGarden
* 创建时间: 2026/4/5
* 通知上传响应
* **********************
*/
public class UploadNotificationResponse {
private boolean ok;
private int received;
private String id;
private String processingStatus;
private String emailSyncRecordId;
private String duplicateOfEmailSyncRecordId;
private String errorMessage;
public boolean isOk() {
return ok;
}
public void setOk(boolean ok) {
this.ok = ok;
}
public int getReceived() {
return received;
}
public void setReceived(int received) {
this.received = received;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProcessingStatus() {
return processingStatus;
}
public void setProcessingStatus(String processingStatus) {
this.processingStatus = processingStatus;
}
public String getEmailSyncRecordId() {
return emailSyncRecordId;
}
public void setEmailSyncRecordId(String emailSyncRecordId) {
this.emailSyncRecordId = emailSyncRecordId;
}
public String getDuplicateOfEmailSyncRecordId() {
return duplicateOfEmailSyncRecordId;
}
public void setDuplicateOfEmailSyncRecordId(String duplicateOfEmailSyncRecordId) {
this.duplicateOfEmailSyncRecordId = duplicateOfEmailSyncRecordId;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@@ -0,0 +1,76 @@
package com.miraclegarden.smsmessage.model;
import java.util.List;
public class UserInfo {
private String id;
private String username;
private String role;
private boolean isFirstLogin;
private List<String> projectAccesses;
private String parentUserId;
private String googleExcelId;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public boolean isFirstLogin() {
return isFirstLogin;
}
public void setFirstLogin(boolean firstLogin) {
isFirstLogin = firstLogin;
}
public List<String> getProjectAccesses() {
return projectAccesses;
}
public void setProjectAccesses(List<String> projectAccesses) {
this.projectAccesses = projectAccesses;
}
public String getParentUserId() {
return parentUserId;
}
public void setParentUserId(String parentUserId) {
this.parentUserId = parentUserId;
}
public String getGoogleExcelId() {
return googleExcelId;
}
public void setGoogleExcelId(String googleExcelId) {
this.googleExcelId = googleExcelId;
}
public boolean hasProjectAccess(String projectType) {
if (projectAccesses == null || projectAccesses.isEmpty()) {
return false;
}
return projectAccesses.contains(projectType);
}
}

View File

@@ -0,0 +1,376 @@
package com.miraclegarden.smsmessage.network;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
import com.miraclegarden.smsmessage.model.ApiError;
import com.miraclegarden.smsmessage.model.AppNotificationSettings;
import com.miraclegarden.smsmessage.model.BankInfo;
import com.miraclegarden.smsmessage.model.BankListResponse;
import com.miraclegarden.smsmessage.model.LoginResponse;
import com.miraclegarden.smsmessage.model.UploadNotificationResponse;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* API服务类 - 封装所有网络请求
*/
public class ApiService {
private static final String TAG = "ApiService";
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final String BASE_URL = "https://www.judy88.xin";
private final OkHttpClient httpClient;
private final Gson gson;
private final TokenManager tokenManager;
public ApiService(Context context) {
this.tokenManager = TokenManager.getInstance(context);
this.gson = new Gson();
this.httpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
}
/**
* 登录接口
*/
public void login(String username, String password, ApiCallback<LoginResponse> callback) {
try {
JSONObject json = new JSONObject();
json.put("username", username);
json.put("password", password);
json.put("clientType", "app");
Request request = new Request.Builder()
.url(BASE_URL + "/api/auth/login")
.header("Content-Type", "application/json")
.post(RequestBody.create(json.toString(), JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, LoginResponse.class, callback);
}
});
} catch (JSONException e) {
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
}
}
/**
* 修改密码
*/
public void changePassword(String oldPassword, String newPassword, ApiCallback<Void> callback) {
try {
JSONObject json = new JSONObject();
json.put("oldPassword", oldPassword);
json.put("newPassword", newPassword);
Request request = buildAuthRequest("/api/auth/change-password")
.post(RequestBody.create(json.toString(), JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful()) {
callback.onSuccess(null);
} else {
callback.onError(parseError(response));
}
}
});
} catch (JSONException e) {
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
}
}
/**
* 获取银行账户列表
*/
public void getBankList(ApiCallback<List<BankInfo>> callback) {
Request request = buildAuthRequest("/api/bank-email-parser/banks")
.get()
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, BankListResponse.class, new ApiCallback<BankListResponse>() {
@Override
public void onSuccess(BankListResponse result) {
callback.onSuccess(result.getBanks());
}
@Override
public void onError(ApiError error) {
callback.onError(error);
}
});
}
});
}
/**
* 创建银行账户
*/
public void createBank(BankInfo bankInfo, ApiCallback<BankInfo> callback) {
try {
JSONObject json = new JSONObject();
json.put("bankName", bankInfo.getBankName());
json.put("account", bankInfo.getAccount());
if (bankInfo.getRemark() != null) {
json.put("remark", bankInfo.getRemark());
}
Request request = buildAuthRequest("/api/bank-email-parser/banks")
.post(RequestBody.create(json.toString(), JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, BankInfo.class, callback);
}
});
} catch (JSONException e) {
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
}
}
/**
* 更新银行账户
*/
public void updateBank(String bankId, BankInfo bankInfo, ApiCallback<BankInfo> callback) {
try {
JSONObject json = new JSONObject();
json.put("bankName", bankInfo.getBankName());
json.put("account", bankInfo.getAccount());
if (bankInfo.getRemark() != null) {
json.put("remark", bankInfo.getRemark());
}
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
.patch(RequestBody.create(json.toString(), JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, BankInfo.class, callback);
}
});
} catch (JSONException e) {
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
}
}
/**
* 删除银行账户
*/
public void deleteBank(String bankId, ApiCallback<Void> callback) {
Request request = buildAuthRequest("/api/bank-email-parser/banks/" + bankId)
.delete()
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful()) {
callback.onSuccess(null);
} else {
callback.onError(parseError(response));
}
}
});
}
/**
* 获取App通知设置
*/
public void getAppNotificationSettings(ApiCallback<AppNotificationSettings> callback) {
Request request = buildAuthRequest("/api/settings/app-notification")
.get()
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, AppNotificationSettings.class, callback);
}
});
}
/**
* 更新App通知设置
*/
public void updateAppNotificationSettings(boolean enabled, ApiCallback<Void> callback) {
try {
JSONObject json = new JSONObject();
json.put("appNotificationAutoProcessEnabled", enabled);
Request request = buildAuthRequest("/api/settings/app-notification")
.patch(RequestBody.create(json.toString(), JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful()) {
callback.onSuccess(null);
} else {
callback.onError(parseError(response));
}
}
});
} catch (JSONException e) {
callback.onError(new ApiError(0, "JSON_ERROR", e.getMessage()));
}
}
/**
* 上传App通知
*/
public void uploadNotification(String jsonPayload, ApiCallback<UploadNotificationResponse> callback) {
Request request = buildAuthRequest("/api/bills/app-upload")
.post(RequestBody.create(jsonPayload, JSON))
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
callback.onError(new ApiError(0, "NETWORK_ERROR", e.getMessage()));
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
handleResponse(response, UploadNotificationResponse.class, callback);
}
});
}
/**
* 构建带认证的请求
*/
private Request.Builder buildAuthRequest(String path) {
Request.Builder builder = new Request.Builder()
.url(BASE_URL + path)
.header("Content-Type", "application/json");
String authHeader = tokenManager.getAuthHeader();
if (authHeader != null) {
builder.header("Authorization", authHeader);
}
return builder;
}
/**
* 处理响应
*/
private <T> void handleResponse(Response response, Class<T> clazz, ApiCallback<T> callback) {
try {
if (response.isSuccessful()) {
String body = response.body() != null ? response.body().string() : "";
T result = gson.fromJson(body, clazz);
callback.onSuccess(result);
} else {
callback.onError(parseError(response));
}
} catch (Exception e) {
Log.e(TAG, "Response handling error", e);
callback.onError(new ApiError(0, "PARSE_ERROR", e.getMessage()));
} finally {
response.close();
}
}
/**
* 解析错误响应
*/
private ApiError parseError(Response response) {
try {
int code = response.code();
String body = response.body() != null ? response.body().string() : "";
try {
JSONObject json = new JSONObject(body);
String error = json.optString("error", "UNKNOWN_ERROR");
String message = json.optString("message", body);
return new ApiError(code, error, message);
} catch (JSONException e) {
return new ApiError(code, "HTTP_ERROR", body.isEmpty() ? "HTTP " + code : body);
}
} catch (IOException e) {
return new ApiError(response.code(), "READ_ERROR", e.getMessage());
}
}
/**
* API回调接口
*/
public interface ApiCallback<T> {
void onSuccess(T result);
void onError(ApiError error);
}
}

View File

@@ -0,0 +1,84 @@
package com.miraclegarden.smsmessage.network;
import android.content.Context;
import android.content.SharedPreferences;
import com.miraclegarden.smsmessage.model.LoginResponse;
import com.miraclegarden.smsmessage.model.UserInfo;
/**
* JWT Token管理器
*/
public class TokenManager {
private static final String PREFS_NAME = "auth_prefs";
private static final String KEY_TOKEN = "jwt_token";
private static final String KEY_USER_ID = "user_id";
private static final String KEY_USERNAME = "username";
private static final String KEY_ROLE = "role";
private static final String KEY_IS_FIRST_LOGIN = "is_first_login";
private final SharedPreferences prefs;
private static TokenManager instance;
private TokenManager(Context context) {
prefs = context.getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
}
public static synchronized TokenManager getInstance(Context context) {
if (instance == null) {
instance = new TokenManager(context);
}
return instance;
}
public void saveLoginData(LoginResponse response) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_TOKEN, response.getToken());
UserInfo user = response.getUser();
if (user != null) {
editor.putString(KEY_USER_ID, user.getId());
editor.putString(KEY_USERNAME, user.getUsername());
editor.putString(KEY_ROLE, user.getRole());
editor.putBoolean(KEY_IS_FIRST_LOGIN, user.isFirstLogin());
}
editor.apply();
}
public String getToken() {
return prefs.getString(KEY_TOKEN, null);
}
public String getAuthHeader() {
String token = getToken();
return token != null ? "Bearer " + token : null;
}
public boolean isLoggedIn() {
return getToken() != null;
}
public String getUserId() {
return prefs.getString(KEY_USER_ID, null);
}
public String getUsername() {
return prefs.getString(KEY_USERNAME, null);
}
public String getRole() {
return prefs.getString(KEY_ROLE, null);
}
public boolean isFirstLogin() {
return prefs.getBoolean(KEY_IS_FIRST_LOGIN, false);
}
public void setFirstLoginComplete() {
prefs.edit().putBoolean(KEY_IS_FIRST_LOGIN, false).apply();
}
public void clear() {
prefs.edit().clear().apply();
}
}