|
@@ -10,32 +10,43 @@ import (
|
|
|
"server/dao"
|
|
|
"server/logger"
|
|
|
"strings"
|
|
|
+ "sync"
|
|
|
"time"
|
|
|
)
|
|
|
|
|
|
const dataFilePath = "static/data.json"
|
|
|
|
|
|
-// LoadData 从文件中加载 Region 数据。
|
|
|
-func LoadData() ([]dao.Region, error) {
|
|
|
+var InitRegionData []dao.Region
|
|
|
+
|
|
|
+// LoadData1 从文件中加载 Region 数据。
|
|
|
+func LoadData1() {
|
|
|
var regions []dao.Region
|
|
|
|
|
|
// 尝试从主数据文件读取数据
|
|
|
data, err := os.ReadFile(dataFilePath)
|
|
|
if err != nil {
|
|
|
if !os.IsNotExist(err) {
|
|
|
- return nil, fmt.Errorf("读取文件失败: %v", err)
|
|
|
+ logger.Get().Errorf("读取文件失败: %v", err)
|
|
|
+ return
|
|
|
}
|
|
|
// 如果主数据文件不存在,则尝试从备份文件读取
|
|
|
} else {
|
|
|
// 检查文件是否为空
|
|
|
if len(data) > 0 {
|
|
|
if err := json.Unmarshal(data, ®ions); err != nil {
|
|
|
- return nil, fmt.Errorf("解析 JSON 失败 (主文件): %v, 原始数据: %s", err, string(data))
|
|
|
+ logger.Get().Errorf("解析 JSON 失败 (主文件): %v, 原始数据: %s", err, string(data))
|
|
|
+ return
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return regions, nil
|
|
|
+ InitRegionData = regions
|
|
|
+}
|
|
|
+
|
|
|
+// LoadData 从文件中加载 Region 数据。
|
|
|
+func LoadData() ([]dao.Region, error) {
|
|
|
+
|
|
|
+ return InitRegionData, nil
|
|
|
}
|
|
|
|
|
|
func GetOnlineDevices() (devices []dao.Device, err error) {
|
|
@@ -103,6 +114,46 @@ func SaveRegionOnData(data dao.Region) ([]dao.Region, error) {
|
|
|
}
|
|
|
|
|
|
return regions, nil
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+var s sync.Mutex
|
|
|
+
|
|
|
+func SaveRegions(regions []dao.Region) {
|
|
|
+ s.Lock()
|
|
|
+ // 创建一个映射以便快速查找
|
|
|
+ regionMap := make(map[string]dao.Region)
|
|
|
+ orderSlice := make([]string, 0, len(InitRegionData)) // 用于保存顺序的切片
|
|
|
+ for _, region := range InitRegionData {
|
|
|
+ regionMap[region.Name] = region
|
|
|
+ orderSlice = append(orderSlice, region.Name)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历传入的新数据,进行版本控制检查
|
|
|
+ for _, newRegion := range regions {
|
|
|
+ existingRegion, exists := regionMap[newRegion.Name]
|
|
|
+ if exists {
|
|
|
+ if existingRegion.Version != newRegion.Version {
|
|
|
+ logger.Get().Errorf("版本冲突: 区域 '%s' 的当前版本=%d, 提交版本=%d", newRegion.Name, existingRegion.Version, newRegion.Version)
|
|
|
+ }
|
|
|
+ // 更新版本号
|
|
|
+ newRegion.Version++
|
|
|
+ regionMap[newRegion.Name] = newRegion
|
|
|
+ } else {
|
|
|
+ // 如果区域不存在,则直接添加(假设这是新增的区域)
|
|
|
+ newRegion.Version = 1 // 新增区域的初始版本号设为1
|
|
|
+ regionMap[newRegion.Name] = newRegion
|
|
|
+ orderSlice = append(orderSlice, newRegion.Name) // 添加新区域到顺序切片
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 将 map 按照 orderSlice 的顺序转换回 slice
|
|
|
+ updatedRegions := make([]dao.Region, 0, len(regionMap))
|
|
|
+ for _, name := range orderSlice {
|
|
|
+ updatedRegions = append(updatedRegions, regionMap[name])
|
|
|
+ }
|
|
|
+ InitRegionData = updatedRegions
|
|
|
+ s.Unlock()
|
|
|
}
|
|
|
|
|
|
// SaveData 保存数据到文件
|
|
@@ -175,7 +226,20 @@ func SaveData(path string, newData []dao.Region) error {
|
|
|
return fmt.Errorf("关闭临时文件失败: %v", err)
|
|
|
}
|
|
|
|
|
|
- // 使用原子操作替换原始文件
|
|
|
+ // 检查目标文件是否存在以及是否有写权限
|
|
|
+ if _, err := os.Stat(path); err == nil {
|
|
|
+ // 文件存在,检查是否可写
|
|
|
+ if writable, err := checkWritable(path); err != nil || !writable {
|
|
|
+ return fmt.Errorf("目标文件不可写: %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if err = os.Remove(path); err != nil {
|
|
|
+ if !os.IsNotExist(err) {
|
|
|
+ return fmt.Errorf("删除目标文件失败: %w", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 尝试重命名临时文件到目标位置
|
|
|
if err := os.Rename(tempFile.Name(), path); err != nil {
|
|
|
return fmt.Errorf("替换文件失败: %v", err)
|
|
|
}
|
|
@@ -183,6 +247,16 @@ func SaveData(path string, newData []dao.Region) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// 辅助函数:检查文件是否可写
|
|
|
+func checkWritable(filename string) (bool, error) {
|
|
|
+ file, err := os.OpenFile(filename, os.O_WRONLY, 0)
|
|
|
+ if err != nil {
|
|
|
+ return false, err
|
|
|
+ }
|
|
|
+ file.Close()
|
|
|
+ return true, nil
|
|
|
+}
|
|
|
+
|
|
|
const (
|
|
|
maxRetries = 3 // 最大重试次数
|
|
|
readTimeout = 5 * time.Second // 读取超时时间
|