Kaynağa Gözat

feat 自动刷新loginToken

RobinTan1024 4 yıl önce
ebeveyn
işleme
67cb527a98

+ 37 - 40
app/src/main/java/com/doverfuelingsolutions/issp/api/SystemApi.kt

@@ -39,8 +39,7 @@ object SystemApi {
     suspend fun login(accountName: String, password: String): DFSResult<*> {
         val resultLogin = loginOnly(accountName, password)
         return if (resultLogin.success) {
-            deviceSessionInfo()
-            stationLogo()
+            // 通知服务器(不明白其实际意义,但照做就是了)
             notifyTrx(PosTrxType.LogOn)
         } else {
             resultLogin
@@ -175,6 +174,42 @@ object SystemApi {
         })
     }
 
+    /**
+     * 查询油站联系电话
+     */
+    suspend fun sessionInfo() = suspendCoroutine<DFSResult<ResultDeviceSessionInfo>> {
+        serviceBase.deviceSessionInfo(GlobalData.serialNumber.get()).enqueue(object : Callback<ResultDeviceSessionInfo> {
+            override fun onResponse(call: Call<ResultDeviceSessionInfo>, response: Response<ResultDeviceSessionInfo>) {
+                val code = response.code()
+                val body = response.body()
+                if (code != 200 || body == null) {
+                    it.resume(DFSResult.fail(R.string.return_data_error))
+                } else {
+                    it.resume(DFSResult.success(body))
+                }
+            }
+
+            override fun onFailure(call: Call<ResultDeviceSessionInfo>, t: Throwable) {
+                DFSLog.e(t)
+                it.resume(DFSResult.fail(R.string.return_data_error))
+            }
+        })
+    }
+
+    /**
+     * 获取一些油站信息(LOGO)
+     */
+    suspend fun stationLogo(): DFSResult<String> {
+        val resultConfig = config(WayneApiConfig.CONFIG_LOGO)
+        if (resultConfig.success && resultConfig.data!!.id.isNotEmpty()) {
+            val resultFile = file(resultConfig.data.id)
+            if (resultFile.success && resultFile.data != null) {
+                return DFSResult.success(resultFile.data)
+            }
+        }
+        return DFSResult.fail(R.string.req_fail)
+    }
+
 
 
 
@@ -243,29 +278,6 @@ object SystemApi {
         })
     }
 
-    /**
-     * 查询设备附属信息
-     */
-    private suspend fun deviceSessionInfo() = suspendCoroutine<DFSResult<ResultDeviceSessionInfo>> {
-        serviceBase.deviceSessionInfo(GlobalData.serialNumber.get()).enqueue(object : Callback<ResultDeviceSessionInfo> {
-            override fun onResponse(call: Call<ResultDeviceSessionInfo>, response: Response<ResultDeviceSessionInfo>) {
-                val code = response.code()
-                val body = response.body()
-                if (code != 200 || body == null) {
-                    it.resume(DFSResult.fail(R.string.return_data_error))
-                } else {
-                    GlobalData.stationTel.set(body.phoneNumber)
-                    it.resume(DFSResult.success(body))
-                }
-            }
-
-            override fun onFailure(call: Call<ResultDeviceSessionInfo>, t: Throwable) {
-                DFSLog.e(t)
-                it.resume(DFSResult.fail(R.string.return_data_error))
-            }
-        })
-    }
-
     /**
      * 查询配置信息
      */
@@ -324,21 +336,6 @@ object SystemApi {
         })
     }
 
-    /**
-     * 获取一些油站信息(LOGO)
-     */
-    private suspend fun stationLogo(): DFSResult<String> {
-        val resultConfig = config(WayneApiConfig.CONFIG_LOGO)
-        if (resultConfig.success && resultConfig.data!!.id.isNotEmpty()) {
-            val resultFile = file(resultConfig.data.id)
-            if (resultFile.success) {
-                GlobalData.stationLogoFileName.set(resultFile.data!!)
-                return DFSResult.success(resultFile.data)
-            }
-        }
-        return DFSResult.fail(R.string.req_fail)
-    }
-
     /**
      * 在设置中修改后需要重新生成 baseUrl
      */

+ 5 - 4
app/src/main/java/com/doverfuelingsolutions/issp/data/GlobalData.kt

@@ -1,10 +1,9 @@
 package com.doverfuelingsolutions.issp.data
 
 import com.doverfuelingsolutions.issp.utils.DeviceUtil
+import com.doverfuelingsolutions.issp.utils.TimeUtil
+import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.doverfuelingsolutions.issp.utils.sp.SPKeys
-import com.doverfuelingsolutions.issp.utils.sp.SPUtil
-import com.google.gson.reflect.TypeToken
-import com.wayne.www.waynelib.fdc.message.DeviceClass
 
 /**
  * 这个工具是试验性的东西,主要目的是:对于轻量数据,就不需要过于频繁地读取,存内存即可
@@ -28,6 +27,8 @@ object GlobalData {
     val paymentName = GlobalDataProvider("", SPKeys.PAYMENT_NAME)
 
     fun init() {
-        isLogin = System.currentTimeMillis() < accessTokenExpire.get()
+        val localTokenExpireMoment = accessTokenExpire.get()
+        DFSLog.d("loginToken will expire at ${ TimeUtil.dateFormat(localTokenExpireMoment) }")
+        isLogin = System.currentTimeMillis() < localTokenExpireMoment
     }
 }

+ 16 - 0
app/src/main/java/com/doverfuelingsolutions/issp/utils/TimeUtil.kt

@@ -0,0 +1,16 @@
+package com.doverfuelingsolutions.issp.utils
+
+import com.doverfuelingsolutions.issp.R
+import java.text.SimpleDateFormat
+import java.util.*
+
+object TimeUtil {
+
+    private val defaultFormatter: SimpleDateFormat by lazyOf(SimpleDateFormat(StringUtil.get(R.string.format_date), Locale.CHINA))
+
+    private fun dateFormat(date: Date) = defaultFormatter.format(date)
+    private fun dateFormat(date: Date, format: String) = SimpleDateFormat(format, Locale.CHINA).format(date)
+
+    fun dateFormat(mills: Long): String = defaultFormatter.format(Date(mills))
+    private fun dateFormat(mills: Long, format: String) = SimpleDateFormat(format, Locale.CHINA).format(Date(mills))
+}

+ 26 - 0
app/src/main/java/com/doverfuelingsolutions/issp/view/LoginActivity.kt

@@ -1,5 +1,7 @@
 package com.doverfuelingsolutions.issp.view
 
+import android.content.Context
+import android.content.Intent
 import androidx.appcompat.app.AppCompatActivity
 import android.os.Bundle
 import android.view.View
@@ -14,12 +16,19 @@ import com.doverfuelingsolutions.issp.data.GlobalData
 import com.doverfuelingsolutions.issp.databinding.ActivityLoginBinding
 import com.doverfuelingsolutions.issp.utils.PermissionUtil
 import com.doverfuelingsolutions.issp.utils.StringUtil
+import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.google.android.material.snackbar.Snackbar
 import kotlinx.coroutines.launch
 
 class LoginActivity : AppCompatActivity(),
     View.OnClickListener {
 
+    companion object {
+        fun start(context: Context) {
+            Intent(context, LoginActivity::class.java).let { context.startActivity(it) }
+        }
+    }
+
     private val loginViewModel: LoginViewModel by viewModels()
     private val binding: ActivityLoginBinding by lazy { DataBindingUtil.setContentView(this, R.layout.activity_login) }
     private val snackbar: Snackbar by lazy {
@@ -50,6 +59,10 @@ class LoginActivity : AppCompatActivity(),
         }
     }
 
+    override fun onBackPressed() {
+        if (loginViewModel.submitting.value == false) super.onBackPressed()
+    }
+
     private fun submit() {
         if (loginViewModel.submitting.value == true) return
 
@@ -64,6 +77,19 @@ class LoginActivity : AppCompatActivity(),
                     if (!result.success) snackbar.setText(StringUtil.get(R.string.login_fail_reason, result.message)).show()
                     else {
                         GlobalData.isLogin = true
+                        // 油站号码
+                        val resultPhone = SystemApi.sessionInfo()
+                        if (resultPhone.success && resultPhone.data?.phoneNumber?.isBlank() == false) {
+                            DFSLog.d("station phone: ${resultPhone.data.phoneNumber}")
+                            GlobalData.stationTel.set(resultPhone.data.phoneNumber)
+                        }
+                        // 油站 LOGO
+                        val resultLogo = SystemApi.stationLogo()
+                        if (resultLogo.success && resultLogo.data != null) {
+                            DFSLog.d("station logo file: ${resultLogo.data}")
+                            GlobalData.stationLogoFileName.set(resultLogo.data)
+                        }
+
                         startMain()
                     }
                 }

+ 3 - 20
app/src/main/java/com/doverfuelingsolutions/issp/view/MainActivity.kt

@@ -58,7 +58,6 @@ class MainActivity : AppCompatActivity(),
         binding.mainViewModel = mainViewModel
         initView()
         initFusion()
-        refreshToken()
     }
 
     override fun onResume() {
@@ -195,25 +194,9 @@ class MainActivity : AppCompatActivity(),
         }
     }
 
-    // confirm 测试刷新是否有效
-    private fun refreshToken() {
-        // FIXME 如果当前正在进行支付,如果刷新失败则会导致支付中断的问题
-        lifecycleScope.launchWhenResumed {
-            val duration = GlobalData.accessTokenExpire.get() - System.currentTimeMillis()
-            repeat(99999) {
-                delay(duration)
-                val result = SystemApi.login(GlobalData.accountName.get(), GlobalData.password.get())
-                if (!result.success) {
-                    this@MainActivity.startActivity(
-                        Intent(
-                            this@MainActivity,
-                            LoginActivity::class.java
-                        )
-                    )
-                    finish()
-                }
-            }
-        }
+    fun backToLogin() {
+        LoginActivity.start(this)
+        finish()
     }
 
     class MainViewModel : ViewModel() {

+ 2 - 1
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentNozzle.kt

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.lifecycleScope
 import androidx.recyclerview.widget.GridLayoutManager
 import com.doverfuelingsolutions.issp.R
 import com.doverfuelingsolutions.issp.api.entity.PumpInfo
@@ -58,7 +59,7 @@ class FragmentNozzle private constructor(private val pumpList: List<PumpInfo>) :
     }
 
     private fun handleNozzleRefresh() {
-        GlobalScope.launch(Dispatchers.Main) {
+        lifecycleScope.launch {
             val resultNozzles = FusionManager.requestPumpInfo()
             if (resultNozzles.success && resultNozzles.data != null) {
                 if (FusionManager.pumpList.isNotEmpty()) FusionManager.pumpList.clear()

+ 76 - 34
app/src/main/java/com/doverfuelingsolutions/issp/view/fragment/FragmentSelect.kt

@@ -1,5 +1,6 @@
 package com.doverfuelingsolutions.issp.view.fragment
 
+import android.content.Context
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -7,20 +8,20 @@ import android.view.ViewGroup
 import androidx.databinding.DataBindingUtil
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.viewModels
+import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import androidx.lifecycle.lifecycleScope
 import com.doverfuelingsolutions.issp.DFSApplication
 import com.doverfuelingsolutions.issp.R
+import com.doverfuelingsolutions.issp.api.SystemApi
 import com.doverfuelingsolutions.issp.data.GlobalData
 import com.doverfuelingsolutions.issp.databinding.FragmentSearchTypeBinding
 import com.doverfuelingsolutions.issp.fusion.FusionManager
 import com.doverfuelingsolutions.issp.utils.log.DFSLog
 import com.doverfuelingsolutions.issp.utils.sp.SPUtil
 import com.doverfuelingsolutions.issp.view.MainActivity
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.*
 
 class FragmentSelect : Fragment(),
     View.OnClickListener {
@@ -28,6 +29,8 @@ class FragmentSelect : Fragment(),
     private lateinit var binding: FragmentSearchTypeBinding
     private val viewModel: SearchTypeViewModel by viewModels()
 
+    private var job: Job? = null
+
     override fun onCreateView(
         inflater: LayoutInflater,
         container: ViewGroup?,
@@ -49,16 +52,51 @@ class FragmentSelect : Fragment(),
         handleUnsolvedOrder()
     }
 
+    override fun onStart() {
+        super.onStart()
+
+        refreshLoginToken()
+    }
+
+    override fun onStop() {
+        super.onStop()
+
+        job?.let {
+            if (it.isActive) it.cancel()
+        }
+    }
+
+    override fun onClick(v: View?) {
+        when (v) {
+            binding.chooseNozzle -> {
+                (activity as MainActivity).setFragment(
+                    FragmentNozzle.build(FusionManager.pumpList),
+                    true
+                )
+            }
+            // TODO 油品选择
+            binding.chooseOil -> {
+                (activity as MainActivity).setFragment(
+                    FragmentNozzle.build(FusionManager.pumpList),
+                    true
+                )
+            }
+        }
+    }
+
+    // TODO 当本地未处理订单无法按预期处理时(如本地锁的单,但在服务器上被其他渠道解锁了,造成解锁失败;消单同理;)
     /* 未处理的订单 */
     private fun handleUnsolvedOrder() {
-        DFSLog.i("「未解锁,未消」订单批处理")
+        DFSLog.i("try handle all locked, uncleared orders created by this device")
         GlobalScope.launch(Dispatchers.IO) {
+            val lockList = SPUtil.getLockOrderList()
+            val unclearedList = SPUtil.getUnclearedOrderList()
+            DFSLog.d("size of locked orders: ${lockList.size}, uncleared orders: ${unclearedList.size}")
             // 解锁订单
-            SPUtil.getLockOrderList().let { lockList ->
-                DFSLog.d("未解锁订单数:${lockList.size}")
-                if (lockList.isEmpty()) return@let
+            lockList.let { list ->
+                if (list.isEmpty()) return@let
 
-                val deferredList = lockList.map { item ->
+                val deferredList = list.map { item ->
                     val info = item.split("-")
                     val pumpId = info[0].toInt()
                     val transactionNo = info[1]
@@ -69,23 +107,22 @@ class FragmentSelect : Fragment(),
                 }
                 val resultList = deferredList.map { item -> item.await() }
                 val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "自动解锁订单「${lockList[index]}」"
+                    val tip = "unlock order「${list[index]}」"
                     if (result.success) {
-                        DFSLog.d("$tip 成功")
+                        DFSLog.d("$tip succeeded")
                         null
                     } else {
-                        DFSLog.d("$tip 失败")
-                        lockList[index]
+                        DFSLog.d("$tip failed")
+                        list[index]
                     }
                 }.filterNotNull()
                 SPUtil.replaceLockOrderList(remainLockList)
             }
             // 消除订单
-            SPUtil.getUnclearedOrderList().let { unclearedList ->
-                DFSLog.d("未消订单数:${unclearedList.size}")
-                if (unclearedList.isEmpty()) return@let
+            unclearedList.let { list ->
+                if (list.isEmpty()) return@let
 
-                val deferredList = unclearedList.map { item ->
+                val deferredList = list.map { item ->
                     val info = item.split("-")
                     val pumpId = info[0].toInt()
                     val transactionNo = info[1]
@@ -96,13 +133,13 @@ class FragmentSelect : Fragment(),
                 }
                 val resultList = deferredList.map { item -> item.await() }
                 val remainLockList = resultList.mapIndexed { index, result ->
-                    val tip = "自动消单「${unclearedList[index]}」"
+                    val tip = "clear order「${list[index]}」"
                     if (result.success) {
-                        DFSLog.d("$tip 成功")
+                        DFSLog.d("$tip succeeded")
                         null
                     } else {
-                        DFSLog.d("$tip 失败")
-                        unclearedList[index]
+                        DFSLog.d("$tip failed")
+                        list[index]
                     }
                 }.filterNotNull()
                 SPUtil.replaceUnclearedOrderList(remainLockList)
@@ -110,20 +147,25 @@ class FragmentSelect : Fragment(),
         }
     }
 
-    override fun onClick(v: View?) {
-        when (v) {
-            binding.chooseNozzle -> {
-                (activity as MainActivity).setFragment(
-                    FragmentNozzle.build(FusionManager.pumpList),
-                    true
-                )
+    /* 刷新登录状态 */
+    private fun refreshLoginToken() {
+        // 登录有效时长两小时,为了避免在操作过程出现接口验证失败
+        job = lifecycleScope.launch {
+            // 8min 余留,避免边缘时刻进入流程
+            val expireMoment = GlobalData.accessTokenExpire.get()
+            val delayTime = expireMoment - 480000 - System.currentTimeMillis()
+            if (delayTime > 0) {
+                delay(delayTime)
             }
-            // TODO 油品选择
-            binding.chooseOil -> {
-                (activity as MainActivity).setFragment(
-                    FragmentNozzle.build(FusionManager.pumpList),
-                    true
-                )
+            val result = SystemApi.login(GlobalData.accountName.get(), GlobalData.password.get())
+            if (result.success) {
+                DFSLog.d("loginToken refresh succeeded")
+                // 处理持续停留
+                refreshLoginToken()
+            } else {
+                DFSLog.d("loginToken refresh failed: ${result.message}")
+                // 刷新失败返回登录界面
+                (activity as MainActivity).backToLogin()
             }
         }
     }