Skip to content

Commit 0363d40

Browse files
committed
同步优化
1 parent f202cc6 commit 0363d40

20 files changed

Lines changed: 1005 additions & 315 deletions

client/app/src/main/java/com/hao/heji/data/Status.kt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,8 @@ object Status {
99
/** 已删除(软删除) */
1010
const val DELETED = 1
1111

12-
/** 未同步(新建) */
12+
/** 未同步 */
1313
const val NOT_SYNCED = 0
1414
/** 已同步 */
1515
const val SYNCED = 1
16-
/** 已修改未同步 */
17-
const val UPDATED = 2
18-
/** 同步中(已发送,等待ACK) */
19-
const val SYNCING = 3
2016
}

client/app/src/main/java/com/hao/heji/data/db/BillDao.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ interface BillDao {
6262
@Query("SELECT count(*) FROM bill WHERE book_id =:bookId")
6363
fun countByBookId(bookId: String): Int
6464

65-
@Query("SELECT * FROM bill WHERE book_id=:bookId AND synced NOT IN (1, 3) LIMIT 100")
65+
@Query("SELECT * FROM bill WHERE book_id=:bookId AND synced != 1 LIMIT 100")
6666
fun flowNotSynced(bookId: String): Flow<List<Bill>>
6767

6868
/**

client/app/src/main/java/com/hao/heji/data/db/BookDao.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ interface BookDao {
3131
@Query("SELECT * FROM book WHERE deleted!=1 ORDER BY book_id")
3232
fun allBooks(): Flow<MutableList<Book>>
3333

34-
@Query("SELECT * FROM book WHERE (crt_user_id=:uid or book_id=:bid) AND synced NOT IN (1, 3)")
34+
@Query("SELECT * FROM book WHERE (crt_user_id=:uid or book_id=:bid) AND synced != 1")
3535
fun flowNotSynced(uid:String,bid:String):Flow<MutableList<Book>>
3636

37+
@Query("SELECT * FROM book WHERE crt_user_id=:uid AND synced != 1 AND deleted != 1")
38+
suspend fun listNotSynced(uid: String): List<Book>
39+
3740
@Query("SELECT count(0) FROM book")
3841
fun count(): Int
3942

client/app/src/main/java/com/hao/heji/data/db/dto/BillD2O.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ data class BillTotal(val money: BigDecimal, val time: String, val type: Int)
77
/**
88
* 收入支出
99
*/
10-
class Income {
11-
var income: BigDecimal = BigDecimal.ZERO
12-
var expenditure: BigDecimal = BigDecimal.ZERO
13-
}
10+
data class Income(
11+
var income: BigDecimal = BigDecimal.ZERO,
12+
var expenditure: BigDecimal = BigDecimal.ZERO,
13+
)
1414

1515

1616
/**

client/app/src/main/java/com/hao/heji/network/ApiServer.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.hao.heji.network
22

3+
import com.hao.heji.data.db.Bill
34
import com.hao.heji.data.db.Book
45
import com.hao.heji.network.request.CategoryEntity
56
import com.hao.heji.network.response.ImageEntity
67
import com.hao.heji.ui.user.register.RegisterUser
8+
import kotlinx.serialization.json.JsonElement
79
import okhttp3.MultipartBody
810
import okhttp3.ResponseBody
911
import retrofit2.Call
@@ -27,7 +29,7 @@ interface ApiServer {
2729

2830
//----------------------BOOK---------------------------//
2931
@POST("/api/v1/book/")
30-
fun createBook(@Body book: Book): Call<BaseResponse<String>>
32+
fun createBook(@Body book: Book): Call<BaseResponse<JsonElement>>
3133

3234
@GET("/api/v1/book/{id}")
3335
fun findBook(@Path("id") bookId: String): Call<BaseResponse<Book>>
@@ -40,21 +42,37 @@ interface ApiServer {
4042

4143
@PUT("/api/v1/book/{id}")
4244
fun updateBook(@Path("id") bookId: String,
43-
@Body body: Map<String, String>): Call<BaseResponse<String>>
45+
@Body body: Map<String, String>): Call<BaseResponse<JsonElement>>
4446

4547
@DELETE("/api/v1/book/{id}")
46-
fun deleteBook(@Path("id") bookId: String): Call<BaseResponse<String>>
48+
fun deleteBook(@Path("id") bookId: String): Call<BaseResponse<JsonElement>>
4749

4850
@GET("/api/v1/book/")
4951
fun bookList(): Call<BaseResponse<MutableList<Book>>>
5052

5153
//----------------------BILL---------------------------//
54+
@POST("/api/v1/bill/")
55+
fun createBill(@Body bill: Bill): Call<BaseResponse<JsonElement>>
56+
57+
@PUT("/api/v1/bill/{id}")
58+
fun updateBill(@Path("id") billId: String, @Body bill: Bill): Call<BaseResponse<JsonElement>>
59+
60+
@DELETE("/api/v1/bill/{id}")
61+
fun deleteBill(@Path("id") billId: String): Call<BaseResponse<JsonElement>>
62+
5263
@Streaming
5364
@GET("/api/v1/bill/export")
5465
fun exportBills(@Query("book_id") bookId: String,
5566
@Query("year") year: String?,
5667
@Query("month") month: String?): Call<ResponseBody>
5768

69+
//----------------------SYNC---------------------------//
70+
@GET("/api/v1/sync/changes")
71+
fun syncChanges(
72+
@Query("since") since: Long,
73+
@Query("limit") limit: Int = 100,
74+
): Call<BaseResponse<SyncChangesResponse>>
75+
5876
//----------------------CATEGORY---------------------------//
5977
@POST("/api/v1/category/batch")
6078
fun addCategories(@Body categories: List<CategoryEntity>): Call<BaseResponse<String>>

client/app/src/main/java/com/hao/heji/network/HttpManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.hao.heji.network
22

33
import com.hao.heji.config.Config
4+
import com.hao.heji.data.db.Bill
45
import com.hao.heji.data.db.Book
56
import com.hao.heji.network.request.CategoryEntity
67
import com.hao.heji.ui.user.register.RegisterUser
@@ -44,6 +45,15 @@ class HttpManager {
4445

4546
suspend fun joinBook(code: String) = server().joinBook(code).await()
4647

48+
// Bill CRUD
49+
suspend fun createBill(bill: Bill) = server().createBill(bill).await()
50+
suspend fun updateBill(billId: String, bill: Bill) = server().updateBill(billId, bill).await()
51+
suspend fun deleteBill(billId: String) = server().deleteBill(billId).await()
52+
53+
// Sync
54+
suspend fun syncChanges(since: Long, limit: Int = 100) =
55+
server().syncChanges(since, limit).await()
56+
4757
suspend fun imageUpload(
4858
@Part part: MultipartBody.Part,
4959
_id: String,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.hao.heji.network
2+
3+
import com.hao.heji.data.db.Bill
4+
import com.hao.heji.data.db.Book
5+
import kotlinx.serialization.SerialName
6+
import kotlinx.serialization.Serializable
7+
8+
@Serializable
9+
data class SyncChangesResponse(
10+
@SerialName("books") val books: List<Book> = emptyList(),
11+
@SerialName("bills") val bills: List<Bill> = emptyList(),
12+
@SerialName("has_more") val hasMore: Boolean = false,
13+
@SerialName("next_since") val nextSince: Long = 0,
14+
)

client/app/src/main/java/com/hao/heji/sync/MqttSyncClient.kt

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions
1616
import org.eclipse.paho.client.mqttv3.MqttMessage
1717
import com.github.shamil.Xid
1818

19+
/**
20+
* MQTT 客户端 (v2 — 纯订阅模式)
21+
* 只订阅服务端推送的通知主题 heji/notify/{userId}/,不再发布任何消息。
22+
* 数据同步通过 HTTP API 完成(SyncTrigger 独立运行)。
23+
*/
1924
class MqttSyncClient {
2025
private var mqttClient: MqttAndroidClient? = null
2126
private var brokerUrl = ""
@@ -30,11 +35,12 @@ class MqttSyncClient {
3035
private val syncScope = CoroutineScope(Dispatchers.Main + syncJob)
3136

3237
private val syncReceiver by lazy { SyncReceiver() }
33-
private val syncTrigger by lazy { SyncTrigger(syncScope) }
3438

3539
companion object {
36-
private const val TOPIC_BOOK_SYNC = "heji/book/%s/sync"
37-
private const val TOPIC_USER_SYNC = "heji/user/%s/sync"
40+
// 下行通知主题:服务端 → 客户端
41+
private const val TOPIC_NOTIFY_BOOK = "heji/notify/%s/book"
42+
private const val TOPIC_NOTIFY_BILL = "heji/notify/%s/bill"
43+
private const val TOPIC_NOTIFY_IMAGE = "heji/notify/%s/image"
3844

3945
@Volatile
4046
private var instance: MqttSyncClient? = null
@@ -50,24 +56,6 @@ class MqttSyncClient {
5056
}
5157
}
5258

53-
fun send(message: SyncMessage): Boolean {
54-
val mqttMessage = MqttMessage().apply {
55-
payload = message.toJson().toByteArray()
56-
qos = 1
57-
isRetained = false
58-
}
59-
return try {
60-
val bookId = Config.book.id
61-
val topic = String.format(TOPIC_BOOK_SYNC, bookId)
62-
mqttClient?.publish(topic, mqttMessage)
63-
LogUtils.d("MQTT published to $topic")
64-
true
65-
} catch (e: Exception) {
66-
LogUtils.e("MQTT publish failed", e)
67-
false
68-
}
69-
}
70-
7159
fun close() {
7260
LogUtils.d("close mqtt")
7361
try {
@@ -101,20 +89,18 @@ class MqttSyncClient {
10189
status = Status.CONNECTED
10290
subscribeTopics()
10391
syncReceiver.register()
104-
syncTrigger.register()
10592
}
10693

10794
override fun connectionLost(cause: Throwable?) {
10895
LogUtils.w("MQTT connection lost: ${cause?.message}")
10996
status = Status.DISCONNECTED
11097
syncReceiver.unregister()
111-
syncTrigger.unregister()
11298
}
11399

114100
override fun messageArrived(topic: String?, message: MqttMessage?) {
115101
message?.let {
116102
val text = String(it.payload)
117-
LogUtils.d("MQTT message from $topic: $text")
103+
LogUtils.d("MQTT notify from $topic: $text")
118104
syncScope.launch(Dispatchers.IO) {
119105
syncReceiver.onReceiver(text)
120106
}
@@ -152,16 +138,16 @@ class MqttSyncClient {
152138
private fun subscribeTopics() {
153139
try {
154140
val userId = Config.user.id
155-
val userTopic = String.format(TOPIC_USER_SYNC, userId)
156-
mqttClient?.subscribe(userTopic, 1)
157-
LogUtils.d("MQTT subscribed: $userTopic")
158-
159-
val bookId = Config.book.id
160-
val bookTopic = String.format(TOPIC_BOOK_SYNC, bookId)
141+
val bookTopic = String.format(TOPIC_NOTIFY_BOOK, userId)
142+
val billTopic = String.format(TOPIC_NOTIFY_BILL, userId)
143+
val imageTopic = String.format(TOPIC_NOTIFY_IMAGE, userId)
161144
mqttClient?.subscribe(bookTopic, 1)
162-
LogUtils.d("MQTT subscribed: $bookTopic")
145+
mqttClient?.subscribe(billTopic, 1)
146+
mqttClient?.subscribe(imageTopic, 1)
147+
LogUtils.d("MQTT subscribed: $bookTopic, $billTopic, $imageTopic")
163148
} catch (e: Exception) {
164149
LogUtils.e("MQTT subscribe error", e)
165150
}
166151
}
167152
}
153+

client/app/src/main/java/com/hao/heji/sync/SyncMessage.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,26 @@ package com.hao.heji.sync
22

33
import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
5+
import com.github.shamil.Xid
56

67
@Serializable
78
data class SyncMessage(
9+
@SerialName("id") val id: String = Xid.string(),
810
@SerialName("type") val type: String,
9-
@SerialName("sender_id") val senderId: String,
11+
@SerialName("book_id") val bookId: String,
1012
@SerialName("content") val content: String,
11-
@SerialName("id") val id: String = "",
12-
@SerialName("receiver_ids") val receiverIds: List<String> = emptyList(),
13+
@SerialName("timestamp") val timestamp: Long = System.currentTimeMillis(),
1314
) {
1415
object Type {
1516
const val ADD_BILL = "ADD_BILL"
1617
const val DELETE_BILL = "DELETE_BILL"
1718
const val UPDATE_BILL = "UPDATE_BILL"
18-
const val ADD_BILL_ACK = "ADD_BILL_ACK"
19-
const val DELETE_BILL_ACK = "DELETE_BILL_ACK"
20-
const val UPDATE_BILL_ACK = "UPDATE_BILL_ACK"
2119

2220
const val ADD_BOOK = "ADD_BOOK"
2321
const val DELETE_BOOK = "DELETE_BOOK"
2422
const val UPDATE_BOOK = "UPDATE_BOOK"
25-
const val ADD_BOOK_ACK = "ADD_BOOK_ACK"
26-
const val DELETE_BOOK_ACK = "DELETE_BOOK_ACK"
27-
const val UPDATE_BOOK_ACK = "UPDATE_BOOK_ACK"
23+
24+
const val IMAGE_UPLOADED = "IMAGE_UPLOADED"
25+
const val IMAGE_DELETED = "IMAGE_DELETED"
2826
}
2927
}

client/app/src/main/java/com/hao/heji/sync/SyncService.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,31 @@ import androidx.lifecycle.asLiveData
1010
import com.blankj.utilcode.util.LogUtils
1111
import com.hao.heji.App
1212
import com.hao.heji.config.Config
13+
import kotlinx.coroutines.CoroutineScope
14+
import kotlinx.coroutines.Dispatchers
15+
import kotlinx.coroutines.Job
1316

1417
/**
15-
* 同步服务
16-
* @date 2022/6/20
17-
* @author 锅得铁
18-
* @since v1.0
18+
* 同步服务 (v2)
19+
* - SyncTrigger: 观察本地变更 → HTTP API 上传(独立于 MQTT)
20+
* - MqttSyncClient: 纯订阅服务端推送通知
1921
*/
2022
class SyncService : Service(), Observer<Config> {
2123
private val binder = SyncBinder()
2224
private val mqttClient = MqttSyncClient.getInstance()
2325

26+
private val syncJob = Job()
27+
private val syncScope = CoroutineScope(Dispatchers.IO + syncJob)
28+
private val syncTrigger by lazy { SyncTrigger(syncScope) }
29+
2430
private var networkMonitor: NetworkMonitor? = null
2531
private val configLiveData = App.viewModel.configChange.asLiveData()
2632
override fun onCreate() {
2733
super.onCreate()
2834
LogUtils.d("onCreate")
2935
networkMonitor = NetworkMonitor(this) {
3036
if (it) {
37+
syncTrigger.register()
3138
if (mqttClient.isDisconnected() || mqttClient.isError()) {
3239
connectSync()
3340
}
@@ -49,6 +56,8 @@ class SyncService : Service(), Observer<Config> {
4956

5057
private fun closeSync() {
5158
mqttClient.close()
59+
syncTrigger.unregister()
60+
syncJob.cancel()
5261
}
5362

5463
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

0 commit comments

Comments
 (0)