feat: 实现小程序离线巡检与可靠同步

This commit is contained in:
weijuesen
2026-08-14 09:03:48 +08:00
parent 6b222abb7b
commit 69ef5a0704
19 changed files with 543 additions and 19 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ import (
)
// CurrentSchemaVersion 是当前后端代码期望的迁移版本。
const CurrentSchemaVersion = "6"
const CurrentSchemaVersion = "7"
// RunMigrations 使用嵌入式 SQL 迁移文件将数据库升级到最新版本。
func RunMigrations(db *gorm.DB) error {
@@ -116,4 +116,8 @@ func TestEmbeddedMigrationsIncludeBaseline(t *testing.T) {
if err != nil || next != 6 {
t.Fatalf("expected biosecurity migration version 6, got %d (err %v)", next, err)
}
next, err = driver.Next(next)
if err != nil || next != 7 {
t.Fatalf("expected inspection idempotency migration version 7, got %d (err %v)", next, err)
}
}
+6 -5
View File
@@ -70,10 +70,11 @@ func createInspection(db *gorm.DB, s3 *service.S3Service, ai *service.AIClient,
return func(c *gin.Context) {
idemKey := strings.TrimSpace(c.GetHeader("Idempotency-Key"))
roomID := strings.TrimSpace(c.PostForm("roomId"))
userID := currentUserID(c)
if idemKey != "" {
if idemKey != "" && userID != nil {
var exist model.InspectionRecord
if db.Where("idempotency_key = ?", idemKey).First(&exist).Error == nil {
if db.Where("user_id = ? AND idempotency_key = ?", *userID, idemKey).First(&exist).Error == nil {
c.JSON(http.StatusOK, exist)
return
}
@@ -113,7 +114,7 @@ func createInspection(db *gorm.DB, s3 *service.S3Service, ai *service.AIClient,
imageURL := s3.Endpoint() + "/" + bucket + "/" + key
rec := model.InspectionRecord{
UserID: currentUserID(c),
UserID: userID,
ImageURL: &imageURL,
AIStatus: "done",
}
@@ -221,9 +222,9 @@ func createInspection(db *gorm.DB, s3 *service.S3Service, ai *service.AIClient,
})
if txErr != nil {
// 并发幂等:唯一索引冲突时返回已有记录
if idemKey != "" {
if idemKey != "" && userID != nil {
var exist model.InspectionRecord
if db.Where("idempotency_key = ?", idemKey).First(&exist).Error == nil {
if db.Where("user_id = ? AND idempotency_key = ?", *userID, idemKey).First(&exist).Error == nil {
c.JSON(http.StatusOK, exist)
return
}
+1 -1
View File
@@ -18,7 +18,7 @@ type InspectionRecord struct {
ModelVersion *string `gorm:"column:model_version;size:64" json:"modelVersion,omitempty"`
IsMock *bool `gorm:"column:is_mock;default:false" json:"isMock,omitempty"`
AIStatus string `gorm:"column:ai_status;size:16;default:done" json:"aiStatus"`
IdempotencyKey *string `gorm:"column:idempotency_key;size:128;uniqueIndex" json:"idempotencyKey,omitempty"`
IdempotencyKey *string `gorm:"column:idempotency_key;size:128" json:"idempotencyKey,omitempty"`
CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"`
UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"`
RoomName *string `gorm:"-" json:"roomName,omitempty"`
@@ -0,0 +1,16 @@
DROP INDEX IF EXISTS idx_inspection_records_user_idempotency;
-- 恢复全局唯一索引前,先处理跨用户重复,保留所有记录。
UPDATE inspection_records AS r
SET idempotency_key = r.idempotency_key || '#' || r.id
WHERE r.idempotency_key IS NOT NULL
AND EXISTS (
SELECT 1
FROM inspection_records AS x
WHERE x.idempotency_key = r.idempotency_key
AND x.id <> r.id
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_inspection_records_idempotency_key
ON inspection_records (idempotency_key)
WHERE idempotency_key IS NOT NULL;
@@ -0,0 +1,17 @@
-- 保留全部历史记录,只对同用户重复键追加记录 ID,避免迁移删除或丢失数据。
UPDATE inspection_records AS r
SET idempotency_key = r.idempotency_key || '#' || r.id
WHERE r.idempotency_key IS NOT NULL
AND EXISTS (
SELECT 1
FROM inspection_records AS x
WHERE x.user_id IS NOT DISTINCT FROM r.user_id
AND x.idempotency_key = r.idempotency_key
AND x.id <> r.id
);
DROP INDEX IF EXISTS idx_inspection_records_idempotency_key;
CREATE UNIQUE INDEX IF NOT EXISTS idx_inspection_records_user_idempotency
ON inspection_records (user_id, idempotency_key)
WHERE idempotency_key IS NOT NULL;