feat: 添加双 token 机制,进行无感更新token
This commit is contained in:
parent
4e0acd5335
commit
632cd5c4e6
@ -154,7 +154,7 @@
|
||||
},
|
||||
{
|
||||
"id": "5:2885532406154205395",
|
||||
"lastPropertyId": "7:7996607163318458427",
|
||||
"lastPropertyId": "9:1671273767790122969",
|
||||
"name": "UserInfo",
|
||||
"properties": [
|
||||
{
|
||||
@ -194,6 +194,16 @@
|
||||
"id": "7:7996607163318458427",
|
||||
"name": "status",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "8:6299664727899448104",
|
||||
"name": "accessToken",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "9:1671273767790122969",
|
||||
"name": "refreshToken",
|
||||
"type": 9
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
|
@ -74,7 +74,7 @@
|
||||
},
|
||||
{
|
||||
"id": "4:6179749773128044271",
|
||||
"lastPropertyId": "14:5371512009949707960",
|
||||
"lastPropertyId": "15:191614052410347083",
|
||||
"name": "Contact",
|
||||
"properties": [
|
||||
{
|
||||
@ -145,8 +145,8 @@
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "14:5371512009949707960",
|
||||
"name": "disturb",
|
||||
"id": "15:191614052410347083",
|
||||
"name": "doNotDisturb",
|
||||
"type": 1
|
||||
}
|
||||
],
|
||||
@ -298,7 +298,8 @@
|
||||
2020630799900991467,
|
||||
385998119105891942,
|
||||
8166842332862045141,
|
||||
605708604168234493
|
||||
605708604168234493,
|
||||
5371512009949707960
|
||||
],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
|
1
app/src/main/assets/loading2.json
Normal file
1
app/src/main/assets/loading2.json
Normal file
@ -0,0 +1 @@
|
||||
{"ddd":0,"fr":100,"h":200,"ip":0,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"loader_21","ln":"WlKberZN","sr":1,"ks":{"a":{"ix":2,"a":0,"k":[0,0]},"p":{"ix":2,"a":0,"k":[0,0]},"s":{"ix":2,"a":0,"k":[100,100]},"r":{"ix":2,"a":0,"k":0},"sk":{"ix":2,"a":0,"k":0},"sa":{"ix":2,"a":0,"k":0},"o":{"ix":2,"a":0,"k":100}},"ao":0,"ip":0,"op":101,"st":0,"bm":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,-19.75],[0,20]],"i":[[0,0],[0,0]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[0.45098039215686275,0.4235294117647059,0.9294117647058824],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":16,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[40,40.125],"ix":2},"a":{"a":0,"k":[0,0.125],"ix":2},"s":{"a":0,"k":[100,100],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":43,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":103,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"gr","nm":"Shape 1","it":[{"ty":"sh","d":1,"ks":{"a":0,"k":{"c":false,"v":[[0,-19.75],[0,20]],"i":[[0,0],[0,0]],"o":[[0,0],[0,0]]}}},{"ty":"tm","s":{"a":1,"k":[{"t":0,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[49],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[0],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"e":{"a":1,"k":[{"t":0,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[50],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":0,"ix":2},"m":1},{"ty":"st","c":{"a":0,"k":[0.9607843137254902,0.3607843137254902,0.47843137254901963],"ix":2},"o":{"a":0,"k":100,"ix":2},"w":{"a":0,"k":16,"ix":2},"lc":2,"lj":2,"ml":4},{"ty":"tr","p":{"a":0,"k":[40,40.125],"ix":2},"a":{"a":0,"k":[0,0.125],"ix":2},"s":{"a":1,"k":[{"t":0,"s":[100,100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":33,"s":[94,94],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[94,94],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":83,"s":[100,100],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"r":{"a":1,"k":[{"t":0,"s":[90],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":43,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":50,"s":[180],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}},{"t":103,"s":[270],"i":{"x":[0.75],"y":[0.75]},"o":{"x":[0.25],"y":[0.25]}}],"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]},{"ty":"tr","p":{"a":0,"k":[100.0000038146973,99.99999618530273],"ix":2},"a":{"a":0,"k":[39.99999904632568,40.12500286102295],"ix":2},"s":{"a":0,"k":[150,150],"ix":2},"r":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":100,"ix":2},"sk":{"a":0,"k":0,"ix":2},"sa":{"a":0,"k":0,"ix":2}}]}]}],"markers":[],"meta":{"g":"jsDesign"},"nm":"Comp 1","op":100,"v":"5.7.5","w":200}
|
@ -21,5 +21,7 @@ data class UserInfo(
|
||||
var avatarUrl: String,
|
||||
var signature: String,
|
||||
var telephone: String,
|
||||
var status: String? = null
|
||||
var status: String? = null,
|
||||
var accessToken: String? = null,
|
||||
var refreshToken: String? = null
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ import io.objectbox.Box
|
||||
*/
|
||||
class UserAuthRepository {
|
||||
|
||||
private val userApiService = RetrofitClient.userApiService
|
||||
private val authApiService = RetrofitClient.authApiService
|
||||
|
||||
private val userInfoBox: Box<UserInfo> by lazy { getBoxStore().boxFor(UserInfo::class.java) }
|
||||
|
||||
@ -26,7 +26,7 @@ class UserAuthRepository {
|
||||
// 用户注册
|
||||
suspend fun register(registerRequest: RegisterRequest): Result<Register?> {
|
||||
return apiCall(
|
||||
apiCall = { userApiService.register(registerRequest) },
|
||||
apiCall = { authApiService.register(registerRequest) },
|
||||
errorMessage = "注册成功,但未返回用户数据"
|
||||
).onSuccess { register ->
|
||||
register?.let {
|
||||
@ -38,7 +38,7 @@ class UserAuthRepository {
|
||||
// 登录方法
|
||||
suspend fun loginByUsername(username: String, password: String): Result<UserInfo?> {
|
||||
return apiCall(
|
||||
apiCall = { userApiService.loginByUsername(username, password) },
|
||||
apiCall = { authApiService.loginByUsername(username, password) },
|
||||
errorMessage = "登录成功,但未返回用户数据"
|
||||
).onSuccess { userInfo ->
|
||||
userInfo?.let {
|
||||
@ -50,7 +50,7 @@ class UserAuthRepository {
|
||||
|
||||
suspend fun loginByTelephone(telephone: String, password: String): Result<UserInfo?> {
|
||||
return apiCall(
|
||||
apiCall = { userApiService.loginByTelephone(telephone, password) },
|
||||
apiCall = { authApiService.loginByTelephone(telephone, password) },
|
||||
errorMessage = "登录成功,但未返回用户数据"
|
||||
).onSuccess { userInfo ->
|
||||
userInfo?.let {
|
||||
|
96
app/src/main/kotlin/com/kaixed/kchat/manager/AppManager.kt
Normal file
96
app/src/main/kotlin/com/kaixed/kchat/manager/AppManager.kt
Normal file
@ -0,0 +1,96 @@
|
||||
package com.kaixed.kchat.manager
|
||||
|
||||
/**
|
||||
* @Author: kaixed
|
||||
* @Date: 2025/1/15 21:29
|
||||
*/
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import java.util.Stack
|
||||
|
||||
class AppManager private constructor() {
|
||||
|
||||
private val activityStack: Stack<Activity> = Stack()
|
||||
|
||||
companion object {
|
||||
val instance: AppManager by lazy { AppManager() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 Activity 到堆栈
|
||||
*/
|
||||
fun addActivity(activity: Activity) {
|
||||
activityStack.add(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Activity(堆栈中最后一个压入的)
|
||||
*/
|
||||
fun currentActivity(): Activity? {
|
||||
return if (activityStack.isNotEmpty()) {
|
||||
activityStack.lastElement()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束当前 Activity(堆栈中最后一个压入的)
|
||||
*/
|
||||
fun finishActivity() {
|
||||
currentActivity()?.let { finishActivity(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束指定的 Activity
|
||||
*/
|
||||
fun finishActivity(activity: Activity) {
|
||||
activityStack.remove(activity)
|
||||
activity.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束指定类名的 Activity
|
||||
*/
|
||||
fun finishActivity(cls: Class<*>) {
|
||||
val iterator = activityStack.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val activity = iterator.next()
|
||||
if (activity.javaClass == cls) {
|
||||
iterator.remove()
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束所有 Activity
|
||||
*/
|
||||
fun finishAllActivities() {
|
||||
while (activityStack.isNotEmpty()) {
|
||||
val activity = activityStack.pop()
|
||||
activity.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束所有 Activity
|
||||
*/
|
||||
fun finishAllActivities(activity: Activity) {
|
||||
activity.finishAffinity()
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出应用程序
|
||||
*/
|
||||
fun appExit(context: Context) {
|
||||
try {
|
||||
finishAllActivities()
|
||||
val activityMgr =
|
||||
context.getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
|
||||
activityMgr.restartPackage(context.packageName) // 注意:此方法已被废弃,实际应用中可能需要调整退出逻辑
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,10 @@ package com.kaixed.kchat.network
|
||||
object NetworkInterface {
|
||||
|
||||
// private const val URL = "app.kaixed.com/kchat"
|
||||
// private const val URL = "192.168.45.209:6196/kchat"
|
||||
private const val URL = "49.233.105.103:6000"
|
||||
const val SERVER_URL = "https://$URL"
|
||||
private const val URL = "192.168.31.18:6196"
|
||||
// private const val URL = "49.233.105.103:6000"
|
||||
// const val SERVER_URL = "https://$URL"
|
||||
const val SERVER_URL = "http://$URL"
|
||||
const val WEBSOCKET_SERVER_URL = "wss://$URL"
|
||||
const val WEBSOCKET = "/websocket/single/"
|
||||
const val USER_INFO = "/users/info/"
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.kaixed.kchat.network
|
||||
|
||||
import com.kaixed.kchat.network.interceptor.TokenRefreshInterceptor
|
||||
import com.kaixed.kchat.network.service.AuthApiService
|
||||
import com.kaixed.kchat.network.service.ContactService
|
||||
import com.kaixed.kchat.network.service.FileApiService
|
||||
import com.kaixed.kchat.network.service.UserApiService
|
||||
@ -7,6 +9,7 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* @Author: kaixed
|
||||
@ -14,28 +17,50 @@ import retrofit2.converter.gson.GsonConverterFactory
|
||||
*/
|
||||
object RetrofitClient {
|
||||
|
||||
// private const val BASE_URL = "https://app.kaixed.com/kchat/"
|
||||
// private const val BASE_URL = "http://192.168.45.209:6196/"
|
||||
private const val BASE_URL = "http://49.233.105.103:6000/"
|
||||
private const val BASE_URL = "${NetworkInterface.SERVER_URL}/"
|
||||
|
||||
// 添加日志拦截器
|
||||
private val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
level = HttpLoggingInterceptor.Level.BODY
|
||||
}
|
||||
|
||||
private val client = OkHttpClient.Builder()
|
||||
// 创建一个不包含 Token 拦截器的 OkHttpClient,专门用于授权 API
|
||||
private val authClient = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
// .addInterceptor(SignInterceptor()) // 添加签名拦截器
|
||||
.pingInterval(15, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
// 创建单独的 Retrofit 实例(专门用于授权 API)
|
||||
private val authRetrofit: Retrofit by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(authClient) // 使用不包含 Token 拦截器的 OkHttpClient
|
||||
.build()
|
||||
}
|
||||
|
||||
// 授权 API 服务实例,使用不包含 Token 拦截器的 Retrofit 实例
|
||||
val authApiService: AuthApiService by lazy {
|
||||
authRetrofit.create(AuthApiService::class.java)
|
||||
}
|
||||
|
||||
// 创建一个 OkHttpClient,包含 Token 拦截器
|
||||
private val client = OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.pingInterval(15, TimeUnit.SECONDS)
|
||||
.addInterceptor(TokenRefreshInterceptor()) // Token 拦截器
|
||||
.build()
|
||||
|
||||
// 创建 Retrofit 实例(用于普通 API 调用)
|
||||
private val retrofit: Retrofit by lazy {
|
||||
Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(client)
|
||||
.client(client) // 使用包含 Token 拦截器的 OkHttpClient
|
||||
.build()
|
||||
}
|
||||
|
||||
// API 服务实例
|
||||
val userApiService: UserApiService by lazy {
|
||||
retrofit.create(UserApiService::class.java)
|
||||
}
|
||||
|
@ -0,0 +1,127 @@
|
||||
package com.kaixed.kchat.network.interceptor
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kaixed.kchat.network.ApiCall
|
||||
import com.kaixed.kchat.network.RetrofitClient
|
||||
import com.kaixed.kchat.utils.CacheUtils
|
||||
import com.kaixed.kchat.utils.ConstantsUtils.getUsername
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.json.JSONObject
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class TokenRefreshInterceptor() : Interceptor {
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
private val authApiService by lazy { RetrofitClient.authApiService }
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
// 获取当前的 accessToken
|
||||
var accessToken = CacheUtils.getAccessToken()
|
||||
var refreshToken = CacheUtils.getRefreshToken()
|
||||
val expiration = parseTokenManually(accessToken)
|
||||
val refreshTokenExpiration = parseTokenManually(refreshToken)
|
||||
val now = System.currentTimeMillis() / 1000
|
||||
Log.d("haha", now.toString())
|
||||
|
||||
// 如果 Token 距离过期时间小于 5 分钟,刷新 Token
|
||||
if (expiration - now < 300) {
|
||||
lock.withLock {
|
||||
// Double-check 防止并发刷新
|
||||
val latestAccessToken = CacheUtils.getAccessToken()
|
||||
if (latestAccessToken == accessToken) {
|
||||
// 调用刷新 Token 方法
|
||||
runBlocking {
|
||||
refreshAccessToken() // 刷新 Refresh Token
|
||||
}
|
||||
} else {
|
||||
accessToken = latestAccessToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断 Refresh Token 是否即将过期(提前一天刷新)
|
||||
if (refreshTokenExpiration - now < 24 * 60 * 60) { // 剩余小于24小时
|
||||
lock.withLock {
|
||||
val latestRefreshToken = CacheUtils.getRefreshToken()
|
||||
if (latestRefreshToken == refreshToken) {
|
||||
runBlocking {
|
||||
refreshRefreshToken() // 刷新 Refresh Token
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将最新的 Token 添加到请求头
|
||||
val newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", "Bearer $accessToken")
|
||||
.build()
|
||||
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
private suspend fun refreshAccessToken() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
ApiCall.apiCall(
|
||||
apiCall = { authApiService.auth("Bearer ${CacheUtils.getRefreshToken()}", getUsername()) },
|
||||
errorMessage = "刷新Token失败"
|
||||
).onSuccess {
|
||||
it?.let {
|
||||
CacheUtils.setAccessToken(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshRefreshToken() {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
ApiCall.apiCall(
|
||||
apiCall = {
|
||||
authApiService.refresh(
|
||||
CacheUtils.getRefreshToken(),
|
||||
getUsername()
|
||||
)
|
||||
},
|
||||
errorMessage = "刷新Token失败"
|
||||
).onSuccess {
|
||||
it?.let {
|
||||
CacheUtils.setRefreshToken(it)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseTokenManually(token: String): Long {
|
||||
var expiration = 0L
|
||||
try {
|
||||
// JWT 的有效载荷部分是第二部分
|
||||
val payload = token.split(".")[1]
|
||||
|
||||
// 解码Base64编码的字符串
|
||||
val decodedBytes = Base64.decode(payload, Base64.URL_SAFE or Base64.NO_WRAP)
|
||||
val decodedString = String(decodedBytes)
|
||||
|
||||
// 将解码后的字符串转为JSONObject
|
||||
val jsonObject = JSONObject(decodedString)
|
||||
|
||||
expiration = jsonObject.optLong("exp") // 获取过期时间
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return expiration
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.kaixed.kchat.network.service
|
||||
|
||||
import com.kaixed.kchat.data.local.entity.UserInfo
|
||||
import com.kaixed.kchat.data.model.request.RegisterRequest
|
||||
import com.kaixed.kchat.data.model.response.register.Register
|
||||
import com.kaixed.kchat.network.ApiResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* @Author: kaixed
|
||||
* @Date: 2025/1/22 18:35
|
||||
*/
|
||||
interface AuthApiService {
|
||||
|
||||
@POST("users/access-token")
|
||||
suspend fun auth(
|
||||
@Header("Authorization") token: String,
|
||||
@Query("username") username: String
|
||||
): ApiResponse<String>
|
||||
|
||||
@POST("users/refresh-token")
|
||||
suspend fun refresh(
|
||||
@Header("Authorization") token: String,
|
||||
@Query("username") username: String
|
||||
): ApiResponse<String>
|
||||
|
||||
// 登录接口(根据用户名登录)
|
||||
@FormUrlEncoded
|
||||
@POST("users/login/username")
|
||||
suspend fun loginByUsername(
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String
|
||||
): ApiResponse<UserInfo?>
|
||||
|
||||
// 登录接口(根据电话登录)
|
||||
@FormUrlEncoded
|
||||
@POST("users/login/telephone")
|
||||
suspend fun loginByTelephone(
|
||||
@Field("telephone") telephone: String,
|
||||
@Field("password") password: String
|
||||
): ApiResponse<UserInfo>
|
||||
|
||||
// 注册接口
|
||||
@POST("users/register")
|
||||
suspend fun register(
|
||||
@Body registerRequest: RegisterRequest
|
||||
): ApiResponse<Register>
|
||||
}
|
@ -24,28 +24,6 @@ import retrofit2.http.Path
|
||||
*/
|
||||
interface UserApiService {
|
||||
|
||||
// 注册接口
|
||||
@POST("users/register")
|
||||
suspend fun register(
|
||||
@Body registerRequest: RegisterRequest
|
||||
): ApiResponse<Register>
|
||||
|
||||
// 登录接口(根据用户名登录)
|
||||
@FormUrlEncoded
|
||||
@POST("users/login/username")
|
||||
suspend fun loginByUsername(
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String
|
||||
): ApiResponse<UserInfo?>
|
||||
|
||||
// 登录接口(根据电话登录)
|
||||
@FormUrlEncoded
|
||||
@POST("users/login/telephone")
|
||||
suspend fun loginByTelephone(
|
||||
@Field("telephone") telephone: String,
|
||||
@Field("password") password: String
|
||||
): ApiResponse<UserInfo>
|
||||
|
||||
// 获取用户列表
|
||||
@GET("userList/{username}")
|
||||
suspend fun getUserListByNickname(
|
||||
|
@ -15,9 +15,10 @@ import com.kaixed.kchat.R
|
||||
import com.kaixed.kchat.data.LocalDatabase
|
||||
import com.kaixed.kchat.databinding.ActivityLoginBinding
|
||||
import com.kaixed.kchat.ui.base.BaseActivity
|
||||
import com.kaixed.kchat.utils.Constants.ACCESS_TOKEN
|
||||
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT
|
||||
import com.kaixed.kchat.utils.Constants.KEYBOARD_HEIGHT_RATIO
|
||||
import com.kaixed.kchat.utils.Constants.MMKV_COMMON_DATA
|
||||
import com.kaixed.kchat.utils.Constants.REFRESH_TOKEN
|
||||
import com.kaixed.kchat.utils.DrawableUtil.createDrawable
|
||||
import com.kaixed.kchat.utils.ScreenUtils.dp2px
|
||||
import com.kaixed.kchat.viewmodel.UserViewModel
|
||||
@ -80,6 +81,8 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
|
||||
loginResult.onSuccess {
|
||||
it?.let {
|
||||
LocalDatabase.saveUserInfo(it)
|
||||
MMKV.defaultMMKV().putString(ACCESS_TOKEN, it.accessToken)
|
||||
MMKV.defaultMMKV().putString(REFRESH_TOKEN, it.refreshToken)
|
||||
}
|
||||
navigateToMain()
|
||||
}
|
||||
|
@ -90,13 +90,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
|
||||
initViewPager()
|
||||
setListener()
|
||||
preLoad()
|
||||
}
|
||||
|
||||
private fun preLoad() {
|
||||
Glide.with(this)
|
||||
.load(getAvatarUrl())
|
||||
.preload()
|
||||
}
|
||||
|
||||
// 待优化成自定义界面,目前使用的华为sdk的默认界面
|
||||
@ -182,7 +175,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
delay(200)
|
||||
loadingDialogFragment.showLoading(supportFragmentManager)
|
||||
|
||||
contactViewModel.loadFriendList(getUsername())
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.kaixed.kchat.ui.base
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.kaixed.kchat.manager.AppManager
|
||||
|
||||
/**
|
||||
* @Author: kaixed
|
||||
@ -21,6 +21,12 @@ abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
|
||||
binding = inflateBinding()
|
||||
setContentView(binding.root)
|
||||
initData()
|
||||
AppManager.instance.addActivity(this)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
AppManager.instance.finishActivity(this)
|
||||
}
|
||||
|
||||
abstract fun initData()
|
||||
|
@ -56,6 +56,7 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
|
||||
context = requireContext()
|
||||
|
||||
loadData()
|
||||
setObservation()
|
||||
|
||||
loadFriendRequest()
|
||||
|
||||
@ -63,17 +64,7 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
|
||||
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver(mReceiver, filter)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
loadFriendRequest()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
private fun loadFriendRequest() {
|
||||
private fun setObservation() {
|
||||
contactViewModel.contactRequestListResult.observe(viewLifecycleOwner) { result ->
|
||||
result.onSuccess {
|
||||
val items = result.getOrNull()?.toMutableList() ?: emptyList()
|
||||
@ -88,12 +79,25 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
|
||||
}
|
||||
result.onFailure { }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
loadFriendRequest()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
private fun loadFriendRequest() {
|
||||
contactViewModel.getContactRequestList(getUsername())
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
loading = true
|
||||
binding.tvLoading.visibility = View.VISIBLE
|
||||
binding.includeLoading.main.visibility = View.VISIBLE
|
||||
binding.recycleFriendList.visibility = View.INVISIBLE
|
||||
|
||||
val contacts = contactViewModel.loadFriendListInDb()
|
||||
@ -102,7 +106,7 @@ class ContactFragment : BaseFragment<FragmentContactBinding>() {
|
||||
addAll(contacts)
|
||||
}
|
||||
friendAdapter.updateData(allItems)
|
||||
binding.tvLoading.visibility = View.GONE
|
||||
binding.includeLoading.main.visibility = View.GONE
|
||||
binding.recycleFriendList.visibility = View.VISIBLE
|
||||
loading = false
|
||||
setupRecyclerView()
|
||||
|
@ -28,7 +28,6 @@ class DiscoveryFragment : BaseFragment<FragmentDiscoveryBinding>() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setListener()
|
||||
}
|
||||
|
||||
|
27
app/src/main/kotlin/com/kaixed/kchat/utils/CacheUtils.kt
Normal file
27
app/src/main/kotlin/com/kaixed/kchat/utils/CacheUtils.kt
Normal file
@ -0,0 +1,27 @@
|
||||
package com.kaixed.kchat.utils
|
||||
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
/**
|
||||
* @Author: kaixed
|
||||
* @Date: 2025/1/22 18:06
|
||||
*/
|
||||
object CacheUtils {
|
||||
fun getAccessToken(): String {
|
||||
val accessToken = MMKV.defaultMMKV().getString(Constants.ACCESS_TOKEN, "")
|
||||
return accessToken ?: ""
|
||||
}
|
||||
|
||||
fun getRefreshToken(): String {
|
||||
val refreshToken = MMKV.defaultMMKV().getString(Constants.REFRESH_TOKEN, "")
|
||||
return refreshToken ?: ""
|
||||
}
|
||||
|
||||
fun setAccessToken(accessToken: String) {
|
||||
MMKV.defaultMMKV().putString(Constants.ACCESS_TOKEN, accessToken)
|
||||
}
|
||||
|
||||
fun setRefreshToken(refreshToken: String) {
|
||||
MMKV.defaultMMKV().putString(Constants.REFRESH_TOKEN, refreshToken)
|
||||
}
|
||||
}
|
@ -22,6 +22,9 @@ object Constants {
|
||||
const val SCREEN_HEIGHT = "screenHeight"
|
||||
const val SCREEN_WIDTH = "screenWidth"
|
||||
|
||||
const val ACCESS_TOKEN = "accessToken"
|
||||
const val REFRESH_TOKEN = "refreshToken"
|
||||
|
||||
const val KEYBOARD_HEIGHT_RATIO = 0.15F
|
||||
const val KEYBOARD_DEFAULT_HEIGHT = 200
|
||||
const val STATUS_BAR_DEFAULT_HEIGHT: Int = 50
|
||||
|
@ -26,13 +26,8 @@
|
||||
android:background="@color/white"
|
||||
android:overScrollMode="never" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_loading"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/title"
|
||||
android:gravity="center"
|
||||
android:text="正在加载中..."
|
||||
android:textColor="@color/black" />
|
||||
<include
|
||||
android:id="@+id/include_loading"
|
||||
layout="@layout/layout_loading" />
|
||||
|
||||
</RelativeLayout>
|
32
app/src/main/res/layout/layout_loading.xml
Normal file
32
app/src/main/res/layout/layout_loading.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:id="@+id/lottie"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.4"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_fileName="loading2.json"
|
||||
app:lottie_loop="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="正在加载中..."
|
||||
android:textColor="@color/black"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/lottie"
|
||||
app:layout_constraintStart_toEndOf="@id/lottie"
|
||||
app:layout_constraintTop_toTopOf="@id/lottie" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -2,5 +2,6 @@
|
||||
<network-security-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">49.233.105.103</domain>
|
||||
<domain includeSubdomains="true">192.168.31.18</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
@ -32,10 +32,12 @@ window = "1.3.0"
|
||||
kotlin = "1.9.23"
|
||||
coreKtx = "1.13.1"
|
||||
objectbox = "4.0.2"
|
||||
workRuntimeKtx = "2.10.0"
|
||||
|
||||
[libraries]
|
||||
agcp = { module = "com.huawei.agconnect:agcp", version.ref = "agcp" }
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||
compress = { module = "io.github.lucksiege:compress", version.ref = "pictureselector" }
|
||||
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }
|
||||
|
Loading…
Reference in New Issue
Block a user