| New file |
| | |
| | | {"agcgw":{"url":"connect-drcn.dbankcloud.cn","backurl":"connect-drcn.hispace.hicloud.com","websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn","websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com"},"agcgw_all":{"SG":"connect-dra.dbankcloud.cn","SG_back":"connect-dra.hispace.hicloud.com","CN":"connect-drcn.dbankcloud.cn","CN_back":"connect-drcn.hispace.hicloud.com","RU":"connect-drru.hispace.dbankcloud.ru","RU_back":"connect-drru.hispace.dbankcloud.cn","DE":"connect-dre.dbankcloud.cn","DE_back":"connect-dre.hispace.hicloud.com"},"websocketgw_all":{"SG":"connect-ws-dra.hispace.dbankcloud.cn","SG_back":"connect-ws-dra.hispace.dbankcloud.com","CN":"connect-ws-drcn.hispace.dbankcloud.cn","CN_back":"connect-ws-drcn.hispace.dbankcloud.com","RU":"connect-ws-drru.hispace.dbankcloud.ru","RU_back":"connect-ws-drru.hispace.dbankcloud.cn","DE":"connect-ws-dre.hispace.dbankcloud.cn","DE_back":"connect-ws-dre.hispace.dbankcloud.com"},"client":{"cp_id":"10086000932593129","product_id":"101653523864090831","client_id":"1947933504989878464","client_secret":"2FF3E11D2FBEA319A40BFFB113076FF808FDC879DE294A75892DE4251C330D06","project_id":"101653523864090831","app_id":"117680933","api_key":"DgEDANNbKTHvOmqNjimOJ+fartLex/1/hdD2nfgFIW4+CBa+xdVg5bSwlYRsMgMQvvuzE+sBG6gUiY21hbnwB1RJ/MvNL/lcqtoyUA==","package_name":"com.xw.yunwei"},"oauth_client":{"client_id":"117680933","client_type":1},"app_info":{"app_id":"117680933","package_name":"com.xw.yunwei"},"service":{"analytics":{"collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn","collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn","collector_url_ru":"datacollector-drru.dt.dbankcloud.ru,datacollector-drru.dt.hicloud.com","collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn","resource_id":"p1","channel_id":""},"ml":{"mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn"},"cloudstorage":{"storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg":"https://ops-dra.agcstorage.link","storage_url_de":"https://ops-dre.agcstorage.link","storage_url_cn":"https://agc-storage-drcn.platform.dbankcloud.cn","storage_url_ru_back":"https://agc-storage-drru.cloud.huawei.ru","storage_url_sg_back":"https://agc-storage-dra.cloud.huawei.asia","storage_url_de_back":"https://agc-storage-dre.cloud.huawei.eu","storage_url_cn_back":"https://agc-storage-drcn.cloud.huawei.com.cn"},"search":{"url":"https://search-drcn.cloud.huawei.com"},"edukit":{"edu_url":"edukit.cloud.huawei.com.cn","dh_url":"edukit.cloud.huawei.com.cn"}},"region":"CN","configuration_version":"3.0","appInfos":[{"package_name":"com.xw.yunwei","client":{"app_id":"117680933"},"oauth_client":{"client_id":"117680933","client_type":1},"app_info":{"app_id":"117680933","package_name":"com.xw.yunwei"}}]} |
| | |
| | | apply(plugin = "com.huawei.agconnect") |
| | | plugins { |
| | | alias(libs.plugins.android.application) |
| | | alias(libs.plugins.kotlin.android) |
| | |
| | | |
| | | flavorDimensions.add("api") |
| | | productFlavors { |
| | | create("development") { // 开发环境 |
| | | create("local") { // 本地开发环境 |
| | | dimension = "api" |
| | | applicationId = APPLICATION_ID |
| | | |
| | | manifestPlaceholders["APP_NAME"] = "@string/app_name_dev" |
| | | manifestPlaceholders["icon"] = "@mipmap/ic_launcher" |
| | | |
| | | buildConfigField("String", "API_HOST", "\"http://221.182.45.100:8000/\"") |
| | | } |
| | | create("local") { // 本地环境 |
| | | dimension = "api" |
| | | applicationId = APPLICATION_ID |
| | | |
| | | manifestPlaceholders["APP_NAME"] = "@string/app_name_dev" |
| | | manifestPlaceholders["icon"] = "@mipmap/ic_launcher" |
| | | |
| | | buildConfigField("String", "API_HOST", "\"https://rt-mt.test.xiaopeng.local/\"") |
| | | buildConfigField("String", "DEV_API_HOST", "\"\"") |
| | | buildConfigField("String", "API_HOST", "\"http://rt-admin.test.xiaopeng.local/\"") |
| | | buildConfigField("String", "WS_URL", "\"ws://221.182.45.100:8888/websocket\"") |
| | | //翼虎appId |
| | | buildConfigField("String", "YIHU_APPID", "\"RoboTaxi-admin-test\"") |
| | | //翼虎appSecret |
| | | buildConfigField("String", "YIHU_APP_SECRET", "\"SXa7vEe7V23Xh3RN\"") |
| | | //翼虎公钥 |
| | | buildConfigField("String", "YIHU_PUBLIC_KEY", "\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjT9Pl5kqJeinJyAjvW95ulxtON34ABhX pTRiPczVRJSVSTQzqLyr6UNRU6AvR2diZ4eGRHbeJ+NNtiuSverAZ+xu4B05xWhERThV0SETigkR 9IaJ9glucmAL6S6CNE5JCwWYYDfCgSIN3fzWhC48hGwEL8nauBIHrg2TRs0x4/rtmUR5oEMrm6RA eafdHDD62Dtsc7zm7uIxaOfn90cG35DKsp3iIcw7BLFT/HmBSVML2SLTruNkgDhH2qIZ1t6YQEJj C0uO+jiHXoluKRLWDMg9+SuoJHrsntCzRdwx246g6nMKfxtyC/jrPB6KUdrSR9xaxXZPcNQLb0ip y6mTkwIDAQAB\"") |
| | | //翼虎网络域名 |
| | | buildConfigField("String", "YIHU_API_HOST", "\"https://sso.test.xiaopeng.com/sso/\"") |
| | | } |
| | | create("uat") { // 预发环境 |
| | | dimension = "api" |
| | |
| | | manifestPlaceholders["APP_NAME"] = "@string/app_name_dev" |
| | | manifestPlaceholders["icon"] = "@mipmap/ic_launcher" |
| | | |
| | | buildConfigField("String", "DEV_API_HOST", "\"\"") |
| | | buildConfigField("String", "API_HOST", "\"https://rt-mt.deploy-test.xiaopeng.com/\"") |
| | | buildConfigField("String", "WS_URL", "\"ws://192.168.110.188:8888/websocket\"") |
| | | //翼虎appId |
| | | buildConfigField("String", "YIHU_APPID", "\"RoboTaxi-admin-predeploy\"") |
| | | //翼虎appSecret |
| | | buildConfigField("String", "YIHU_APP_SECRET", "\"i6B5t4744XzO9Ed4\"") |
| | | //翼虎公钥 |
| | | buildConfigField("String", "YIHU_PUBLIC_KEY", "\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvvLduJASaa//KZNaVOH7ieU9QlMs/XxN CRBfFKAtWdwAGtaEnGoAdVJ+6c5dRICVm65Nbq0Xu4jqmdyZceUgWkXjBnxCRNCF0KT5HP6f08XS maDlWXXGDcE3ybZ9rhjwAplAJXoDHjLIi8aklI8qY6G5qUIPX+Occun5xhmiVDTKoyAkNFZ+n1K7 pVc+0rYzownPrGo4Yg+BmSJPoigxJJVZeVZ91jOiwt7F8jyE6EDmTQItJaElFF/zCbY6JlE3Ftsg HjkP/2lFDp+xMZi6b//BvB/GctWmLhkgFZ7j1PtH2Q5YKUzz8Ko6FhAkLaP7K7LXFfkQ1edOrygn 5XRuxQIDAQAB\"") |
| | | //翼虎网络域名 |
| | | buildConfigField("String", "YIHU_API_HOST", "\"https://xtigersso.deploy-test.xiaopeng.com/sso/\"") |
| | | } |
| | | create("prd") { // 正式环境 |
| | | dimension = "api" |
| | |
| | | manifestPlaceholders["APP_NAME"] = "@string/app_name" |
| | | manifestPlaceholders["icon"] = "@mipmap/ic_launcher" |
| | | |
| | | buildConfigField("String", "DEV_API_HOST", "\"\"") |
| | | buildConfigField("String", "API_HOST", "\"https://rt-mt.xiaopeng.com/\"") |
| | | buildConfigField("String", "WS_URL", "\"ws://192.168.110.188:8888/websocket\"") |
| | | //翼虎appId |
| | | buildConfigField("String", "YIHU_APPID", "\"xx\"") |
| | | //翼虎appSecret |
| | | buildConfigField("String", "YIHU_APP_SECRET", "\"xx\"") |
| | | //翼虎公钥 |
| | | buildConfigField("String", "YIHU_PUBLIC_KEY", "\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3O+kbGNL8OsgS8ejbYh7MLxwr6dzrvE4euAcUn+Yk8H5aCgkd7tSCYntd8iQZoml1japizdB8MedJeMZZGeuNLXcHMpf+OmBA//ms3SG6aiocgxruHuHDVkVri/2rTuo078OsHaqnCJlvYEwbuuGj3D3So+TFz/Cw67ipL++j9z90tec40kzNebdfWG12Bv3uX+U8a34QhwE95+uKgwfCU8JEFqXWmA3KAV0COCL4glf3eioe5dBmpBvgnOwauepthjfOKPNqfGqHN88/WTgOHuQraWs9YY4QeYk5s+2CXVWMNlX++r83otgcfzMKmEdToQAHvfLRM4W4I1qaTKR8wIDAQAB\"") |
| | | //翼虎网络域名 |
| | | buildConfigField("String", "YIHU_API_HOST", "\"https://xpsso.xiaopeng.com/sso/\"") |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | implementation(project(":common")) |
| | | implementation(libs.dialog.pickerview) |
| | | implementation(libs.huawei.agconnect.core) |
| | | } |
| | | |
| | |
| | | import org.koin.android.ext.koin.androidContext |
| | | import org.koin.core.context.GlobalContext.startKoin |
| | | import com.tencent.bugly.crashreport.CrashReport |
| | | import com.xw.yunwei.utils.DeviceId |
| | | |
| | | class MyApp : Application() { |
| | | |
| | |
| | | override fun onCreate() { |
| | | super.onCreate() |
| | | myApp = this |
| | | DeviceId.init(this) |
| | | CrashReport.initCrashReport(this, "1d59d50929", true) // bugly |
| | | |
| | | initLog() |
| | |
| | | import com.xw.yunwei.net.UserRepository |
| | | import com.xw.yunwei.net.WorkApi |
| | | import com.xw.yunwei.net.WorkRepository |
| | | import com.xw.yunwei.net.YiHuApi |
| | | import com.xw.yunwei.net.YiHuRepository |
| | | import com.xw.yunwei.net.interceptor.YiHuHeaderInterceptor |
| | | import org.koin.dsl.module |
| | | |
| | | val networkModule = module { |
| | |
| | | |
| | | single { |
| | | val baseHttpManager: BaseHttpManager = get() |
| | | baseHttpManager.create(BuildConfig.API_HOST, UserApi::class.java) |
| | | baseHttpManager.create( |
| | | if (BuildConfig.DEBUG && BuildConfig.DEV_API_HOST.isNotEmpty()) BuildConfig.DEV_API_HOST else BuildConfig.API_HOST, |
| | | UserApi::class.java |
| | | ) |
| | | } |
| | | single { UserRepository(get()) } |
| | | |
| | | single { |
| | | val baseHttpManager: BaseHttpManager = get() |
| | | baseHttpManager.create(BuildConfig.API_HOST, WorkApi::class.java) |
| | | baseHttpManager.create( |
| | | if (BuildConfig.DEBUG && BuildConfig.DEV_API_HOST.isNotEmpty()) BuildConfig.DEV_API_HOST else BuildConfig.API_HOST, |
| | | WorkApi::class.java |
| | | ) |
| | | } |
| | | single { WorkRepository(get()) } |
| | | |
| | | single { |
| | | val baseHttpManager: BaseHttpManager = get() |
| | | baseHttpManager.create(BuildConfig.API_HOST, TaskApi::class.java) |
| | | baseHttpManager.create( |
| | | if (BuildConfig.DEBUG && BuildConfig.DEV_API_HOST.isNotEmpty()) BuildConfig.DEV_API_HOST else BuildConfig.API_HOST, |
| | | TaskApi::class.java |
| | | ) |
| | | } |
| | | single { TaskRepository(get()) } |
| | | |
| | | single { |
| | | val baseHttpManager: BaseHttpManager = get() |
| | | baseHttpManager.create(BuildConfig.API_HOST, CommonApi::class.java) |
| | | baseHttpManager.create( |
| | | if (BuildConfig.DEBUG && BuildConfig.DEV_API_HOST.isNotEmpty()) BuildConfig.DEV_API_HOST else BuildConfig.API_HOST, |
| | | CommonApi::class.java |
| | | ) |
| | | } |
| | | single { CommonRepository(get()) } |
| | | |
| | | |
| | | single { |
| | | val baseHttpManager: BaseHttpManager = get() |
| | | baseHttpManager.create( |
| | | BuildConfig.YIHU_API_HOST, |
| | | YiHuApi::class.java, |
| | | YiHuHeaderInterceptor() |
| | | ) |
| | | } |
| | | single { YiHuRepository(get()) } |
| | | } |
| New file |
| | |
| | | package com.xw.yunwei.extension |
| | | |
| | | import android.content.Context |
| | | import androidx.core.content.ContextCompat |
| | | |
| | | // 扩展 Float |
| | | fun Double.spToPx(context: Context): Double = |
| | | this * context.resources.displayMetrics.scaledDensity |
| | | |
| | | fun Double.pxToSp(context: Context): Double = |
| | | this / context.resources.displayMetrics.scaledDensity |
| | | |
| | | // 扩展 Float |
| | | fun Float.spToPx(context: Context): Float = |
| | | this * context.resources.displayMetrics.scaledDensity |
| | | |
| | | fun Float.pxToSp(context: Context): Float = |
| | | this / context.resources.displayMetrics.scaledDensity |
| | | |
| | | // 扩展 Int |
| | | fun Int.spToPx(context: Context): Int = |
| | | (this * context.resources.displayMetrics.scaledDensity).toInt() |
| | | |
| | | fun Int.pxToSp(context: Context): Int = |
| | | (this / context.resources.displayMetrics.scaledDensity).toInt() |
| | | |
| | | // 扩展 Float |
| | | fun Double.dpToPx(context: Context): Double = |
| | | this * context.resources.displayMetrics.density |
| | | |
| | | fun Double.pxToDp(context: Context): Double = |
| | | this / context.resources.displayMetrics.density |
| | | |
| | | // 扩展 Float |
| | | fun Float.dpToPx(context: Context): Float = |
| | | this * context.resources.displayMetrics.density |
| | | |
| | | fun Float.pxToDp(context: Context): Float = |
| | | this / context.resources.displayMetrics.density |
| | | |
| | | // 扩展 Int |
| | | fun Int.dpToPx(context: Context): Int = |
| | | (this * context.resources.displayMetrics.density + 0.5f).toInt() |
| | | |
| | | fun Int.pxToDp(context: Context): Int = |
| | | (this / context.resources.displayMetrics.density + 0.5f).toInt() |
| | | |
| | | //颜色资源id转颜色值 |
| | | fun Int.colorIdToColorValue(context: Context): Int = |
| | | ContextCompat.getColor(context, this) |
| New file |
| | |
| | | package com.xw.yunwei.extension |
| | | |
| | | import android.util.Base64 |
| | | import java.nio.charset.StandardCharsets |
| | | import java.security.KeyFactory |
| | | import java.security.MessageDigest |
| | | import java.security.PublicKey |
| | | import java.security.spec.X509EncodedKeySpec |
| | | import javax.crypto.Cipher |
| | | import javax.crypto.Mac |
| | | import javax.crypto.SecretKey |
| | | import javax.crypto.spec.SecretKeySpec |
| | | |
| | | //扩展函数 MD5加密 耗时操作 |
| | | fun String.md5(): String { |
| | | return try { |
| | | val md = MessageDigest.getInstance("MD5") |
| | | val digest = md.digest(toByteArray()) |
| | | digest.joinToString("") { |
| | | "%02x".format(it) |
| | | } |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | "" |
| | | } |
| | | } |
| | | |
| | | //扩展函数 通过HmacSHA256加密 |
| | | fun String.encryptByHmacSHA256(key: String): String { |
| | | try { |
| | | val key: SecretKey = |
| | | SecretKeySpec( |
| | | key.toByteArray(StandardCharsets.UTF_8), |
| | | "HmacSHA256" |
| | | ) |
| | | val mac = Mac.getInstance(key.algorithm) |
| | | mac.init(key) |
| | | val sig = mac.doFinal(toByteArray(StandardCharsets.UTF_8)) |
| | | val hex = java.lang.StringBuilder(sig.size * 2) |
| | | for (b in sig) { |
| | | hex.append(Character.forDigit((b.toInt() shr 4) and 0xF, 16)) |
| | | hex.append(Character.forDigit(b.toInt() and 0xF, 16)) |
| | | } |
| | | return hex.toString() |
| | | } catch (e: Exception) { |
| | | throw RuntimeException(e) |
| | | } |
| | | } |
| | | |
| | | //扩展函数 通过公钥RSA加密 |
| | | fun String.encryptByPublicKey(publicKeyText: String): String { |
| | | return try { |
| | | // 1. 解码 Base64 编码的公钥文本获取 DER 格式字节数组 |
| | | // 对应 Python: base64.b64decode(public_key_text) |
| | | val derPublicKeyBytes = Base64.decode(publicKeyText, Base64.DEFAULT) |
| | | |
| | | // 2. 加载 DER 格式的公钥 |
| | | // 对应 Python: serialization.load_der_public_key(...) |
| | | // 在 Java/Kotlin 中,通常使用 X509EncodedKeySpec 来加载 DER 格式的公钥 |
| | | val keySpec = X509EncodedKeySpec(derPublicKeyBytes) |
| | | |
| | | // 注意:这里使用 "RSA" 算法。如果你的后端严格要求 PKCS#1 格式, |
| | | // 可能需要使用 "RSA" 并配合 Bouncy Castle 库,但 X509EncodedKeySpec 通常能处理大多数标准公钥。 |
| | | val keyFactory = KeyFactory.getInstance("RSA") |
| | | val publicKey: PublicKey = keyFactory.generatePublic(keySpec) |
| | | |
| | | // 3. 使用 RSA 加密 |
| | | // 对应 Python: public_key.encrypt(..., padding.PKCS1v15()) |
| | | val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") |
| | | cipher.init(Cipher.ENCRYPT_MODE, publicKey) |
| | | |
| | | // 将文本转换为字节数组并加密 |
| | | val encryptedBytes = cipher.doFinal(toByteArray(StandardCharsets.UTF_8)) |
| | | |
| | | // 4. 返回 Base64 编码的加密结果 |
| | | // 对应 Python: base64.b64encode(ciphertext).decode('utf-8') |
| | | Base64.encodeToString(encryptedBytes, Base64.NO_WRAP) |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | "" |
| | | } |
| | | } |
| New file |
| | |
| | | package com.xw.yunwei.extension |
| | | |
| | | import android.view.View |
| | | |
| | | //扩展函数 设置防重复点击监听器 |
| | | fun View.setOnDurationClickListener(duration: Long = 1000L, listener: View.OnClickListener) { |
| | | var lastClickTimestamp = 0L |
| | | setOnClickListener { |
| | | val now = System.currentTimeMillis() |
| | | if (now - lastClickTimestamp > duration) { |
| | | lastClickTimestamp = now |
| | | listener.onClick(it) |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | package com.xw.yunwei.model |
| | | |
| | | //账号密码登录验证码验证信息 |
| | | data class PasswordLoginCodeValidateInfo(val randomKey: String) |
| New file |
| | |
| | | package com.xw.yunwei.model |
| | | |
| | | import android.os.Parcelable |
| | | import kotlinx.parcelize.Parcelize |
| | | |
| | | //翼虎登录信息 |
| | | @Parcelize |
| | | data class YiHuLoginInfo( |
| | | val access_token: String?,//登录令牌 |
| | | val expires_in: String?,//access_token的有效时间,单位为‘秒’ |
| | | val refresh_token: String?,//用于刷新token |
| | | val scope: String?,//token权限,可以忽略 |
| | | val token_type: String?//固定为‘Bearer’,可以忽略 |
| | | ) : Parcelable |
| | |
| | | |
| | | import com.xw.common.net.interceptor.HttpResponse |
| | | import com.xw.common.net.interceptor.PageResponse |
| | | import com.xw.yunwei.BuildConfig |
| | | import com.xw.yunwei.model.AppPermissionModel |
| | | import com.xw.yunwei.model.AppVersionModel |
| | | import com.xw.yunwei.model.ClockInStatusModel |
| | |
| | | import okhttp3.RequestBody |
| | | import retrofit2.Call |
| | | import retrofit2.http.Body |
| | | import retrofit2.http.Field |
| | | import retrofit2.http.FormUrlEncoded |
| | | import retrofit2.http.GET |
| | | import retrofit2.http.Header |
| | | import retrofit2.http.POST |
| | | import retrofit2.http.Path |
| | | import retrofit2.http.Query |
| | |
| | | @POST("account/maintenance-app/base/v1/verificationCodeLogin") |
| | | fun loginByCode(@Body body: RequestBody): Call<HttpResponse<LoginModel>> |
| | | |
| | | /*通过第三方token登录*/ |
| | | @GET("${BuildConfig.API_HOST}account/maintenance-app/ssoAuthSdk/appLogin") |
| | | fun loginByThirdToken( |
| | | @Query("deviceType") deviceType: Int = 1, |
| | | @Header("Content-Type") contentType: String = "application/json", |
| | | @Header("XT-Client-Type") clientType: String, |
| | | @Header("XT-DeviceId") deviceId: String, |
| | | @Header("XT-Nonce") nonce: String, |
| | | @Header("XT-AppId") appId: String, |
| | | @Header("XT-Signature") sign: String, |
| | | @Header("Authorization") token: String |
| | | ): Call<HttpResponse<LoginModel>> |
| | | |
| | | /*获取手机验证码*/ |
| | | @POST("account/maintenance-app/base/v1/getVerificationCode") |
| | | fun getVerificationCode(@Body body: RequestBody): Call<HttpResponse<Any>> |
| | |
| | | package com.xw.yunwei.net |
| | | |
| | | import android.util.Log |
| | | import com.xw.common.net.BaseHttpManager.Companion.getBody |
| | | import com.xw.common.net.interceptAwait |
| | | import com.xw.yunwei.BuildConfig |
| | | import com.xw.yunwei.extension.encryptByHmacSHA256 |
| | | import com.xw.yunwei.extension.md5 |
| | | import com.xw.yunwei.model.LoginModel |
| | | import com.xw.yunwei.utils.DeviceId |
| | | import kotlinx.coroutines.flow.flow |
| | | |
| | | class UserRepository(private val api: UserApi) { |
| | | |
| | | private val TAG = "UserRepository" |
| | | fun loginByPassword(map: Map<String, Any?>) = flow { |
| | | emit(api.loginByPassword(getBody(map)).interceptAwait()) |
| | | } |
| | |
| | | |
| | | suspend fun loginByCode(map: Map<String, Any?>) = api.loginByCode(getBody(map)).interceptAwait() |
| | | |
| | | /*通过第三方token登录*/ |
| | | suspend fun loginByThirdToken(token: String): LoginModel? { |
| | | val clientType = "Android" |
| | | val deviceId = DeviceId.get() |
| | | val nonce = System.currentTimeMillis().toString() |
| | | val appId = BuildConfig.YIHU_APPID |
| | | |
| | | val path = "/xtiger/app/userinfo" |
| | | //第1步,生成parameter |
| | | val parameter = mapOf( |
| | | "XT-Client-Type" to clientType, |
| | | "XT-DeviceId" to deviceId, |
| | | "XT-Nonce" to nonce, |
| | | "XT-AppId" to appId, |
| | | ).toSortedMap().map { "${it.key.uppercase()}=${it.value}" }.joinToString("&") |
| | | |
| | | val content = "" |
| | | //第2步,请求JSON body转成contentMd5 ,注意md5后为小写 |
| | | val contentMd5 = content.md5() |
| | | Log.d(TAG, "intercept: contentMd5:${contentMd5}") |
| | | // 第3步,组合成最终需要签名的字符串 |
| | | val strToSign = "${path}\n${parameter}\n${contentMd5}" |
| | | Log.d(TAG, "intercept: strToSign:${strToSign}") |
| | | // 第4步. 最终签名值 |
| | | val sign = strToSign.encryptByHmacSHA256(BuildConfig.YIHU_APP_SECRET) |
| | | Log.d(TAG, "intercept: sign:${sign}") |
| | | return api.loginByThirdToken( |
| | | clientType = clientType, |
| | | deviceId = deviceId, |
| | | nonce = nonce, |
| | | appId = appId, |
| | | sign = sign, |
| | | token = token |
| | | ).interceptAwait() |
| | | } |
| | | |
| | | |
| | | suspend fun modifierPsw(map: Map<String, Any?>) = api.modifierPsw(getBody(map)).interceptAwait() |
| | | suspend fun read(id: Long) = api.read(id).interceptAwait() |
| | | suspend fun getAppPermission() = api.getAppPermission().interceptAwait() |
| New file |
| | |
| | | package com.xw.yunwei.net |
| | | |
| | | import com.xw.common.net.interceptor.HttpResponse |
| | | import com.xw.yunwei.model.PasswordLoginCodeValidateInfo |
| | | import com.xw.yunwei.model.YiHuLoginInfo |
| | | import okhttp3.RequestBody |
| | | import retrofit2.Call |
| | | import retrofit2.http.Body |
| | | import retrofit2.http.POST |
| | | |
| | | //翼虎api |
| | | interface YiHuApi { |
| | | |
| | | /*获取手机号验证码*/ |
| | | @POST("app/sms/code") |
| | | fun getPhoneLoginCode(@Body body: RequestBody): Call<HttpResponse<Any>> |
| | | |
| | | /*手机验证码登录*/ |
| | | @POST("app/sms/login") |
| | | fun phoneLogin(@Body body: RequestBody): Call<HttpResponse<YiHuLoginInfo>> |
| | | |
| | | /*获取双因子验证码(非TEE版本)*/ |
| | | @POST("app/twoFactor/code") |
| | | fun getPasswordLoginCode(@Body body: RequestBody): Call<HttpResponse<PasswordLoginCodeValidateInfo>> |
| | | |
| | | /*验证码+账密双因子登录(TEE方式)*/ |
| | | @POST("app/twoFactor/login") |
| | | fun passwordLogin(@Body body: RequestBody): Call<HttpResponse<YiHuLoginInfo>> |
| | | } |
| New file |
| | |
| | | package com.xw.yunwei.net |
| | | |
| | | import com.xw.common.net.BaseHttpManager.Companion.getBody |
| | | import com.xw.common.net.interceptAwait |
| | | import com.xw.yunwei.BuildConfig |
| | | import kotlinx.coroutines.flow.flow |
| | | |
| | | //翼虎网络接口仓库 |
| | | class YiHuRepository(private val api: YiHuApi) { |
| | | |
| | | //获取登录验证码 |
| | | fun getPhoneLoginCode(phone: String) = flow { |
| | | val map = mapOf<String, Any?>( |
| | | "phone" to phone, |
| | | "smsType" to "LOGIN", |
| | | "clientId" to BuildConfig.YIHU_APPID |
| | | ) |
| | | emit(api.getPhoneLoginCode(getBody(map)).interceptAwait()) |
| | | } |
| | | |
| | | //手机验证码登录 |
| | | fun login(phone: String, authCode: String) = flow { |
| | | val map = mapOf<String, Any?>( |
| | | "phone" to phone, |
| | | "authCode" to authCode, |
| | | "clientId" to BuildConfig.YIHU_APPID |
| | | ) |
| | | emit(api.phoneLogin(getBody(map)).interceptAwait()) |
| | | } |
| | | |
| | | //获取双因子验证码(非TEE版本) |
| | | fun getPasswordLoginCode(phone: String) = flow { |
| | | val map = mapOf<String, Any?>( |
| | | "username" to phone, |
| | | "twoFactorType" to 1, |
| | | "clientId" to BuildConfig.YIHU_APPID |
| | | ) |
| | | emit(api.getPasswordLoginCode(getBody(map)).interceptAwait()) |
| | | } |
| | | |
| | | //验证码+账密双因子登录(TEE方式) |
| | | fun passwordLogin(phone: String, authCode: String, password: String, randomKey: String) = flow { |
| | | val map = mapOf<String, Any?>( |
| | | "username" to phone, |
| | | "twoFactorType" to 1, |
| | | "authCode" to authCode, |
| | | "randomKey" to randomKey, |
| | | "password" to password, |
| | | "clientId" to BuildConfig.YIHU_APPID |
| | | ) |
| | | emit(api.passwordLogin(getBody(map)).interceptAwait()) |
| | | } |
| | | } |
| New file |
| | |
| | | package com.xw.yunwei.net.interceptor |
| | | |
| | | import android.util.Log |
| | | import com.xw.yunwei.BuildConfig |
| | | import com.xw.yunwei.extension.encryptByHmacSHA256 |
| | | import com.xw.yunwei.extension.md5 |
| | | import com.xw.yunwei.utils.DeviceId |
| | | import okhttp3.FormBody |
| | | import okhttp3.Interceptor |
| | | import okhttp3.Response |
| | | import okio.Buffer |
| | | |
| | | |
| | | //翼虎网络接口header拦截器 |
| | | class YiHuHeaderInterceptor : Interceptor { |
| | | private val TAG = "YiHuHeaderInterceptor" |
| | | override fun intercept(chain: Interceptor.Chain): Response { |
| | | val original = chain.request() |
| | | val publicHeader = mutableMapOf( |
| | | "XT-Client-Type" to "Android", |
| | | "XT-DeviceId" to DeviceId.get(), |
| | | "XT-Nonce" to System.currentTimeMillis().toString(), |
| | | "XT-AppId" to BuildConfig.YIHU_APPID, |
| | | ) |
| | | Log.d(TAG, "intercept: publicHeader:${publicHeader}") |
| | | val path = original.url.toString().let { |
| | | val start = it.indexOf("sso/") |
| | | val end = it.indexOf("?") |
| | | if (end == -1) { |
| | | it.substring(start + 3) |
| | | } else { |
| | | it.substring(start + 3, end) |
| | | } |
| | | } |
| | | Log.d(TAG, "intercept: path:${path}") |
| | | val map = original.url.let { url -> |
| | | url.queryParameterNames.associateWith { |
| | | url.queryParameter(it) |
| | | } |
| | | }.toMutableMap() |
| | | map.putAll(publicHeader) |
| | | val body = original.body |
| | | if (body is FormBody) { |
| | | (0 until body.size).forEach { |
| | | map[body.name(it)] = body.value(it) |
| | | } |
| | | } |
| | | //第1步,生成parameter |
| | | val parameter = |
| | | map.toSortedMap().map { "${it.key.uppercase()}=${it.value}" }.joinToString("&") |
| | | Log.d(TAG, "intercept: parameter:${parameter}") |
| | | val method = original.method |
| | | val content = |
| | | if ((method == "POST" || method == "PUT") && body?.contentType()?.subtype == "json") { |
| | | val buffer = Buffer() |
| | | body.writeTo(buffer) |
| | | buffer.readUtf8() |
| | | } else { |
| | | "" |
| | | } |
| | | Log.d(TAG, "intercept: content:${content}") |
| | | //第2步,请求JSON body转成contentMd5 ,注意md5后为小写 |
| | | val contentMd5 = content.md5() |
| | | Log.d(TAG, "intercept: contentMd5:${contentMd5}") |
| | | // 第3步,组合成最终需要签名的字符串 |
| | | val strToSign = "${path}\n${parameter}\n${contentMd5}" |
| | | Log.d(TAG, "intercept: strToSign:${strToSign}") |
| | | // 第4步. 最终签名值 |
| | | val sign = strToSign.encryptByHmacSHA256(BuildConfig.YIHU_APP_SECRET) |
| | | Log.d(TAG, "intercept: sign:${sign}") |
| | | publicHeader["XT-Signature"] = sign |
| | | publicHeader["Content-Type"] = "application/json" |
| | | val request = original.newBuilder() |
| | | publicHeader.forEach { (key, value) -> request.addHeader(key, value) } |
| | | return chain.proceed(request.build()) |
| | | } |
| | | } |
| | |
| | | |
| | | import android.annotation.SuppressLint |
| | | import android.graphics.Color |
| | | import android.text.InputType |
| | | import android.text.TextPaint |
| | | import android.text.method.HideReturnsTransformationMethod |
| | | import android.text.method.LinkMovementMethod |
| | |
| | | |
| | | override fun setObservable() { |
| | | super.setObservable() |
| | | viewModel.tabIndex.observe(this) { |
| | | // binding.phone.setText("") |
| | | binding.code.setText("") |
| | | when (it) { |
| | | 0 -> { |
| | | // binding.phone.hint = "手机号" |
| | | // binding.phone.inputType = InputType.TYPE_CLASS_PHONE |
| | | // viewModel.passwordLoginCodeValidateInfo = null |
| | | } |
| | | |
| | | 1 -> { |
| | | // binding.phone.hint = "登录账户或者邮箱" |
| | | // binding.phone.inputType = InputType.TYPE_CLASS_TEXT |
| | | } |
| | | } |
| | | } |
| | | viewModel.protocolStatus.observe(this) { |
| | | MMKVManager.getInstance().saveBoolean(KeyPreferences.NEED_READ, !it) |
| | | } |
| | | viewModel.phone.observe(this) { |
| | | viewModel.canGetCode.value = it.length == 11 |
| | | when (viewModel.tabIndex.value) { |
| | | 0 -> { |
| | | viewModel.canGetCode.postValue(it.length == 11) |
| | | } |
| | | |
| | | 1 -> { |
| | | // viewModel.canGetCode.postValue(it.isNotEmpty()) |
| | | viewModel.canGetCode.postValue(it.length == 11) |
| | | } |
| | | } |
| | | } |
| | | viewModel.eyeStatus2.observe(this) { |
| | | setPasswordVisible(binding.psw, it) |
| | |
| | | import com.xw.common.utils.KeyPreferences |
| | | import com.xw.common.utils.MMKVManager |
| | | import com.xw.common.utils.ToastUtilCommon |
| | | import com.xw.yunwei.BuildConfig |
| | | import com.xw.yunwei.R |
| | | import com.xw.yunwei.extension.encryptByPublicKey |
| | | import com.xw.yunwei.model.PasswordLoginCodeValidateInfo |
| | | import com.xw.yunwei.net.UserRepository |
| | | import com.xw.yunwei.net.WorkRepository |
| | | import com.xw.yunwei.net.YiHuRepository |
| | | import com.xw.yunwei.ui.MainActivity |
| | | import kotlinx.coroutines.flow.MutableStateFlow |
| | | import kotlinx.coroutines.flow.StateFlow |
| | | import org.koin.core.component.inject |
| | | import kotlin.getValue |
| | | |
| | | class LoginVM(application: Application) : BaseViewModel(application) { |
| | | val userRepository: UserRepository by inject() |
| | | val yiHuRepository: YiHuRepository by inject() |
| | | |
| | | val tabIndex = MutableLiveData(0) |
| | | |
| | |
| | | |
| | | val protocolStatus = MutableLiveData(false) |
| | | |
| | | //账号密码验证码验证信息 |
| | | var passwordLoginCodeValidateInfo: PasswordLoginCodeValidateInfo? = null |
| | | val onClick = View.OnClickListener { |
| | | when (it.id) { |
| | | R.id.protocolStatus -> protocolStatus.value = !protocolStatus.value!! |
| | |
| | | ToastUtilCommon.showError("请输入验证码!") |
| | | return |
| | | } |
| | | launchRequest(tips = "登录中...") { |
| | | val map = mapOf("phoneNumber" to phone.value, "verificationCode" to code.value, "deviceType" to 1) |
| | | val data = userRepository.loginByCode(map) |
| | | data?.let { |
| | | Constants.token = it.token?.access_token ?: "" |
| | | MMKVManager.getInstance().saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | launchRequestFlow( |
| | | tips = "登录中...", |
| | | flowBlock = { |
| | | yiHuRepository.login(phone.value!!, code.value!!) |
| | | }, |
| | | onCollect = { yiHuData -> |
| | | yiHuData?.let { |
| | | launchRequest(tips = "登录中...") { |
| | | val data = userRepository.loginByThirdToken(yiHuData.access_token!!) |
| | | data?.let { |
| | | Constants.token = it.token?.access_token ?: "" |
| | | MMKVManager.getInstance() |
| | | .saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | ) |
| | | } |
| | | |
| | | private fun loginByPassword() { |
| | | if (phone.value.isNullOrEmpty()) { |
| | | ToastUtilCommon.showError("请输入手机号!") |
| | | return |
| | | } |
| | | if (code.value.isNullOrEmpty()) { |
| | | ToastUtilCommon.showError("请输入验证码!") |
| | | return |
| | | } |
| | | if (passwordLoginCodeValidateInfo == null) { |
| | | ToastUtilCommon.showError("请重新获取验证码!") |
| | | return |
| | | } |
| | | if (psw.value.isNullOrEmpty()) { |
| | |
| | | launchRequestFlow( |
| | | tips = "登录中...", |
| | | flowBlock = { |
| | | val map = mapOf("phoneNumber" to phone.value, "password" to psw.value, "deviceType" to 1) |
| | | userRepository.loginByPassword(map) |
| | | yiHuRepository.passwordLogin( |
| | | phone.value!!, |
| | | code.value!!, |
| | | psw.value!!.encryptByPublicKey(BuildConfig.YIHU_PUBLIC_KEY), |
| | | passwordLoginCodeValidateInfo!!.randomKey |
| | | ) |
| | | }, |
| | | onCollect = { data -> |
| | | data?.let { |
| | | Constants.token = it.token?.access_token ?: "" |
| | | MMKVManager.getInstance().saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | onCollect = { yiHuData -> |
| | | yiHuData?.let { |
| | | launchRequest(tips = "登录中...") { |
| | | val data = userRepository.loginByThirdToken(yiHuData.access_token!!) |
| | | data?.let { |
| | | Constants.token = it.token?.access_token ?: "" |
| | | MMKVManager.getInstance() |
| | | .saveModel(KeyPreferences.USER, it.info?.xpOperationsUser) |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | ) |
| | |
| | | closeKeyboard() |
| | | timer.start() |
| | | canGetCode.postValue(false) |
| | | launchRequest(tips = "验证码获取中...", error = { code, msg -> |
| | | canGetCode.postValue(true) |
| | | timer.cancel() |
| | | codeTips.postValue("重新发送") |
| | | ToastUtilCommon.showError(msg) |
| | | }) { |
| | | val map = mapOf("phoneNumber" to phone.value!!, "codeType" to codeType) |
| | | userRepository.getVerificationCode(map) |
| | | ToastUtilCommon.showSuc("验证码已发送") |
| | | if (codeType != 1) { |
| | | launchRequest(tips = "验证码获取中...", error = { code, msg -> |
| | | canGetCode.postValue(true) |
| | | timer.cancel() |
| | | codeTips.postValue("重新发送") |
| | | ToastUtilCommon.showError(msg) |
| | | }) { |
| | | val map = mapOf("phoneNumber" to phone.value!!, "codeType" to codeType) |
| | | userRepository.getVerificationCode(map) |
| | | ToastUtilCommon.showSuc("验证码已发送") |
| | | } |
| | | return |
| | | } |
| | | launchRequestFlow( |
| | | tips = "验证码获取中...", |
| | | flowBlock = { |
| | | if (tabIndex.value == 0) { |
| | | yiHuRepository.getPhoneLoginCode(phone.value!!) |
| | | } else { |
| | | yiHuRepository.getPasswordLoginCode(phone.value!!) |
| | | } |
| | | }, |
| | | error = { code, msg -> |
| | | canGetCode.postValue(true) |
| | | timer.cancel() |
| | | codeTips.postValue("重新发送") |
| | | ToastUtilCommon.showError(msg) |
| | | }, |
| | | onCollect = { data -> |
| | | ToastUtilCommon.showSuc("验证码已发送") |
| | | if (tabIndex.value == 1) { |
| | | passwordLoginCodeValidateInfo = data as PasswordLoginCodeValidateInfo? |
| | | } |
| | | } |
| | | ) |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | package com.xw.yunwei.utils |
| | | |
| | | import android.content.Context |
| | | import android.content.SharedPreferences |
| | | import androidx.core.content.edit |
| | | import java.util.UUID |
| | | |
| | | //设备id |
| | | object DeviceId { |
| | | private const val KEY_DEVICE_ID = "device_id" |
| | | private lateinit var sp: SharedPreferences |
| | | |
| | | //初始化 |
| | | fun init(context: Context) { |
| | | sp = context.getSharedPreferences("device", Context.MODE_PRIVATE) |
| | | var id = sp.getString(KEY_DEVICE_ID, null) |
| | | if (id == null) { |
| | | id = UUID.randomUUID().toString().replace("-","") |
| | | sp.edit { putString(KEY_DEVICE_ID, id) } |
| | | } |
| | | } |
| | | |
| | | fun get(): String { |
| | | return sp.getString(KEY_DEVICE_ID, null) ?: "" |
| | | } |
| | | } |
| | |
| | | |
| | | <androidx.appcompat.widget.AppCompatEditText |
| | | android:id="@+id/code" |
| | | show="@{viewModel.tabIndex==0}" |
| | | android:layout_width="match_parent" |
| | | android:layout_height="@dimen/dp_56" |
| | | android:layout_marginHorizontal="@dimen/dp_32" |
| | |
| | | <androidx.appcompat.widget.AppCompatTextView |
| | | android:id="@+id/getCode" |
| | | selected="@{viewModel.canGetCode}" |
| | | show="@{viewModel.tabIndex==0}" |
| | | android:layout_width="wrap_content" |
| | | android:layout_height="wrap_content" |
| | | android:onClick="@{viewModel.onClick}" |
| | |
| | | app:layout_constraintEnd_toEndOf="@+id/code" |
| | | tools:text="发送验证码" /> |
| | | |
| | | <View |
| | | android:layout_width="match_parent" |
| | | android:layout_height="1px" |
| | | android:layout_marginHorizontal="@dimen/dp_32" |
| | | android:background="@color/gray_4" |
| | | app:layout_constraintTop_toBottomOf="@+id/code" /> |
| | | |
| | | <androidx.appcompat.widget.AppCompatEditText |
| | | android:id="@+id/psw" |
| | | show="@{viewModel.tabIndex==1}" |
| | |
| | | // Top-level build file where you can add configuration options common to all sub-projects/modules. |
| | | // 注意:buildscript块必须放在最前面 |
| | | buildscript { |
| | | repositories { |
| | | google() |
| | | mavenCentral() |
| | | // 添加华为仓库 |
| | | maven { url = uri("https://developer.huawei.com/repo/") } |
| | | } |
| | | dependencies { |
| | | classpath("com.android.tools.build:gradle:8.12.3") |
| | | // 添加华为推送插件的classpath |
| | | classpath("com.huawei.agconnect:agcp:1.5.2.300") // 请确认版本号,可以使用libs.versions.toml中定义的版本 |
| | | } |
| | | } |
| | | plugins { |
| | | alias(libs.plugins.android.application) apply false |
| | | alias(libs.plugins.android.library) apply false |
| | |
| | | import com.xw.common.net.interceptor.HeaderInterceptor |
| | | import com.xw.common.net.interceptor.LoggingInterceptor |
| | | import okhttp3.HttpUrl.Companion.toHttpUrl |
| | | import okhttp3.Interceptor |
| | | import okhttp3.MediaType.Companion.toMediaTypeOrNull |
| | | import okhttp3.OkHttpClient |
| | | import okhttp3.RequestBody |
| | | import org.json.JSONArray |
| | | import org.json.JSONObject |
| | | import retrofit2.Retrofit |
| | | import retrofit2.converter.gson.GsonConverterFactory |
| | | import retrofit2.converter.scalars.ScalarsConverterFactory |
| | |
| | | |
| | | } |
| | | |
| | | fun <T> create(baseUrl: String, c: Class<T>): T { |
| | | fun <T> create( |
| | | baseUrl: String, |
| | | c: Class<T>, |
| | | interceptor: Interceptor = HeaderInterceptor() |
| | | ): T { |
| | | return Retrofit.Builder() |
| | | .baseUrl(baseUrl.toHttpUrl()) |
| | | .client(provideOKHttpClient()) |
| | | .client(provideOKHttpClient(interceptor)) |
| | | .addConverterFactory(ScalarsConverterFactory.create()) |
| | | .addConverterFactory(GsonConverterFactory.create()) |
| | | .build() |
| | | .create(c) |
| | | } |
| | | |
| | | private fun provideOKHttpClient(): OkHttpClient { |
| | | private fun provideOKHttpClient(interceptor: Interceptor): OkHttpClient { |
| | | val builder = OkHttpClient.Builder() |
| | | .connectTimeout(30, TimeUnit.SECONDS) |
| | | .readTimeout(30, TimeUnit.SECONDS) |
| | | .writeTimeout(30, TimeUnit.SECONDS) |
| | | .retryOnConnectionFailure(true) |
| | | .addInterceptor(HeaderInterceptor()) |
| | | .addInterceptor(interceptor) |
| | | .addInterceptor(LoggingInterceptor()) |
| | | return builder.build() |
| | | } |
| | |
| | | package com.xw.common.net.interceptor |
| | | |
| | | import android.os.Build |
| | | import com.blankj.utilcode.util.DeviceUtils |
| | | import com.xw.common.Constants.token |
| | | import okhttp3.Interceptor |
| | | import okhttp3.Response |
| | |
| | | class HeaderInterceptor : Interceptor { |
| | | |
| | | override fun intercept(chain: Interceptor.Chain): Response { |
| | | val original = chain.request() |
| | | val request = original.newBuilder() |
| | | .header("Authorization", token) |
| | | return chain.proceed(request.build()) |
| | | val request = chain.request() |
| | | val builder = request.newBuilder() |
| | | if (request.header("Authorization").isNullOrEmpty()) { |
| | | builder.header("Authorization", token) |
| | | } |
| | | return chain.proceed(builder.build()) |
| | | } |
| | | } |
| | |
| | | package com.xw.common.net.interceptor |
| | | |
| | | import com.xw.common.utils.LogUtils |
| | | import okhttp3.Headers |
| | | import okhttp3.Interceptor |
| | | import okhttp3.MediaType |
| | | import okhttp3.MultipartBody |
| | |
| | | |
| | | class LoggingInterceptor : Interceptor { |
| | | |
| | | val TAG = "zmc-Log" |
| | | val TAG = "LoggingInterceptor" |
| | | |
| | | @Throws(IOException::class) |
| | | override fun intercept(chain: Interceptor.Chain): Response { |
| | | val request = chain.request() |
| | | |
| | | val startTime = System.currentTimeMillis() |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "head " + request.headers) |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "----------Start----------------") |
| | | if (request.method.compareTo("get", true) == 0 || request.method.compareTo("delete", true) == 0) { |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "| " + URLDecoder.decode(request.toString(), "UTF-8")) |
| | | } else { |
| | | val requestInfo = "Request{method=${request.method}, url=${request.url}, head=${request.headers}}" |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "| $requestInfo") |
| | | try { |
| | | val jsonInfo = "RequestParams:" + URLDecoder.decode(bodyToString(request), "UTF-8") |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "| $jsonInfo") |
| | | } catch (e: Exception) { |
| | | LogUtils.e("$TAG-${Thread.currentThread().id}", "| " + "RequestParams:" + bodyToString(request)) |
| | | LogUtils.e("$TAG-${Thread.currentThread().id}", "| " + "error:" + e.message) |
| | | } |
| | | } |
| | | |
| | | val response: Response |
| | | try { |
| | | response = chain.proceed(request) |
| | | } catch (e: IOException) { |
| | | LogUtils.d(TAG, "----------Start----------------") |
| | | printRequest(request) |
| | | val endTime = System.currentTimeMillis() |
| | | val duration = endTime - startTime |
| | | LogUtils.e("$TAG-${Thread.currentThread().id}", "| Network Error: ${e.javaClass.simpleName}: ${e.message}") |
| | | LogUtils.e("$TAG-${Thread.currentThread().id}", "----------End(Error):" + duration + "毫秒----------") |
| | | e.printStackTrace() |
| | | LogUtils.e(TAG, "----------End(Error):" + duration + "毫秒----------") |
| | | throw e // 重新抛出,让上层处理 |
| | | } |
| | | LogUtils.d(TAG, "----------Start----------------") |
| | | printRequest(request) |
| | | val endTime = System.currentTimeMillis() |
| | | |
| | | val bodyString = response.body?.string() ?: "" |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}", "| Response:$bodyString") |
| | | LogUtils.d(TAG, "response=$bodyString") |
| | | printHeader("responseHeaders=", response.headers) |
| | | val duration = endTime - startTime |
| | | LogUtils.d("$TAG-${Thread.currentThread().id}","----------End:" + duration + "毫秒----------") |
| | | LogUtils.d(TAG, "----------End:" + duration + "毫秒----------") |
| | | |
| | | val newBody: ResponseBody = bodyString.toResponseBody(response.body?.contentType()) |
| | | return response.newBuilder() |
| | | .body(newBody) |
| | | .build() |
| | | } |
| | | |
| | | //打印请求体 |
| | | private fun printRequest(request: Request) { |
| | | LogUtils.d(TAG, "url=" + request.url) |
| | | LogUtils.d(TAG, "method=" + request.method) |
| | | printHeader("headers=", request.headers) |
| | | if (request.method.compareTo("get", true) == 0 || request.method.compareTo( |
| | | "delete", |
| | | true |
| | | ) == 0 |
| | | ) { |
| | | val params = request.url.let { url -> |
| | | url.queryParameterNames.associateWith { |
| | | url.queryParameter(it) |
| | | } |
| | | }.map { "${it.key}=${it.value}" }.joinToString("&") |
| | | LogUtils.d(TAG, "params=${params}") |
| | | } else { |
| | | try { |
| | | LogUtils.d(TAG, "params=${URLDecoder.decode(bodyToString(request), "UTF-8")}") |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | LogUtils.e(TAG, "params=${bodyToString(request)}") |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun printHeader(prefix: String, headers: Headers) { |
| | | LogUtils.d(TAG, prefix + buildString { |
| | | for (i in 0 until headers.size) { |
| | | val name = headers.name(i) |
| | | val value = headers.value(i) |
| | | append(name) |
| | | append(": ") |
| | | append(value) |
| | | append("\n") |
| | | } |
| | | }) |
| | | } |
| | | |
| | | private fun bodyToString(request: Request): String? { |
| | |
| | | copy.body!!.writeTo(buffer) |
| | | } |
| | | buffer.readUtf8() |
| | | } catch (e: IOException) { |
| | | "did not work" |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | null |
| | | } |
| | | } |
| | | |
| | |
| | | [versions] |
| | | agp = "8.12.3" |
| | | kotlin = "2.0.21" |
| | | huawei = "1.5.2.300" |
| | | coreKtx = "1.17.0" |
| | | appcompat = "1.7.1" |
| | | junit = "4.14-SNAPSHOT" |
| | |
| | | # 腾讯bugly |
| | | crashreport = { module = "com.tencent.bugly:crashreport", version.ref = "crashreport" } |
| | | nativecrashreport = { module = "com.tencent.bugly:nativecrashreport", version.ref = "crashreport" } |
| | | huawei-agconnect-core = { module = "com.huawei.agconnect:agconnect-core", version.ref = "huawei" } |
| | | |
| | | [plugins] |
| | | android-application = { id = "com.android.application", version.ref = "agp" } |
| | |
| | | maven("https://jitpack.io") // 私有仓库地址 |
| | | maven("https://repo1.maven.org/maven2/") |
| | | maven("https://dl.bintray.com/cjt/maven") |
| | | maven("https://developer.huawei.com/repo/") |
| | | } |
| | | } |
| | | dependencyResolutionManagement { |
| | |
| | | maven("https://maven.aliyun.com/nexus/content/repositories/releases/") |
| | | // 配置HMS Core SDK的Maven仓地址,集成华为通道需要。 |
| | | maven("https://developer.huawei.com/repo/") |
| | | |
| | | |
| | | |
| | | } |