登录注册
This commit is contained in:
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package com.example.defenseapplication.utils
|
||||
|
||||
data class Post(
|
||||
val userId: Int,
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val body: String
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user