| | |
| | | |
| | | buildConfigField("String", "API_HOST", "\"http://221.182.45.100:8000/\"") |
| | | buildConfigField("String", "WS_URL", "\"ws://221.182.45.100:8888/websocket\"") |
| | | //翼虎appId |
| | | buildConfigField("String", "YIHU_APPID", "\"RoboTaxi-admin-predeploy\"") |
| | | //翼虎appSecret |
| | | buildConfigField("String", "YIHU_APP_SECRET", "\"i6B5t4744XzO9Ed4\"") |
| | | //翼虎网络域名 |
| | | buildConfigField("String", "YIHU_API_HOST", "\"https://xtigersso.deploy-test.xiaopeng.com/sso/\"") |
| | | } |
| | | create("local") { // 本地环境 |
| | | dimension = "api" |
| | |
| | | |
| | | buildConfigField("String", "API_HOST", "\"https://rt-mt.test.xiaopeng.local/\"") |
| | | buildConfigField("String", "WS_URL", "\"ws://192.168.110.188:8888/websocket\"") |
| | | //翼虎appId |
| | | buildConfigField("String", "YIHU_APPID", "\"RoboTaxi-admin-test\"") |
| | | //翼虎appSecret |
| | | buildConfigField("String", "YIHU_APP_SECRET", "\"SXa7vEe7V23Xh3RN\"") |
| | | //翼虎网络域名 |
| | | buildConfigField("String", "YIHU_API_HOST", "\"http://web-dev.3d.xiaopeng.local:9104/sso/\"") |
| | | } |
| | | create("uat") { // 预发环境 |
| | | dimension = "api" |
| | |
| | | |
| | | 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_API_HOST", "\"https://xtigersso.deploy-test.xiaopeng.com/sso/\"") |
| | | } |
| | | create("prd") { // 正式环境 |
| | | dimension = "api" |
| | |
| | | |
| | | 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_API_HOST", "\"https://xpsso.xiaopeng.com/sso/\"") |
| | | } |
| | | } |
| | | |
| | |
| | | import com.xw.yunwei.ui.dialog.NewTaskDialog |
| | | import com.xw.yunwei.ui.task.accident.detail.AccidentDetailActivity |
| | | import com.xw.yunwei.ui.task.normal.NormalTaskDetailActivity |
| | | import com.xw.yunwei.utils.DeviceId |
| | | import com.xw.yunwei.utils.TTSController |
| | | import com.xw.yunwei.websocket.WebSocketManager |
| | | import kotlinx.coroutines.CompletableDeferred |
| | |
| | | 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 { 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 java.security.MessageDigest |
| | | |
| | | //扩展函数 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() |
| | | "" |
| | | } |
| | | } |
| 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 |
| New file |
| | |
| | | package com.xw.yunwei.net |
| | | |
| | | import com.xw.common.net.interceptor.HttpResponse |
| | | 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 getLoginCode(@Body body: RequestBody): Call<HttpResponse<Any>> |
| | | |
| | | /*手机验证码登录*/ |
| | | @POST("app/sms/login") |
| | | fun login(@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 getLoginCode(phone: String) = flow { |
| | | val map = mapOf<String, Any?>( |
| | | "phone" to phone, |
| | | "smsType" to "LOGIN", |
| | | "clientId" to BuildConfig.YIHU_APPID |
| | | ) |
| | | emit(api.getLoginCode(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.login(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.md5 |
| | | import com.xw.yunwei.utils.DeviceId |
| | | import okhttp3.FormBody |
| | | import okhttp3.Interceptor |
| | | import okhttp3.Response |
| | | import okio.Buffer |
| | | import java.nio.charset.StandardCharsets |
| | | import java.util.Locale |
| | | import javax.crypto.Mac |
| | | import javax.crypto.SecretKey |
| | | import javax.crypto.spec.SecretKeySpec |
| | | |
| | | //翼虎网络接口header拦截器 |
| | | class YiHuHeaderInterceptor : Interceptor { |
| | | private val TAG = "YiHuHeaderInterceptor" |
| | | override fun intercept(chain: Interceptor.Chain): Response { |
| | | val original = chain.request() |
| | | val publicHeader = mutableMapOf( |
| | | "Content-Type" to "application/json", |
| | | "XT-Client-Type" to "Android", |
| | | "XT-DeviceId" to DeviceId.get(), |
| | | "XT-Nonce" to System.currentTimeMillis().toString(), |
| | | "XT-AppId" to BuildConfig.YIHU_APPID, |
| | | ) |
| | | original.headers.forEach { |
| | | publicHeader[it.first] = it.second |
| | | } |
| | | 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 |
| | | when { |
| | | 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 = encryptHmacSHA256(strToSign) |
| | | publicHeader["XT-Signature"] = sign |
| | | val request = original.newBuilder() |
| | | publicHeader.forEach { (key, value) -> request.addHeader(key, value) } |
| | | return chain.proceed(request.build()) |
| | | } |
| | | |
| | | // 签名入口, data - 需要签名的字符串; secretKey - |
| | | private fun encryptHmacSHA256(data: String): String { |
| | | val encodedData = encryptHmac(data, BuildConfig.YIHU_APP_SECRET, "HmacSHA256") ?: return "" |
| | | return byte2hexString(encodedData) |
| | | } |
| | | |
| | | private fun encryptHmac(data: String, secret: String, algorithm: String?): ByteArray? { |
| | | try { |
| | | val secretKey: SecretKey = |
| | | SecretKeySpec(secret.toByteArray(StandardCharsets.UTF_8), algorithm) |
| | | val mac = Mac.getInstance(secretKey.algorithm) |
| | | mac.init(secretKey) |
| | | return mac.doFinal(data.toByteArray(StandardCharsets.UTF_8)) |
| | | } catch (e: Exception) { |
| | | e.printStackTrace() |
| | | } |
| | | return null |
| | | } |
| | | |
| | | private fun byte2hexString(bytes: ByteArray): String { |
| | | val sign = StringBuilder() |
| | | for (aByte in bytes) { |
| | | val hex = Integer.toHexString(aByte.toInt() and 0xFF) |
| | | if (hex.length == 1) { |
| | | sign.append('0') |
| | | } |
| | | sign.append(hex.lowercase(Locale.getDefault())) |
| | | } |
| | | return sign.toString() |
| | | } |
| | | } |
| | |
| | | MyApp.myApp.unbindPushAccount() |
| | | |
| | | val tab1 = binding.tabLayout.newTab().apply { text = "验证码登录" } |
| | | val tab2 = binding.tabLayout.newTab().apply { text = "密码登录" } |
| | | //val tab2 = binding.tabLayout.newTab().apply { text = "密码登录" } |
| | | |
| | | binding.tabLayout.addTab(tab1, true) |
| | | binding.tabLayout.addTab(tab2, false) |
| | | //binding.tabLayout.addTab(tab2, false) |
| | | viewModel.getAgreeConfig() |
| | | } |
| | | |
| | |
| | | import android.os.CountDownTimer |
| | | import android.view.View |
| | | import androidx.lifecycle.MutableLiveData |
| | | import com.amap.location.support.util.TextUtils |
| | | import com.xw.common.Constants |
| | | import com.xw.common.base.BaseViewModel |
| | | import com.xw.common.utils.KeyPreferences |
| | |
| | | import com.xw.yunwei.MyApp |
| | | import com.xw.yunwei.R |
| | | import com.xw.yunwei.model.AgreeInfo |
| | | import com.xw.yunwei.model.YiHuLoginInfo |
| | | import com.xw.yunwei.net.UserRepository |
| | | import com.xw.yunwei.net.YiHuRepository |
| | | import com.xw.yunwei.ui.MainActivity |
| | | import org.koin.core.component.inject |
| | | |
| | | class LoginVM(application: Application) : BaseViewModel(application) { |
| | | val userRepository: UserRepository by inject() |
| | | val yiHuRepository: YiHuRepository by inject() |
| | | |
| | | val tabIndex = MutableLiveData(0) |
| | | |
| | |
| | | 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) |
| | | MyApp.myApp.connectWebSocket() |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | launchRequestFlow( |
| | | tips = "登录中...", |
| | | flowBlock = { |
| | | yiHuRepository.login(phone.value!!, code.value!!) |
| | | }, |
| | | onCollect = { yiHuData -> |
| | | yiHuData?.let { |
| | | 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) |
| | | MyApp.myApp.connectWebSocket() |
| | | startActivity(MainActivity::class.java) |
| | | finish() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | ) |
| | | } |
| | | |
| | | private fun loginByPassword() { |
| | |
| | | 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("验证码已发送") |
| | | } |
| | | // 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("验证码已发送") |
| | | // } |
| | | launchRequestFlow( |
| | | tips = "验证码获取中...", |
| | | flowBlock = { |
| | | yiHuRepository.getLoginCode(phone.value!!) |
| | | }, |
| | | error = { code, msg -> |
| | | canGetCode.postValue(true) |
| | | timer.cancel() |
| | | codeTips.postValue("重新发送") |
| | | ToastUtilCommon.showError(msg) |
| | | }, |
| | | onCollect = { data -> |
| | | ToastUtilCommon.showSuc("验证码已发送") |
| | | } |
| | | ) |
| | | } |
| | | } |
| | | |
| 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) ?: "" |
| | | } |
| | | } |
| | |
| | | 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() |
| | | } |