Skip to content

Commit

Permalink
Merge pull request #471 from RedrockMobile/GuoXR/optimize
Browse files Browse the repository at this point in the history
🐛 课表数据更换为sp存储以解决偶现的课表数据异常
  • Loading branch information
985892345 authored Mar 26, 2024
2 parents 6ac257a + 7e1e1bb commit a1f3574
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ interface ILessonService : IProvider {
* 刷新当前学号的课
* - 上游已主动切换成 io 线程
* - 在得不到这个人课表数据时会抛出异常,比如学号为空串时
*
* @param isForce 如果为 false,则将使用掌邮应用生命周期内的临时缓存,而不再去网络请求
*/
fun refreshLesson(stuNum: String, isForce: Boolean): Single<List<Lesson>>
fun refreshLesson(stuNum: String): Single<List<Lesson>>

/**
* 直接得到当前学号的课
Expand Down Expand Up @@ -77,10 +75,8 @@ interface ILessonService : IProvider {
* - 没登录时发送 emptyList()
* - 没有连接网络并且不允许使用本地缓存时会一直不发送数据给下游
* - 不会抛出异常给下游
*
* @param isForce 是否强制刷新,默认不进行强制刷新,会使用应用生命周期的缓存
*/
fun observeSelfLesson(isForce: Boolean = false): Observable<List<Lesson>>
fun observeSelfLesson(): Observable<List<Lesson>>

/**
* 这里提供 Calendar 与 [hashDay] 互换代码
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.mredrock.cyxbs.course.page.course.bean
import androidx.annotation.WorkerThread
import com.mredrock.cyxbs.course.BuildConfig
import com.mredrock.cyxbs.course.page.course.room.LessonDataBase
import com.mredrock.cyxbs.course.page.course.room.LessonVerEntity
import com.mredrock.cyxbs.lib.utils.extensions.toast

/**
Expand All @@ -28,24 +27,26 @@ sealed interface ILessonVersion {
@WorkerThread
fun judgeVersion(defaultWhenSame: Boolean): Boolean {
// 版本号保存于数据库中
val oldVersion = LessonDataBase.INSTANCE.getLessonVerDao()
.findVersion(num)?.version ?: "0.0.0"
val oldVersion = LessonDataBase.lessonVerDao
.findVersion(num) ?: "0.0.0"
val newVersionList = version.split(".")
val oldVersionList = oldVersion.split(".")
if (newVersionList.size != oldVersionList.size) {
// 不应该出现这种情况,因为版本号规定形式为:0.0.0
// 如果出现,可以认为是远端出现问题,所以就不对本地数据进行更新
if (BuildConfig.DEBUG) {
toast("课表接口 version 字段错误:$version")
toast("课表接口 version 字段错误:$version\n版本号规定形式为:0.0.0")
}
return false
}
for (i in oldVersionList.indices) {
if (newVersionList[i] > oldVersionList[i]) {
LessonDataBase.INSTANCE.getLessonVerDao()
.insertVersion(LessonVerEntity(num, version))
val new = newVersionList[i].toInt()
val old = oldVersionList[i].toInt()
if (new > old) {
LessonDataBase.lessonVerDao
.insertVersion(num, version)
return true
} else if (newVersionList[i] < oldVersionList[i]) {
} else if (new < old) {
return false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import java.util.concurrent.TimeUnit
@Suppress("LiftReturnOrAssignment")
object StuLessonRepository {

private val mStuDB by lazyUnlock { LessonDataBase.INSTANCE.getStuLessonDao() }
private val mStuDB = LessonDataBase.stuLessonDao

// 上一次更新的天数差
private var mLastUpdateDay: Int
Expand All @@ -58,11 +58,9 @@ object StuLessonRepository {
* - 没有连接网络并且不允许使用本地缓存时会一直不发送数据给下游
* - 不会抛出异常给下游
*
* @param isForce 是否强制获取数据,如果不强制的话,将使用临时缓存
* @param isToast 是否提示课表正在使用缓存数据
*/
fun observeSelfLesson(
isForce: Boolean = false,
isToast: Boolean = false,
): Observable<List<StuLessonEntity>> {
return IAccountService::class.impl
Expand All @@ -72,12 +70,14 @@ object StuLessonRepository {
.switchMap { value ->
// 使用 switchMap 可以停止之前学号的订阅
value.nullUnless(Observable.just(emptyList())) { stuNum ->
android.util.Log.d("ggg", "${Exception().stackTrace[0].run { "$fileName:$lineNumber" }} -> " +
"observeSelfLesson: stuNum = $stuNum")
if (ILessonService.isUseLocalSaveLesson) {
LessonDataBase.INSTANCE.getStuLessonDao()
LessonDataBase.stuLessonDao
.observeLesson(stuNum)
.doOnSubscribe {
// 在开始订阅时异步请求一次云端数据,所以下游会先拿到本地数据库中的数据,如果远端数据更新了,整个流会再次通知
refreshLesson(stuNum, isForce).doOnError {
refreshLesson(stuNum).doOnError {
if (isToast) {
val lastUpdateDay = mLastUpdateDay
if (lastUpdateDay < 1) {
Expand All @@ -99,7 +99,7 @@ object StuLessonRepository {
emit(Unit)
}.asObservable()
.switchMap {
refreshLesson(stuNum, isForce)
refreshLesson(stuNum)
.onErrorComplete() // 网络请求的异常全部吞掉
.toObservable()
}
Expand All @@ -108,24 +108,11 @@ object StuLessonRepository {
}
}

/**
* 掌邮应用生命周期内的缓存
*
* 因为 jwzx 不允许我们本地保存课表数据,但关联人那里有显示和不显示的逻辑,会重复获取课表数据,
* 但存在用户打开网络后关闭网络的情况,所以需要单独做一层缓存
*/
private val mCacheForProcessLifecycle = ArrayMap<String, List<StuLessonEntity>>()

/**
* 刷新某人的课,会抛出网络异常
* @param isForce 是否强制刷新,如果不采取强制刷新,则会优先从 [mCacheForProcessLifecycle] 查找数据
*/
fun refreshLesson(stuNum: String, isForce: Boolean = false): Single<List<StuLessonEntity>> {
fun refreshLesson(stuNum: String): Single<List<StuLessonEntity>> {
if (stuNum.isBlank()) return Single.error(IllegalArgumentException("学号不能为空!"))
if (!isForce) {
val cache = mCacheForProcessLifecycle[stuNum]
if (cache != null) return Single.just(cache)
}
return CourseApiServices::class.api
.getStuLesson(stuNum)
.map { bean ->
Expand All @@ -135,8 +122,6 @@ object StuLessonRepository {
// 检查网络请求数据是否出错
bean.throwApiExceptionIfFail()
httpFromStuSuccess(bean)
}.doOnSuccess {
mCacheForProcessLifecycle[stuNum] = it
}.subscribeOn(Schedulers.io())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
@Suppress("LiftReturnOrAssignment")
object TeaLessonRepository {

private val mTeaDB by lazyUnlock { LessonDataBase.INSTANCE.getTeaLessonDao() }
private val mTeaDB = LessonDataBase.teaLessonDao

fun getTeaLesson(teaNum: String): Single<List<TeaLessonEntity>> {
return CourseApiServices::class.api
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
package com.mredrock.cyxbs.course.page.course.room

import androidx.room.*
import android.content.Context
import androidx.core.content.edit
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.mredrock.cyxbs.lib.utils.extensions.appContext
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.subjects.BehaviorSubject
import java.util.concurrent.ConcurrentHashMap

/**
* ...
* # 更新日志
* 2024-3-26:
* 课程出现错乱,导出数据库后发现,Room 中数据源有问题,
* 但是导入有问题的数据库却又能正常更新,所以未排查到问题原因,
*
* 因为课程本来就是直接用后端的数据覆盖本地的,所以这里在保持接口不变的情况下改成了 sp
*
*
*
* @author 985892345 (Guo Xiangrui)
* @email [email protected]
* @date 2022/5/1 21:12
*/
@Database(entities = [StuLessonEntity::class, TeaLessonEntity::class, LessonVerEntity::class], version = 1)
abstract class LessonDataBase : RoomDatabase() {
abstract fun getStuLessonDao(): StuLessonDao
abstract fun getTeaLessonDao(): TeaLessonDao
abstract fun getLessonVerDao(): LessonVerDao

companion object {
val INSTANCE by lazy {
Room.databaseBuilder(
appContext,
LessonDataBase::class.java,
"course_lesson_db"
).fallbackToDestructiveMigration().build()
}
object LessonDataBase {
val stuLessonDao: StuLessonDao by lazy {
StuLessonDao()
}
val teaLessonDao: TeaLessonDao by lazy {
TeaLessonDao()
}
val lessonVerDao: LessonVerDao by lazy {
LessonVerDao()
}
}

Expand All @@ -46,21 +54,8 @@ sealed interface ILessonEntity {
val weekModel: String

val num: String // 学号或者教师工号 (不建议将次设置为数据库字段)

class WeekConverter {
@TypeConverter
fun listToString(list: List<Int>): String {
return list.joinToString("*&*")
}
@TypeConverter
fun stringToList(string: String): List<Int> {
return string.split("*&*").map { it.toInt() }
}
}
}

@Entity(tableName = "stu_lesson", indices = [Index("stuNum")])
@TypeConverters(ILessonEntity.WeekConverter::class)
data class StuLessonEntity(
val stuNum: String,
override val beginLesson: Int,
Expand All @@ -80,40 +75,39 @@ data class StuLessonEntity(
override val weekEnd: Int,
override val weekModel: String,
) : ILessonEntity {

@PrimaryKey(autoGenerate = true)
var id: Int = 0

override val num: String
get() = stuNum
}

@Dao
abstract class StuLessonDao {

@Query("SELECT * FROM stu_lesson WHERE stuNum = :stuNum")
abstract fun observeLesson(stuNum: String): Observable<List<StuLessonEntity>>

@Query("SELECT * FROM stu_lesson WHERE stuNum = :stuNum")
abstract fun getLesson(stuNum: String): List<StuLessonEntity>
private val Gson = Gson()

class StuLessonDao {

private val stuLessonSp = appContext.getSharedPreferences("stu_lesson", Context.MODE_PRIVATE)

private val observerMap = ConcurrentHashMap<String, BehaviorSubject<List<StuLessonEntity>>>()

@Query("DELETE FROM stu_lesson WHERE stuNum = :stuNum")
protected abstract fun deleteLesson(stuNum: String)
fun observeLesson(stuNum: String): Observable<List<StuLessonEntity>> {
return observerMap.getOrPut(stuNum) {
val list = getLesson(stuNum)
BehaviorSubject.createDefault(list)
}
}

@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract fun insertLesson(lesson: List<StuLessonEntity>)
fun getLesson(stuNum: String): List<StuLessonEntity> {
return stuLessonSp.getString(stuNum, null)?.let {
Gson.fromJson(it, object : TypeToken<List<StuLessonEntity>>() {}.type)
} ?: emptyList()
}

@Transaction
open fun resetData(stuNum: String, lesson: List<StuLessonEntity>) {
deleteLesson(stuNum)
insertLesson(lesson)
fun resetData(stuNum: String, lesson: List<StuLessonEntity>) {
stuLessonSp.edit {
putString(stuNum, Gson.toJson(lesson))
}
observerMap[stuNum]?.onNext(lesson)
}
}



@Entity(tableName = "tea_lesson", indices = [Index("teaNum")])
@TypeConverters(TeaLessonEntity.ClassNumberConverter::class, ILessonEntity.WeekConverter::class)
data class TeaLessonEntity(
val teaNum: String,
override val beginLesson: Int,
Expand All @@ -134,56 +128,43 @@ data class TeaLessonEntity(
override val weekModel: String,
val classNumber: List<String>,
) : ILessonEntity {

@PrimaryKey(autoGenerate = true)
var id: Int = 0

override val num: String
get() = teaNum

class ClassNumberConverter {
@TypeConverter
fun listToString(list: List<String>): String {
return list.joinToString("*&*")
}
@TypeConverter
fun stringToList(string: String): List<String> {
return string.split("*&*")
}

class TeaLessonDao {

private val teaLessonSp = appContext.getSharedPreferences("tea_lesson", Context.MODE_PRIVATE)

fun getLesson(teaNum: String): List<TeaLessonEntity> {
return teaLessonSp.getString(teaNum, null)?.let {
Gson.fromJson(it, object : TypeToken<List<TeaLessonEntity>>() {}.type)
} ?: emptyList()
}


fun resetData(teaNum: String, lesson: List<TeaLessonEntity>) {
teaLessonSp.edit {
putString(teaNum, Gson.toJson(lesson))
}
}
}

@Dao
abstract class TeaLessonDao {
@Query("SELECT * FROM tea_lesson WHERE teaNum = :teaNum")
abstract fun getLesson(teaNum: String): List<TeaLessonEntity>

@Query("DELETE FROM tea_lesson WHERE teaNum = :teaNum")
protected abstract fun deleteLesson(teaNum: String)

@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract fun insertLesson(lesson: List<TeaLessonEntity>)

@Transaction
open fun resetData(teaNum: String, lesson: List<TeaLessonEntity>) {
deleteLesson(teaNum)
insertLesson(lesson)
class LessonVerDao {

private val lessonVersionSp = appContext.getSharedPreferences("lesson_version", Context.MODE_PRIVATE)

fun findVersion(num: String): String? {
return lessonVersionSp.getString(num, null)
}
}

@Entity(tableName = "lesson_version")
data class LessonVerEntity(
@PrimaryKey
val num: String,
val version: String
)
fun insertVersion(num: String, version: String) {
lessonVersionSp.edit {
putString(num, version)
}
}

@Dao
interface LessonVerDao {

@Query("SELECT * FROM lesson_version WHERE num = :num")
fun findVersion(num: String): LessonVerEntity?

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertVersion(lessonVer: LessonVerEntity)
fun clear() {
lessonVersionSp.edit { clear() }
}
}
Loading

0 comments on commit a1f3574

Please sign in to comment.