853 lines
35 KiB
Java
853 lines
35 KiB
Java
package com.example.myapplication;
|
||
|
||
import static android.content.ContentValues.TAG;
|
||
|
||
import android.annotation.SuppressLint;
|
||
import android.app.ProgressDialog;
|
||
import android.content.ClipData;
|
||
import android.content.ClipboardManager;
|
||
import android.content.Context;
|
||
import android.content.Intent;
|
||
import android.content.SharedPreferences;
|
||
import android.graphics.Color;
|
||
import android.net.ConnectivityManager;
|
||
import android.net.NetworkCapabilities;
|
||
import android.net.NetworkInfo;
|
||
import android.os.Build;
|
||
import android.os.Bundle;
|
||
import android.os.Handler;
|
||
import android.os.Looper;
|
||
import android.util.Base64;
|
||
import android.util.Log;
|
||
import android.util.Pair;
|
||
import android.view.View;
|
||
import android.view.ViewGroup;
|
||
import android.widget.AdapterView;
|
||
import android.widget.ArrayAdapter;
|
||
import android.widget.AutoCompleteTextView;
|
||
import android.widget.Button;
|
||
import android.widget.CheckBox;
|
||
import android.widget.EditText;
|
||
import android.widget.Filter;
|
||
import android.widget.LinearLayout;
|
||
import android.widget.TextView;
|
||
import android.widget.Toast;
|
||
|
||
import androidx.annotation.NonNull;
|
||
import androidx.annotation.Nullable;
|
||
import androidx.appcompat.app.AlertDialog;
|
||
import androidx.appcompat.app.AppCompatActivity;
|
||
|
||
import com.example.myapplication.DataBase.DatabaseHelper;
|
||
import com.example.myapplication.Tool.AuthInterceptor;
|
||
import com.example.myapplication.Tool.RetrofitClient;
|
||
import com.example.myapplication.Tool.ShowError;
|
||
import com.example.myapplication.api.AuthApi;
|
||
import com.example.myapplication.api.ProjectApi;
|
||
import com.example.myapplication.api.UserApi;
|
||
import com.example.myapplication.model.ApiResponse;
|
||
import com.example.myapplication.model.LoginEntity;
|
||
import com.example.myapplication.model.LoginRequest;
|
||
import com.example.myapplication.model.PageResult;
|
||
import com.example.myapplication.model.Project;
|
||
import com.example.myapplication.model.UserInfo;
|
||
|
||
import java.io.File;
|
||
import java.io.FileWriter;
|
||
import java.io.IOException;
|
||
import java.io.PrintWriter;
|
||
import java.io.StringWriter;
|
||
import java.lang.reflect.Method;
|
||
import java.lang.reflect.ParameterizedType;
|
||
import java.lang.reflect.Type;
|
||
import java.net.SocketTimeoutException;
|
||
import java.net.UnknownHostException;
|
||
import java.security.MessageDigest;
|
||
import java.security.NoSuchAlgorithmException;
|
||
import java.text.SimpleDateFormat;
|
||
import java.util.ArrayList;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.concurrent.ExecutorService;
|
||
import java.util.concurrent.Executors;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.regex.Matcher;
|
||
import java.util.regex.Pattern;
|
||
|
||
import javax.crypto.Cipher;
|
||
import javax.crypto.spec.SecretKeySpec;
|
||
|
||
import okhttp3.HttpUrl;
|
||
import okhttp3.OkHttpClient;
|
||
import okhttp3.Request;
|
||
import okhttp3.ResponseBody;
|
||
import okhttp3.logging.HttpLoggingInterceptor;
|
||
import retrofit2.Call;
|
||
import retrofit2.Callback;
|
||
import retrofit2.Response;
|
||
import retrofit2.Retrofit;
|
||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||
import retrofit2.converter.gson.GsonConverterFactory;
|
||
import retrofit2.http.Body;
|
||
import retrofit2.http.Header;
|
||
import retrofit2.http.Headers;
|
||
import retrofit2.http.POST;
|
||
import retrofit2.http.PUT;
|
||
|
||
|
||
public class LoginActivity extends AppCompatActivity {
|
||
private EditText etUsername, etPassword;
|
||
private CheckBox cbRemember;
|
||
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||
private final ExecutorService executor = Executors.newFixedThreadPool(7);
|
||
|
||
private Button btnClearProject;
|
||
private Button btnLogin;
|
||
|
||
private DatabaseHelper dbHelper;
|
||
private SharedPreferences sharedPreferences;
|
||
private final String AUTH_TOKEN_KEY = "auth_token";
|
||
private static final String Name = "name";
|
||
private static final String BASE_URL = "http://pms.dtyx.net:9158/";
|
||
private String authToken = "";
|
||
private static final String PREFS_NAME = "LoginPrefs";
|
||
private static final String PREF_USERNAME = "username";
|
||
private static final String PREF_PASSWORD = "password";
|
||
private static final String PREF_REMEMBER = "remember";
|
||
private static final String PREF_PROJECT = "project"; // 新增项目键名
|
||
private AutoCompleteTextView actvProject;
|
||
private ArrayAdapter<Project> projectAdapter;
|
||
private TextView tvSelectedProjectName;
|
||
private TextView tvSelectedProjectId;
|
||
private List<Project> projectList = new ArrayList<>();
|
||
ShowError errorShower = new ShowError(this);
|
||
|
||
|
||
|
||
@SuppressLint("SuspiciousIndentation")
|
||
@Override
|
||
protected void onCreate(Bundle savedInstanceState) {
|
||
super.onCreate(savedInstanceState);
|
||
setContentView(R.layout.activity_login);
|
||
|
||
dbHelper = new DatabaseHelper(this);
|
||
// 使用 Context.MODE_PRIVATE 替代 MODE_PRIVATE 常量
|
||
sharedPreferences = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||
|
||
initViews();
|
||
setupListeners();
|
||
loadSavedPreferences();
|
||
loadProjects();
|
||
}
|
||
|
||
private void initViews() {
|
||
etUsername = findViewById(R.id.etUsername);
|
||
etPassword = findViewById(R.id.etPassword);
|
||
actvProject = findViewById(R.id.etProject);
|
||
cbRemember = findViewById(R.id.cbRemember);
|
||
btnLogin = findViewById(R.id.btnLogin);
|
||
btnClearProject = findViewById(R.id.btnClearProject);
|
||
tvSelectedProjectName = findViewById(R.id.tvSelectedProjectName);
|
||
tvSelectedProjectId = findViewById(R.id.tvSelectedProjectId);
|
||
|
||
// 初始化项目下拉框
|
||
initProjectDropdown();
|
||
}
|
||
|
||
private void setupListeners() {
|
||
// 清空项目按钮点击事件
|
||
btnClearProject.setOnClickListener(v -> {
|
||
actvProject.setText("");
|
||
tvSelectedProjectName.setText("项目名称:未选择");
|
||
tvSelectedProjectId.setText("项目ID:未选择");
|
||
loadProjects();
|
||
projectAdapter.getFilter().filter("");
|
||
actvProject.showDropDown();
|
||
});
|
||
|
||
btnLogin.setOnClickListener(v -> attemptLogin());
|
||
}
|
||
private boolean isNetworkAvailable() {
|
||
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||
if (cm == null) return false;
|
||
|
||
NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork());
|
||
return capabilities != null &&
|
||
(capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET));
|
||
}
|
||
private void loadSavedPreferences() {
|
||
boolean remember = sharedPreferences.getBoolean(PREF_REMEMBER, false);
|
||
if (remember) {
|
||
String savedUsername = sharedPreferences.getString(PREF_USERNAME, "");
|
||
String savedPassword = sharedPreferences.getString(PREF_PASSWORD, "");
|
||
String savedProject = sharedPreferences.getString(PREF_PROJECT, "");
|
||
|
||
etUsername.setText(savedUsername);
|
||
etPassword.setText(savedPassword);
|
||
actvProject.setText(savedProject);
|
||
|
||
if (!savedProject.isEmpty()) {
|
||
try {
|
||
int lastBracketIndex = savedProject.lastIndexOf("(");
|
||
int closingBracketIndex = savedProject.lastIndexOf(")");
|
||
|
||
if (lastBracketIndex != -1 && closingBracketIndex != -1 && closingBracketIndex > lastBracketIndex) {
|
||
String projectName = savedProject.substring(0, lastBracketIndex).trim();
|
||
String projectId = savedProject.substring(lastBracketIndex + 1, closingBracketIndex).trim();
|
||
|
||
tvSelectedProjectName.setText("项目名称:" + projectName);
|
||
tvSelectedProjectId.setText("项目ID:" + projectId);
|
||
}
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
tvSelectedProjectName.setText("解析错误");
|
||
tvSelectedProjectId.setText("");
|
||
}
|
||
}
|
||
cbRemember.setChecked(true);
|
||
}
|
||
}
|
||
|
||
private void attemptLogin() {
|
||
String username = etUsername.getText().toString().trim();
|
||
String password = etPassword.getText().toString().trim();
|
||
String projectInput = actvProject.getText().toString().trim();
|
||
|
||
if (username.isEmpty() || password.isEmpty() || projectInput.isEmpty()) {
|
||
Toast.makeText(LoginActivity.this, "请填写所有字段", Toast.LENGTH_SHORT).show();
|
||
return;
|
||
}
|
||
|
||
// 在后台线程执行数据库操作
|
||
// 先进行认证
|
||
if(isNetworkAvailable())
|
||
authenticateUser(username, password, projectInput);
|
||
else continueLoginProcess(username,password,projectInput);
|
||
}
|
||
private void authenticateUser(String username, String password, String projectInput) {
|
||
|
||
executor.execute(() -> {
|
||
try {
|
||
// 加密密码
|
||
String encryptedPassword2 = encryptPassword(username, password);
|
||
|
||
|
||
// 创建认证请求体
|
||
LoginRequest loginRequest = new LoginRequest(username, encryptedPassword2);
|
||
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
|
||
// 初始化Retrofit
|
||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||
|
||
// 创建认证API服务
|
||
AuthApi authApi = retrofit.create(AuthApi.class);
|
||
|
||
// 执行认证请求
|
||
Call<ApiResponse<LoginEntity>> call = authApi.login(loginRequest);
|
||
Response<ApiResponse<LoginEntity>> response = call.execute();
|
||
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
|
||
if (response.body().getCode() == 500200) {
|
||
//修改密码操作
|
||
runOnUiThread(() -> showChangePasswordDialog(username));
|
||
} else if (response.body().getCode() == 200) {
|
||
|
||
authToken = response.body().getData().getTokenValue();
|
||
Log.e("令牌:",authToken);
|
||
sharedPreferences.edit().putString(AUTH_TOKEN_KEY, authToken).apply();
|
||
Retrofit retrofit2 = RetrofitClient.getClient(authToken);
|
||
// 创建UserApi服务
|
||
UserApi userApi = retrofit2.create(UserApi.class);
|
||
|
||
// 执行查询请求,只根据account查询
|
||
Call<PageResult<UserInfo>> userCall = userApi.getUserList(
|
||
authToken, // 注意token前缀,根据你的API要求调整
|
||
username, // account参数
|
||
null, // deptId
|
||
null, // mobile
|
||
null, // name
|
||
null, // userCode
|
||
null, // userStatus
|
||
null // userType
|
||
);
|
||
|
||
try {
|
||
|
||
Response<PageResult<UserInfo>> userResponse = userCall.execute();
|
||
if (userResponse.isSuccessful() && userResponse.body() != null
|
||
&& userResponse.body().getCode() == 200
|
||
&& !userResponse.body().getRows().isEmpty()) {
|
||
// 获取第一个匹配用户的name
|
||
String name = userResponse.body().getRows().get(0).getName();
|
||
sharedPreferences.edit().putString(Name, name).apply();
|
||
executor.execute(()-> {
|
||
|
||
continueLoginProcess(username, password, projectInput);
|
||
});
|
||
|
||
} else {
|
||
// 查询失败或没有结果,使用username作为name
|
||
errorShower.showErrorDialog("查询姓名失败" , userResponse.body().getMsg());
|
||
|
||
sharedPreferences.edit().putString(Name, "").apply();
|
||
executor.execute(()-> {
|
||
|
||
continueLoginProcess(username, password, projectInput);
|
||
});
|
||
}
|
||
} catch (IOException e) {
|
||
e.printStackTrace();
|
||
// 发生异常,使用username作为name
|
||
errorShower.showErrorDialog("出现异常:", e.getMessage());
|
||
executor.execute(()-> {
|
||
|
||
continueLoginProcess(username, password, projectInput);
|
||
});
|
||
}
|
||
|
||
|
||
}
|
||
else {
|
||
showToast(password+"密码出错"+response.body().toString());
|
||
}
|
||
} else {
|
||
runOnUiThread(() -> {
|
||
String errorMsg = "认证失败: ";
|
||
if (response.errorBody() != null) {
|
||
try {
|
||
errorMsg += response.errorBody().string();
|
||
} catch (IOException e) {
|
||
errorMsg += "无法读取错误详情";
|
||
}
|
||
}
|
||
showToast(errorMsg);
|
||
});
|
||
}
|
||
} catch (Exception e) {
|
||
showToast("认证出错:"+e.getMessage());
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
// 封装 Toast 显示
|
||
private void showToast(String message) {
|
||
mainHandler.post(() -> Toast.makeText(LoginActivity.this, message, Toast.LENGTH_SHORT).show());
|
||
}
|
||
|
||
private void showChangePasswordDialog(String username) {
|
||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||
builder.setTitle("请修改密码");
|
||
|
||
View view = getLayoutInflater().inflate(R.layout.dialog_change_password, null);
|
||
EditText etOldPassword = view.findViewById(R.id.etOldPassword);
|
||
EditText etNewPassword = view.findViewById(R.id.etNewPassword);
|
||
EditText etConfirmPassword = view.findViewById(R.id.etConfirmPassword);
|
||
|
||
builder.setView(view);
|
||
builder.setPositiveButton("确认", (dialog, which) -> {
|
||
String oldPassword = etOldPassword.getText().toString().trim();
|
||
String newPassword = etNewPassword.getText().toString().trim();
|
||
String confirmPassword = etConfirmPassword.getText().toString().trim();
|
||
|
||
if (newPassword.equals(confirmPassword)) {
|
||
changePassword(username, oldPassword, newPassword);
|
||
} else {
|
||
Toast.makeText(this, "新密码与确认密码不匹配", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
builder.setNegativeButton("取消", null);
|
||
builder.setCancelable(false);
|
||
builder.show();
|
||
}
|
||
|
||
private void changePassword(String username, String oldPassword, String newPassword) {
|
||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||
executor.execute(() -> {
|
||
try {
|
||
// 加密旧密码和新密码
|
||
String encryptedOldPassword = encryptPassword(username, oldPassword);
|
||
String encryptedNewPassword = encryptPassword(username, newPassword);
|
||
|
||
// 创建修改密码请求体
|
||
ChangePasswordRequest request = new ChangePasswordRequest(
|
||
username,
|
||
encryptedNewPassword,
|
||
encryptedOldPassword
|
||
);
|
||
|
||
// 获取token
|
||
String token = sharedPreferences.getString(AUTH_TOKEN_KEY, "");
|
||
|
||
// 初始化Retrofit
|
||
Retrofit retrofit = RetrofitClient.getClient(token);
|
||
|
||
// 创建API服务
|
||
AuthApi authApi = retrofit.create(AuthApi.class);
|
||
|
||
// 执行修改密码请求
|
||
Call<ApiResponse<Void>> call = authApi.modifyPassword(request);
|
||
Response<ApiResponse<Void>> response = call.execute();
|
||
|
||
runOnUiThread(() -> {
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
if (response.body().isSuccess()) {
|
||
Toast.makeText(LoginActivity.this, "密码修改成功,请重新登录", Toast.LENGTH_SHORT).show();
|
||
} else {
|
||
Toast.makeText(LoginActivity.this, "密码修改失败: " + response.body().toString(), Toast.LENGTH_SHORT).show();
|
||
}
|
||
} else {
|
||
String errorMsg = "修改密码失败: ";
|
||
if (response.errorBody() != null) {
|
||
try {
|
||
errorMsg += response.errorBody().string();
|
||
} catch (IOException e) {
|
||
errorMsg += "无法读取错误详情";
|
||
}
|
||
}
|
||
Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
runOnUiThread(() ->
|
||
Toast.makeText(LoginActivity.this, "修改密码出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
|
||
}
|
||
});
|
||
}
|
||
|
||
// 新增修改密码请求体
|
||
public static class ChangePasswordRequest {
|
||
private String account;
|
||
private String newPassword;
|
||
private String oldPassword;
|
||
|
||
public ChangePasswordRequest(String account, String newPassword, String oldPassword) {
|
||
this.account = account;
|
||
this.newPassword = newPassword;
|
||
this.oldPassword = oldPassword;
|
||
}
|
||
|
||
// getters and setters
|
||
public String getAccount() {
|
||
return account;
|
||
}
|
||
|
||
public void setAccount(String account) {
|
||
this.account = account;
|
||
}
|
||
|
||
public String getNewPassword() {
|
||
return newPassword;
|
||
}
|
||
|
||
public void setNewPassword(String newPassword) {
|
||
this.newPassword = newPassword;
|
||
}
|
||
|
||
public String getOldPassword() {
|
||
return oldPassword;
|
||
}
|
||
|
||
public void setOldPassword(String oldPassword) {
|
||
this.oldPassword = oldPassword;
|
||
}
|
||
}
|
||
private void continueLoginProcess(String username, String password, String projectInput) {
|
||
// 在后台线程执行数据库操作
|
||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||
executor.execute(() -> {
|
||
try {
|
||
// 解析项目ID和名称
|
||
Pair<String, String> projectInfo = parseProjectInfo(projectInput);
|
||
String projectId = projectInfo.first;
|
||
String projectName = projectInfo.second;
|
||
|
||
// 保存登录信息
|
||
saveLoginPreferences(username, password, projectName, projectId);
|
||
|
||
// 检查用户是否存在
|
||
boolean userExists = dbHelper.userExists(username);
|
||
|
||
runOnUiThread(() -> {
|
||
if (userExists) {
|
||
// 用户存在,验证密码
|
||
if (dbHelper.checkUser(username, password)) {
|
||
handleLoginSuccess(username, projectId, projectName,password);
|
||
} else {
|
||
Toast.makeText(LoginActivity.this, "密码错误", Toast.LENGTH_SHORT).show();
|
||
}
|
||
} else {
|
||
// 用户不存在,自动注册
|
||
if (dbHelper.addUser(username, password, projectId, projectName)) {
|
||
handleLoginSuccess(username, projectId, projectName,password);
|
||
} else {
|
||
Toast.makeText(LoginActivity.this, "注册失败", Toast.LENGTH_SHORT).show();
|
||
}
|
||
}
|
||
});
|
||
} catch (Exception e) {
|
||
runOnUiThread(() ->
|
||
Toast.makeText(LoginActivity.this, "登录出错: " + e.getMessage(), Toast.LENGTH_SHORT).show());
|
||
}
|
||
});
|
||
}
|
||
|
||
private String encryptPassword(String username, String password) {
|
||
try {
|
||
// 1. 对账号做MD5计算
|
||
String md5Hash = md5(username);
|
||
|
||
// 2. 取8-24位作为密钥
|
||
String key = md5Hash.substring(8, 24); // 8-24位(索引从0开始)
|
||
Log.e("key:",key);
|
||
// 3. AES加密
|
||
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
|
||
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // PKCS5Padding等同于PKCS7Padding在Java中
|
||
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||
|
||
byte[] encryptedBytes = cipher.doFinal(password.getBytes());
|
||
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT).trim();
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
showToast("加密密码错误:"+e.getMessage());
|
||
return "";
|
||
}
|
||
}
|
||
|
||
private String md5(String input) {
|
||
try {
|
||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||
byte[] messageDigest = md.digest(input.getBytes());
|
||
|
||
StringBuilder hexString = new StringBuilder();
|
||
for (byte b : messageDigest) {
|
||
String hex = Integer.toHexString(0xff & b);
|
||
if (hex.length() == 1) hexString.append('0');
|
||
hexString.append(hex);
|
||
}
|
||
return hexString.toString();
|
||
} catch (NoSuchAlgorithmException e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private Pair<String, String> parseProjectInfo(String projectInput) {
|
||
String projectId = "";
|
||
String projectName = "";
|
||
|
||
Matcher matcher = Pattern.compile(".*\\((.*)\\)").matcher(projectInput);
|
||
if (matcher.find()) {
|
||
projectId = matcher.group(1).trim();
|
||
projectName = projectInput.replace("(" + projectId + ")", "").trim();
|
||
} else {
|
||
for (Project project : projectList) {
|
||
if (project.getProjectName().equals(projectInput) ||
|
||
project.getProjectId().equals(projectInput)) {
|
||
projectId = project.getProjectId();
|
||
projectName = project.getProjectName();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (projectId.isEmpty()) {
|
||
projectName = projectInput;
|
||
}
|
||
}
|
||
return new Pair<>(projectId, projectName);
|
||
}
|
||
|
||
private void saveLoginPreferences(String username, String password, String projectName, String projectId) {
|
||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||
if (cbRemember.isChecked()) {
|
||
editor.putString(PREF_USERNAME, username);
|
||
editor.putString(PREF_PASSWORD, password);
|
||
String projectDisplayText = projectId.isEmpty() ? projectName :
|
||
(projectName + " (" + projectId + ")");
|
||
editor.putString(PREF_PROJECT, projectDisplayText);
|
||
editor.putBoolean(PREF_REMEMBER, true);
|
||
} else {
|
||
editor.clear();
|
||
}
|
||
editor.apply();
|
||
}
|
||
|
||
private void handleLoginSuccess(String username, String projectId, String projectName,String password) {
|
||
dbHelper.updateUserProject(username, projectId, projectName);
|
||
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
|
||
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||
intent.putExtra("username", username);
|
||
intent.putExtra("password",password);
|
||
intent.putExtra("projectId", projectId);
|
||
intent.putExtra("projectName", projectName);
|
||
startActivity(intent);
|
||
finish();
|
||
}
|
||
|
||
private void initProjectDropdown() {
|
||
projectAdapter = new ArrayAdapter<Project>(this,
|
||
R.layout.custom_dropdown_item,
|
||
projectList) {
|
||
@NonNull
|
||
@Override
|
||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||
View view = super.getView(position, convertView, parent);
|
||
TextView textView = view.findViewById(android.R.id.text1);
|
||
Project project = getItem(position);
|
||
if (project != null) {
|
||
textView.setText(project.getProjectName() + " (" + project.getProjectId() + ")");
|
||
textView.setSingleLine(false);
|
||
textView.setMaxLines(3);
|
||
textView.setEllipsize(null);
|
||
}
|
||
return view;
|
||
}
|
||
|
||
@Override
|
||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||
return getView(position, convertView, parent);
|
||
}
|
||
|
||
@Override
|
||
public Filter getFilter() {
|
||
return new Filter() {
|
||
@Override
|
||
protected FilterResults performFiltering(CharSequence constraint) {
|
||
FilterResults results = new FilterResults();
|
||
List<Project> filteredList = new ArrayList<>();
|
||
|
||
if (constraint != null && constraint.length() > 0) {
|
||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||
DatabaseHelper dbHelper = new DatabaseHelper(LoginActivity.this);
|
||
filteredList = dbHelper.searchProjects(filterPattern);
|
||
} else {
|
||
filteredList.addAll(projectList);
|
||
}
|
||
|
||
results.values = filteredList;
|
||
results.count = filteredList.size();
|
||
return results;
|
||
}
|
||
|
||
@Override
|
||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||
clear();
|
||
if (results.values != null) {
|
||
addAll((List<Project>) results.values);
|
||
}
|
||
notifyDataSetChanged();
|
||
}
|
||
};
|
||
}
|
||
};
|
||
|
||
actvProject.setAdapter(projectAdapter);
|
||
actvProject.setThreshold(0);
|
||
actvProject.setDropDownBackgroundResource(android.R.color.white);
|
||
|
||
actvProject.setOnItemClickListener((parent, view, position, id) -> {
|
||
Object item = parent.getItemAtPosition(position);
|
||
if (item instanceof Project) {
|
||
Project selectedProject = (Project) item;
|
||
tvSelectedProjectName.setText("项目名称:" + selectedProject.getProjectName());
|
||
tvSelectedProjectId.setText("项目ID:" + selectedProject.getProjectId());
|
||
actvProject.setText(selectedProject.getProjectName() + " (" + selectedProject.getProjectId() + ")");
|
||
} else {
|
||
Log.e("TYPE_ERROR", "Expected Project but got: " + item.getClass());
|
||
Toast.makeText(this, "数据格式错误", Toast.LENGTH_SHORT).show();
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
private void loadProjects() {
|
||
|
||
|
||
try {
|
||
// 1. 验证接口定义
|
||
if (!validateApiDefinition()) {
|
||
return;
|
||
}
|
||
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder()
|
||
.connectTimeout(30, TimeUnit.SECONDS)
|
||
.readTimeout(30, TimeUnit.SECONDS)
|
||
.writeTimeout(30, TimeUnit.SECONDS);
|
||
okHttpClientBuilder.addInterceptor(new AuthInterceptor(authToken));
|
||
// 2. 初始化Retrofit
|
||
Retrofit retrofit = RetrofitClient.getClient(authToken);
|
||
|
||
// 3. 创建API服务
|
||
ProjectApi projectApi = retrofit.create(ProjectApi.class);
|
||
|
||
// 4. 执行网络请求
|
||
executeProjectRequest(projectApi);
|
||
|
||
} catch (Exception e) {
|
||
showErrorOnUI("初始化失败", getErrorMessage(e));
|
||
logErrorToFile(e);
|
||
}
|
||
}
|
||
|
||
private boolean validateApiDefinition() {
|
||
try {
|
||
Method method = ProjectApi.class.getMethod("getProjectList");
|
||
Type returnType = method.getGenericReturnType();
|
||
|
||
// 预期的完整类型签名
|
||
String expectedType = "retrofit2.Call<com.example.myapplication.model.ApiResponse<java.util.List<com.example.myapplication.model.Project>>>";
|
||
|
||
if (!returnType.toString().equals(expectedType)) {
|
||
String errorMsg = "接口定义错误!\n\n当前定义:\n" + returnType +
|
||
"\n\n应修改为:\n@GET(\"project/list\")\nCall<ApiResponse<List<Project>>> getProjectList();";
|
||
|
||
new Handler(Looper.getMainLooper()).post(() -> {
|
||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||
builder.setTitle("API接口定义不合法")
|
||
.setMessage(errorMsg)
|
||
.setPositiveButton("复制定义", (d, w) -> copyToClipboard(errorMsg))
|
||
.setNegativeButton("关闭", null)
|
||
.show();
|
||
});
|
||
return false;
|
||
}
|
||
return true;
|
||
} catch (Exception e) {
|
||
showErrorOnUI("接口验证失败", e.getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
private void executeProjectRequest(ProjectApi projectApi) {
|
||
Call<ApiResponse<List<Project>>> call = projectApi.getProjectList();
|
||
|
||
call.enqueue(new Callback<ApiResponse<List<Project>>>() {
|
||
@Override
|
||
public void onResponse(Call<ApiResponse<List<Project>>> call, Response<ApiResponse<List<Project>>> response) {
|
||
if (response.isSuccessful() && response.body() != null) {
|
||
handleSuccessResponse(response.body().getData());
|
||
} else {
|
||
handleApiError(response);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void onFailure(Call<ApiResponse<List<Project>>> call, Throwable t) {
|
||
handleNetworkError(t);
|
||
}
|
||
});
|
||
}
|
||
|
||
// === 错误处理方法 ===
|
||
private void handleSuccessResponse(List<Project> projects) {
|
||
runOnUiThread(() -> {
|
||
try {
|
||
dbHelper.saveProjects(projects);
|
||
projectList.clear();
|
||
projectList.addAll(projects);
|
||
projectAdapter.notifyDataSetChanged();
|
||
} catch (Exception e) {
|
||
showErrorOnUI("数据处理错误", e.getMessage());
|
||
}
|
||
});
|
||
}
|
||
|
||
private void handleApiError(Response<?> response) {
|
||
String errorMsg = "服务器响应错误: " + response.code();
|
||
try {
|
||
if (response.errorBody() != null) {
|
||
errorMsg += "\n" + response.errorBody().string();
|
||
}
|
||
} catch (IOException e) {
|
||
errorMsg += "\n无法读取错误详情";
|
||
}
|
||
|
||
showErrorOnUI("API请求失败", errorMsg);
|
||
loadCachedProjects();
|
||
}
|
||
|
||
private void handleNetworkError(Throwable t) {
|
||
String errorMsg = "网络错误: " + t.getMessage();
|
||
if (t instanceof SocketTimeoutException) {
|
||
errorMsg = "连接超时,请检查网络";
|
||
} else if (t instanceof UnknownHostException) {
|
||
errorMsg = "无法解析主机,请检查URL";
|
||
}
|
||
|
||
showErrorOnUI("网络连接失败", errorMsg);
|
||
loadCachedProjects();
|
||
}
|
||
|
||
// === 工具方法 ===
|
||
private void showErrorOnUI(String title, String message) {
|
||
runOnUiThread(() -> {
|
||
// 在界面底部显示错误面板
|
||
ViewGroup rootView = findViewById(android.R.id.content);
|
||
LinearLayout errorPanel = new LinearLayout(this);
|
||
errorPanel.setOrientation(LinearLayout.VERTICAL);
|
||
errorPanel.setBackgroundColor(0x33FF0000);
|
||
errorPanel.setPadding(32, 16, 32, 16);
|
||
|
||
TextView tvError = new TextView(this);
|
||
tvError.setTextColor(Color.RED);
|
||
tvError.setText(title);
|
||
|
||
TextView tvMsg = new TextView(this);
|
||
tvMsg.setText(message);
|
||
|
||
errorPanel.addView(tvError);
|
||
errorPanel.addView(tvMsg);
|
||
rootView.addView(errorPanel);
|
||
|
||
// 5秒后自动消失
|
||
new Handler().postDelayed(() -> rootView.removeView(errorPanel), 5000);
|
||
});
|
||
}
|
||
|
||
private void copyToClipboard(String text) {
|
||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||
clipboard.setPrimaryClip(ClipData.newPlainText("错误定义", text));
|
||
Toast.makeText(this, "定义已复制", Toast.LENGTH_SHORT).show();
|
||
}
|
||
|
||
private String getErrorMessage(Exception e) {
|
||
if (e instanceof IllegalArgumentException) {
|
||
return "参数错误: " + e.getMessage();
|
||
} else if (e instanceof NullPointerException) {
|
||
return "空指针异常: " + e.getMessage();
|
||
} else {
|
||
return "未知错误: " + e.getClass().getSimpleName();
|
||
}
|
||
}
|
||
|
||
private void logErrorToFile(Exception e) {
|
||
String log = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
|
||
"\nError: " + e.getClass().getName() +
|
||
"\nMessage: " + e.getMessage() +
|
||
"\nStack Trace:\n" + Log.getStackTraceString(e);
|
||
|
||
try {
|
||
File file = new File(getExternalFilesDir(null), "error_log.txt");
|
||
FileWriter writer = new FileWriter(file, true);
|
||
writer.append(log).append("\n\n");
|
||
writer.close();
|
||
} catch (IOException ioException) {
|
||
Log.e("FileLog", "无法写入错误日志", ioException);
|
||
}
|
||
}
|
||
|
||
private void loadCachedProjects() {
|
||
new Thread(() -> {
|
||
List<Project> cachedProjects = dbHelper.getAllProjects();
|
||
runOnUiThread(() -> {
|
||
projectList.clear();
|
||
projectList.addAll(cachedProjects);
|
||
projectAdapter.notifyDataSetChanged();
|
||
Toast.makeText(this, "已加载本地缓存数据", Toast.LENGTH_LONG).show();
|
||
});
|
||
}).start();
|
||
}
|
||
|
||
} |