登录注册

This commit is contained in:
2026-05-15 10:44:52 +08:00
parent 193955d983
commit da935a122f
24 changed files with 763 additions and 99 deletions

View File

@@ -0,0 +1,190 @@
package com.example.defenseapplication.utils
import android.annotation.SuppressLint
import android.util.Log
import com.example.defenseapplication.bean.BaseInfo
import com.google.gson.Gson
import com.google.gson.JsonParser
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.logging.HttpLoggingInterceptor
import java.nio.charset.Charset
/**
* 网络配置与拦截器
*/
object ApiConfig {
private const val TAG = "ApiConfig"
private val gson = Gson()
@Volatile
private var authToken: String = ""
fun updateAuthToken(token: String) {
authToken = token
}
fun hasAuthToken(): Boolean {
return authToken.isNotBlank()
}
/**
* 获取日志拦截器
* 完善:针对上传文件等二进制流时,降低日志级别为 HEADERS避免输出大量字节数据
*/
fun getLoggingInterceptor(): Interceptor {
val logger = HttpLoggingInterceptor.Logger { message ->
Log.d(TAG, message)
}
return Interceptor { chain ->
val request = chain.request()
if (/*!BuildConfig.DEBUG*/false) {
return@Interceptor chain.proceed(request)
}
// 1. 判断请求是否为二进制或多部分表单上传
val requestBody = request.body
val requestContentType = requestBody?.contentType()?.toString()?.lowercase() ?: ""
val isBinaryRequest = requestContentType.contains("multipart") ||
requestContentType.contains("octet-stream") ||
requestContentType.contains("image") ||
requestContentType.contains("video") ||
requestContentType.contains("audio")
// 2. 动态设置日志级别
val loggingInterceptor = HttpLoggingInterceptor(logger).apply {
level = if (isBinaryRequest) {
// 二进制上传只打印 Header防止 Body 字节码干扰
HttpLoggingInterceptor.Level.HEADERS
} else {
HttpLoggingInterceptor.Level.BODY
}
}
loggingInterceptor.intercept(chain)
}
}
/**
* 统一拦截器处理异常、Token失效、数据格式化
*/
@SuppressLint("CheckResult")
fun getUnifiedInterceptor() = Interceptor { chain ->
val request = chain.request()
// --- 1. 发起请求并捕获底层网络异常 (无网络、超时等) ---
val response = try {
chain.proceed(request)
} catch (e: Exception) {
Log.e(TAG, "Network Error: ", e)
return@Interceptor buildFakeErrorResponse(request, getErrorMessage(e))
}
// --- 2. 检查响应是否为大文件或二进制流 (直接放行,避免 OOM 和日志污染) ---
val responseBody = response.body ?: return@Interceptor response
val contentType = responseBody.contentType()?.toString()?.lowercase() ?: ""
if (contentType.contains("octet-stream") || contentType.contains("video") ||
contentType.contains("audio") || contentType.contains("image") ||
responseBody.contentLength() > 1024 * 1024
) {
return@Interceptor response
}
// --- 3. 读取 JSON 数据进行统一处理 ---
try {
val source = responseBody.source()
source.request(Long.MAX_VALUE)
val responseString = source.buffer.clone().readString(Charset.defaultCharset())
if (!responseString.trim().startsWith("{")) {
if (response.code == 500) {
return@Interceptor buildFakeErrorResponse(request, "服务器内部错误")
}
return@Interceptor response
}
val jsonElement = JsonParser().parse(responseString)
val jsonObject = jsonElement.asJsonObject
val httpCode = response.code
val businessCode = if (jsonObject.has("code")) jsonObject.get("code").asInt else 0
if (httpCode == 401 || httpCode == 424 || businessCode == 401) {
clearUserLoginInfo()
val msg = if (jsonObject.has("msg")) jsonObject.get("msg").asString else "登录已过期,请重新登录"
return@Interceptor buildFakeErrorResponse(request, msg, code = if (businessCode != 0) businessCode else httpCode)
}
if (httpCode == 500) {
return@Interceptor buildFakeErrorResponse(request, "服务器内部错误(500)")
}
return@Interceptor response
} catch (e: Exception) {
Log.e(TAG, "JSON Parse Error: ", e)
return@Interceptor response
}
}
/**
* 构建一个伪装成 HTTP 200 的统一错误响应体
*/
private fun buildFakeErrorResponse(request: Request, msg: String, code: Int = 1): Response {
val baseInfo = BaseInfo(
message = msg,
code = code,
data = null,
)
val json = gson.toJson(baseInfo)
val responseBody = json.toResponseBody("application/json; charset=utf-8".toMediaTypeOrNull())
return Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(200)
.message("OK")
.body(responseBody)
.build()
}
private fun getErrorMessage(e: Exception): String {
return "网络请求失败,请检查网络设置"
}
// 统一请求头拦截器
fun createHeadersInterceptor() = Interceptor { chain ->
val requestBuilder = chain.request().newBuilder()
val token = authToken
if (token.isNotBlank()) {
requestBuilder.header("Authorization", buildAuthorizationValue(token))
}
requestBuilder
// .header("Client-Toc", "Y")
// .header("Accept-Language", LanguageHelper.getLanguageServiceStr(BrightApp.getInstance()))
// .header("model", "Android")
// .header("X-App-Version", AppUtils.getAppVersionName())
chain.proceed(requestBuilder.build())
}
private fun buildAuthorizationValue(token: String): String {
return if (token.startsWith("Bearer ")) token else "Bearer $token"
}
private fun clearUserLoginInfo() {
try {
authToken = ""
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@@ -1,6 +1,8 @@
package com.example.defenseapplication.utils
import bean.ForgotRequest
import com.example.defenseapplication.bean.DeviceBean
import com.example.defenseapplication.bean.FamilyBean
import com.example.defenseapplication.bean.GetCodeBean
import bean.LoginBean
import bean.LoginRequest
@@ -34,5 +36,17 @@ interface ApiService {
*/
@PUT("/app/forgot")
suspend fun forgotPassword(@Body forgotRequest: ForgotRequest): GetCodeBean<Any>
/**
* 获取我所在家庭列表
*/
@GET("/app/selectFamily")
suspend fun getFamilyList(): GetCodeBean<List<FamilyBean>>
/**
* 切换家庭
*/
//@POST("/app/switchFamily")
//suspend fun switchFamily(@Body request: com.example.defenseapplication.bean.SwitchFamilyRequest): GetCodeBean<Any>
}

View File

@@ -1,8 +0,0 @@
package com.example.defenseapplication.utils
data class Post(
val userId: Int,
val id: Int,
val title: String,
val body: String
)

View File

@@ -1,43 +0,0 @@
package com.example.defenseapplication.utils
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
//网络工具类
object HttpUtils {
val baseURL = "https://stem-boozy-margarine.ngrok-free.dev"
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.writeTimeout(3, TimeUnit.MINUTES)
.readTimeout(3, TimeUnit.MINUTES)
.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val s = UserinfoUtil.getUserInfo()
val aa = chain.request().newBuilder()
.addHeader("token", s?.token ?: "")
.addHeader("userId", "${s?.userId ?: 0}")
.build()
return chain.proceed(aa)
}
})
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.baseUrl(baseURL)
.build()
val api = retrofit.create(ApiService::class.java)
}

View File

@@ -0,0 +1,18 @@
package com.smart.data.repository
import com.example.defenseapplication.utils.ApiService
import com.example.defenseapplication.utils.RetrofitNetwork
class MainRepository(val apiService: ApiService = RetrofitNetwork.getNetworkService()) {
companion object {
fun getInstance(): Array<Any> {
return arrayOf(MainRepository(RetrofitNetwork.getNetworkService()))
}
fun getInstance(vararg any: Any): Array<Any> {
return arrayOf(MainRepository(RetrofitNetwork.getNetworkService()), *any)
}
}
}

View File

@@ -0,0 +1,75 @@
package com.example.defenseapplication.utils
import com.google.gson.Gson
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
/**
* 统一网络请求封装
*/
object RetrofitNetwork {
private const val DEFAULT_TIMEOUT = 30L
const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
private var retrofit: Retrofit? = null
private val gson = Gson()
/**
* 动态修改 BaseUrl
*/
fun setBaseUrl(baseUrl: String) {
retrofit = createRetrofit(baseUrl)
}
fun getNetworkService(): ApiService = getService(ApiService::class.java)
fun <T> getService(service: Class<T>, baseUrl: String? = null): T {
if (retrofit == null || baseUrl != null) {
retrofit = createRetrofit(baseUrl ?: BASE_URL)
}
return retrofit!!.create(service)
}
/**
* 专门用于下载的 Service (无日志,防止内存溢出)
*/
fun createRetrofitDownload(): ApiService {
return Retrofit.Builder()
.baseUrl(normalizeBaseUrl(BASE_URL))
.client(createOkHttpClient(enableLogging = false))
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
.create(ApiService::class.java)
}
private fun createRetrofit(baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(normalizeBaseUrl(baseUrl))
.client(createOkHttpClient(enableLogging = true))
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
private fun normalizeBaseUrl(baseUrl: String): String {
val trimmed = baseUrl.trim()
return if (trimmed.endsWith("/")) trimmed else "$trimmed/"
}
private fun createOkHttpClient(enableLogging: Boolean): OkHttpClient {
val builder = OkHttpClient.Builder()
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(ApiConfig.createHeadersInterceptor())
.addInterceptor(ApiConfig.getUnifiedInterceptor()) // 使用 ApiConfig 中的统一拦截器
if (enableLogging) {
builder.addInterceptor(ApiConfig.getLoggingInterceptor()) // 使用 ApiConfig 中的日志拦截器
}
return builder.build()
}
}