wlzboy
2025-11-29 364adbc9a93a396b74e154f910c2a0a72bfb1a0f
feat: 更新车辆里程统计
25个文件已修改
17 文件已重命名
4个文件已添加
1223 ■■■■ 已修改文件
app/pages.json 85 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/message/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/create.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/AttachmentUpload.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/DepartureSelector.vue 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/DiseaseSelector.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/HospitalSelector.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/OrganizationSelector.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/TaskTypeSelector.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/VehicleSelector.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/map-selector.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/components/uni-section/uni-section.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/create-emergency.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/create-normal.vue 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/create-welfare.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/detail.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit-emergency.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit-welfare.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/edit.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pagesTask/settlement.vue 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CmsVehicleSyncTask.java 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java 535 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/dryad_payment_tables.sql 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fix_duplicate_vehicle_plates.sql 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fixes/fix_duplicate_mileage_stats.sql 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/fixes/fix_task_ratio_column.sql 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/gps_compensation_config.sql 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/resync_vehicle_personnel_job.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/updates/add_segment_count_to_mileage_stats.sql 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/updates/add_task_id_to_segment_mileage.sql 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages.json
@@ -101,51 +101,56 @@
      "navigationBarTitleText": "选择任务类型"
    }
  }, {
    "path": "pages/task/create-normal",
    "style": {
      "navigationBarTitleText": "创建普通任务"
    }
  }, {
    "path": "pages/task/create-emergency",
    "style": {
      "navigationBarTitleText": "创建转运任务"
    }
  }, {
    "path": "pages/task/create-welfare",
    "style": {
      "navigationBarTitleText": "创建福祉车任务"
    }
  }, {
    "path": "pages/task/detail",
    "style": {
      "navigationBarTitleText": "任务详情"
    }
  }, {
    "path": "pages/task/edit",
    "style": {
      "navigationBarTitleText": "编辑任务"
    }
  }, {
    "path": "pages/task/edit-emergency",
    "style": {
      "navigationBarTitleText": "编辑转运任务"
    }
  }, {
    "path": "pages/task/edit-welfare",
    "style": {
      "navigationBarTitleText": "编辑福祗车任务"
    }
  }, {
    "path": "pages/task/settlement",
    "style": {
      "navigationBarTitleText": "任务结算"
    }
  }, {
    "path": "pages/message/index",
    "style": {
      "navigationBarTitleText": "消息中心"
    }
  }],
  "subPackages": [{
    "root": "pagesTask",
    "name": "task",
    "pages": [{
      "path": "create-normal",
      "style": {
        "navigationBarTitleText": "创建普通任务"
      }
    }, {
      "path": "create-emergency",
      "style": {
        "navigationBarTitleText": "创建转运任务"
      }
    }, {
      "path": "create-welfare",
      "style": {
        "navigationBarTitleText": "创建福祉车任务"
      }
    }, {
      "path": "detail",
      "style": {
        "navigationBarTitleText": "任务详情"
      }
    }, {
      "path": "edit",
      "style": {
        "navigationBarTitleText": "编辑任务"
      }
    }, {
      "path": "edit-emergency",
      "style": {
        "navigationBarTitleText": "编辑转运任务"
      }
    }, {
      "path": "edit-welfare",
      "style": {
        "navigationBarTitleText": "编辑福祗车任务"
      }
    }, {
      "path": "settlement",
      "style": {
        "navigationBarTitleText": "任务结算"
      }
    }]
  }],
  "tabBar": {
    "color": "#000000",
    "selectedColor": "#000000",
app/pages/index.vue
@@ -402,7 +402,7 @@
      // 查看任务详情
      viewTaskDetail(task) {
        // 跳转到任务详情页面 - 使用taskId
        this.$tab.navigateTo(`/pages/task/detail?id=${task.taskId || task.id}`);
        this.$tab.navigateTo(`/pagesTask/detail?id=${task.taskId || task.id}`);
      },
      
      // 处理任务操作
app/pages/message/index.vue
@@ -123,7 +123,7 @@
          
          // 跳转到任务详情页面
          if (message.taskId) {
            this.$tab.navigateTo(`/pages/task/detail?id=${message.taskId}`)
            this.$tab.navigateTo(`/pagesTask/detail?id=${message.taskId}`)
          } else {
            this.$modal.showToast('无法找到关联任务')
          }
@@ -131,7 +131,7 @@
          console.error('标记消息已读失败:', error)
          // 即使标记失败,也允许跳转
          if (message && message.taskId) {
            this.$tab.navigateTo(`/pages/task/detail?id=${message.taskId}`)
            this.$tab.navigateTo(`/pagesTask/detail?id=${message.taskId}`)
          }
        }
      },
app/pages/task/create.vue
@@ -37,7 +37,7 @@
          color: '#E54D42',
          description: '紧急医疗转运任务',
          taskType: 'EMERGENCY_TRANSFER',
          page: '/pages/task/create-emergency'
          page: '/pagesTask/create-emergency'
        },
        {
          type: 'normal',
@@ -46,7 +46,7 @@
          color: '#007AFF',
          description: '设备维修、保养等日常任务',
          taskType: 'MAINTENANCE',
          page: '/pages/task/create-normal'
          page: '/pagesTask/create-normal'
        },
        {
          type: 'normal',
@@ -55,7 +55,7 @@
          color: '#1AAD19',
          description: '车辆加油等任务',
          taskType: 'FUEL',
          page: '/pages/task/create-normal'
          page: '/pagesTask/create-normal'
        },
        {
@@ -65,7 +65,7 @@
          color: '#F37B1D',
          description: '老年人、残疾人等特殊群体用车服务',
          taskType: 'WELFARE',
          page: '/pages/task/create-welfare'
          page: '/pagesTask/create-welfare'
        }
      ]
    }
app/pages/task/index.vue
@@ -456,7 +456,7 @@
        
        // 跳转到任务详情页面 - 使用uni.navigateTo
        uni.navigateTo({
          url: `/pages/task/detail?id=${task.taskId}`
          url: `/pagesTask/detail?id=${task.taskId}`
        });
      },
      
app/pagesTask/components/AttachmentUpload.vue
app/pagesTask/components/DepartureSelector.vue
File was renamed from app/components/DepartureSelector.vue
@@ -26,7 +26,7 @@
        
        <view class="current-location-btn" @click="getCurrentLocation">
          <uni-icons type="location" size="20" color="#007AFF"></uni-icons>
          <text>当前位置</text>
        </view>
      </view>
      
@@ -199,48 +199,68 @@
      this.showAddressSuggestions = false
      this.addressSuggestions = []
    },
    // 获取当前位置
    getCurrentLocation() {
      uni.showLoading({
        title: '获取位置中...'
      })
      // 使用uni-app的GPS定位功能
      uni.getLocation({
        type: 'gcj02', // 返回国测局坐标,适用于国内地图
        success: (res) => {
          console.log('获取到GPS坐标:', res)
          const latitude = res.latitude
          const longitude = res.longitude
          // 更新GPS坐标
          this.$emit('update:longitude', longitude)
          this.$emit('update:latitude', latitude)
          // 调用逆地理编码接口,将坐标转换为地址
    geocoder(longitude, latitude){
 // 调用逆地理编码接口,将坐标转换为地址
          reverseGeocoder(latitude, longitude)
            .then(response => {
              uni.hideLoading()
              
              console.log('逆地理编码API完整响应:', JSON.stringify(response))
              if (response.code === 200 && response.data) {
                // 获取详细地址
                const address = response.data.address || response.data.formattedAddress || ''
                // 解析后端返回的数据(可能是字符串,需要parse)
                let responseData = response.data
                if (typeof responseData === 'string') {
                  try {
                    responseData = JSON.parse(responseData)
                  } catch (e) {
                    console.error('解析响应数据失败:', e)
                    responseData = {}
                  }
                }
                
                // 更新地址
                this.$emit('update:address', address)
                console.log('解析后的responseData:', responseData)
                
                // 触发位置获取成功事件
                this.$emit('location-success', {
                  address: address,
                  longitude: longitude,
                  latitude: latitude
                })
                // 腾讯地图API返回格式: {status: 0, result: {address: "..."}}
                let address = ''
                if (responseData.status === 0 && responseData.result) {
                  address = responseData.result.address || responseData.result.formatted_addresses?.recommend || ''
                } else if (responseData.address) {
                  // 兼容其他可能的返回格式
                  address = responseData.address
                } else if (responseData.formattedAddress) {
                  address = responseData.formattedAddress
                }
                
                console.log('逆地理编码成功:', address)
                this.$modal.showToast('已获取当前位置')
                console.log('解析出的地址:', address)
                if (address) {
                  // 更新地址
                  this.$emit('update:address', address)
                  // 触发位置获取成功事件
                  this.$emit('location-success', {
                    address: address,
                    longitude: longitude,
                    latitude: latitude
                  })
                  console.log('逆地理编码成功,地址已更新:', address)
                  this.$modal.showToast('已获取当前位置')
                } else {
                  console.error('未能从响应中提取地址,responseData:', responseData)
                  // 即使地址解析失败,坐标已保存,触发事件
                  this.$emit('location-success', {
                    address: '',
                    longitude: longitude,
                    latitude: latitude
                  })
                  this.$modal.showToast('位置解析失败,请手动输入地址')
                }
              } else {
                console.error('逆地理编码失败:', response.msg)
                console.error('逆地理编码失败,response.code:', response.code, 'msg:', response.msg)
                
                // 即使地址解析失败,坐标已保存,触发事件
                this.$emit('location-success', {
@@ -265,11 +285,41 @@
              
              this.$modal.showToast('位置解析失败,但GPS坐标已保存')
            })
    },
    // 获取当前位置
    getCurrentLocation() {
      uni.showLoading({
        title: '获取位置中...'
      })
      // 使用uni-app的GPS定位功能
      uni.getLocation({
        type: 'gcj02', // 返回国测局坐标,适用于国内地图
        success: (res) => {
          console.log('获取到GPS坐标:', res)
          const latitude = res.latitude
          const longitude = res.longitude
          // 更新GPS坐标
          this.$emit('update:longitude', longitude)
          this.$emit('update:latitude', latitude)
          this.geocoder(longitude, latitude)
        },
        fail: (err) => {
          uni.hideLoading()
          console.error('获取位置失败:', err)
          //我们使用默认的坐标来处理
          //23.20593,113.228998
          const longitude = 113.228998
          const latitude = 23.20593
          this.$emit('location-success', {
            address: '',
            longitude: longitude,
            latitude: latitude
          })
          this.geocoder(longitude, latitude)
          // 提示用户可能的原因
          let errorMsg = '获取位置失败'
          if (err.errMsg && err.errMsg.includes('auth deny')) {
@@ -373,7 +423,7 @@
        background-color: #f0f7ff;
        border-radius: 10rpx;
        white-space: nowrap;
        min-height: 70rpx;
        min-height: 40rpx;
        
        &:active {
          background-color: #e0f0ff;
app/pagesTask/components/DiseaseSelector.vue
app/pagesTask/components/HospitalSelector.vue
app/pagesTask/components/OrganizationSelector.vue
File was renamed from app/components/OrganizationSelector.vue
@@ -152,7 +152,7 @@
      if (organization) {
        // 提取地域关键词(去除"分公司"、"总公司"、"总部"后缀)
        const region = organization.deptName.replace(/(分公司|总公司|总部)$/g, '').trim()
        // console.log('region', organization)
        this.$emit('input', organization.deptId)
        this.$emit('change', {
          deptId: organization.deptId,
app/pagesTask/components/TaskTypeSelector.vue
app/pagesTask/components/VehicleSelector.vue
app/pagesTask/components/map-selector.vue
app/pagesTask/components/uni-section/uni-section.vue
app/pagesTask/create-emergency.vue
File was renamed from app/pages/task/create-emergency.vue
@@ -96,7 +96,6 @@
          v-model="taskForm.transferTime" 
          type="datetime" 
          :placeholder="'请选择转运时间'"
          class="form-input"
        />
      </view>
      
@@ -332,7 +331,7 @@
import { addTask } from "@/api/task"
import { listAvailableVehicles, getUserBoundVehicle } from "@/api/vehicle"
import { searchHospitals, searchHospitalsByDeptRegion } from "@/api/hospital"
import DepartureSelector from '@/components/DepartureSelector.vue'
import DepartureSelector from './components/DepartureSelector.vue'
import { calculateDistance, baiduDistanceByAddress, baiduPlaceSuggestion } from "@/api/map"
import { listBranchUsers } from "@/api/system/user"
import { searchIcd10 } from "@/api/icd10"
@@ -342,10 +341,10 @@
import { getDicts } from "@/api/dict"
import { getServiceOrdAreaTypes, getServiceOrderTypes, getHospitalDepartments } from "@/api/dictionary"
import { listBranchCompany, getDept } from "@/api/system/dept"
import MapSelector from '@/components/map-selector.vue'
import OrganizationSelector from '@/components/OrganizationSelector.vue'
import HospitalSelector from '@/components/HospitalSelector.vue'
import DiseaseSelector from '@/components/DiseaseSelector.vue'
import MapSelector from './components/map-selector.vue'
import OrganizationSelector from './components/OrganizationSelector.vue'
import HospitalSelector from './components/HospitalSelector.vue'
import DiseaseSelector from './components/DiseaseSelector.vue'
export default {
  components: {
app/pagesTask/create-normal.vue
app/pagesTask/create-welfare.vue
File was renamed from app/pages/task/create-welfare.vue
@@ -149,7 +149,7 @@
import { addTask } from "@/api/task"
import { listAvailableVehicles } from "@/api/vehicle"
import { calculateDistance } from "@/api/map"
import MapSelector from '@/components/map-selector.vue'
import MapSelector from './components/map-selector.vue'
export default {
  components: {
app/pagesTask/detail.vue
File was renamed from app/pages/task/detail.vue
@@ -414,7 +414,7 @@
  import { checkVehicleActiveTasks } from '@/api/task'
  import { getPaymentInfo } from '@/api/payment'
  import { formatDateTime } from '@/utils/common'
  import AttachmentUpload from '@/components/AttachmentUpload.vue'
  import AttachmentUpload from './components/AttachmentUpload.vue'
  
  export default {
    components: {
@@ -644,17 +644,17 @@
        if (taskType === 'EMERGENCY_TRANSFER') {
          // 转运任务:跳转到转运任务编辑页面
          uni.navigateTo({
            url: `/pages/task/edit-emergency?id=${taskId}`
            url: `/pagesTask/edit-emergency?id=${taskId}`
          })
        } else if (taskType === 'WELFARE') {
          // 福祗车任务:跳转到福祗车编辑页面
          uni.navigateTo({
            url: `/pages/task/edit-welfare?id=${taskId}`
            url: `/pagesTask/edit-welfare?id=${taskId}`
          })
        } else {
          // 其他任务:跳转到通用任务编辑页面
          uni.navigateTo({
            url: `/pages/task/edit?id=${taskId}`
            url: `/pagesTask/edit?id=${taskId}`
          })
        }
      },
@@ -688,7 +688,7 @@
      // 处理结算
      handleSettlement() {
        uni.navigateTo({
          url: '/pages/task/settlement?taskId=' + this.taskId
          url: '/pagesTask/settlement?taskId=' + this.taskId
        })
      },
      
app/pagesTask/edit-emergency.vue
File was renamed from app/pages/task/edit-emergency.vue
@@ -273,11 +273,11 @@
import { listBranchUsers } from "@/api/system/user"
import { baiduDistanceByAddress } from "@/api/map"
import { calculateTransferPrice } from "@/api/price"
import MapSelector from '@/components/map-selector.vue'
import VehicleSelector from '@/components/VehicleSelector.vue'
import OrganizationSelector from '@/components/OrganizationSelector.vue'
import HospitalSelector from '@/components/HospitalSelector.vue'
import DiseaseSelector from '@/components/DiseaseSelector.vue'
import MapSelector from './components/map-selector.vue'
import VehicleSelector from './components/VehicleSelector.vue'
import OrganizationSelector from './components/OrganizationSelector.vue'
import HospitalSelector from './components/HospitalSelector.vue'
import DiseaseSelector from './components/DiseaseSelector.vue'
import distanceCalculator from '@/mixins/distanceCalculator.js'
export default {
app/pagesTask/edit-welfare.vue
File was renamed from app/pages/task/edit-welfare.vue
@@ -152,8 +152,8 @@
import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import { getTask, updateTask } from "@/api/task"
import MapSelector from '@/components/map-selector.vue'
import VehicleSelector from '@/components/VehicleSelector.vue'
import MapSelector from './components/map-selector.vue'
import VehicleSelector from './components/VehicleSelector.vue'
import distanceCalculator from '@/mixins/distanceCalculator.js'
export default {
app/pagesTask/edit.vue
File was renamed from app/pages/task/edit.vue
@@ -131,9 +131,9 @@
import uniDatetimePicker from '@/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue'
import uniPopup from '@/uni_modules/uni-popup/components/uni-popup/uni-popup.vue'
import { getTask, updateTask } from "@/api/task"
import MapSelector from '@/components/map-selector.vue'
import VehicleSelector from '@/components/VehicleSelector.vue'
import TaskTypeSelector from '@/components/TaskTypeSelector.vue'
import MapSelector from './components/map-selector.vue'
import VehicleSelector from './components/VehicleSelector.vue'
import TaskTypeSelector from './components/TaskTypeSelector.vue'
import distanceCalculator from '@/mixins/distanceCalculator.js'
export default {
app/pagesTask/settlement.vue
ruoyi-admin/src/main/resources/application.yml
@@ -137,6 +137,18 @@
# GPS服务配置
gps:
  mileage:
    compensation:
      days: 7
    segment:
    # 分段计算时间间隔(单位:分钟)
      minutes: 5
  calculate:
  # 计算方法,可选值:haversine(haversine公式)、tianditu(天地图)
    method: haversine
  # 是否跳过已经计算的点
  skip:
    calculated: true
  service:
    domain: https://gps51.com
    username: 王某人
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/CmsVehicleSyncTask.java
@@ -67,27 +67,61 @@
            List<String> onlyCms=cmsPlateNos.stream().filter(e->!notCmsVehicles.contains(e)).collect((Collectors.toList()));
            Integer syncCarCount=0;
            response.getDevices().stream().filter(e->onlyCms.contains(this.getPlateNo(e.getVid()))).forEach(vehicle->{
                String plateNo =this.getPlateNo(vehicle.getVid());
                VehicleInfo vehicleInfo= vehicleInfoService.selectVehicleInfoByPlateNumber(plateNo);
                if (vehicleInfo==null) {
                    vehicleInfo = new VehicleInfo();
                    vehicleInfo.setVehicleNo(plateNo);
                    vehicleInfo.setDeviceId(vehicle.getDid());
                    vehicleInfo.setPlatformCode("CMS");
                    vehicleInfo.setStatus("0");
                    vehicleInfoService.insertVehicleInfo(vehicleInfo);
            for (CmsVehicleDeviceListResponse.CmsVehicleDevice vehicle : response.getDevices()) {
                try {
                    if (!onlyCms.contains(this.getPlateNo(vehicle.getVid()))) {
                        continue;
                    }
                    String plateNo = this.getPlateNo(vehicle.getVid());
                    // 使用重试机制处理死锁
                    int maxRetries = 3;
                    int retryCount = 0;
                    boolean success = false;
                    while (!success && retryCount < maxRetries) {
                        try {
                            // 查询车辆信息(使用精确匹配)
                            VehicleInfo vehicleInfo = vehicleInfoService.selectVehicleInfoByPlateNumber(plateNo);
                            if (vehicleInfo == null) {
                                // 新增车辆
                                vehicleInfo = new VehicleInfo();
                                vehicleInfo.setVehicleNo(plateNo);
                                vehicleInfo.setDeviceId(vehicle.getDid());
                                vehicleInfo.setPlatformCode("CMS");
                                vehicleInfo.setStatus("0");
                                vehicleInfoService.insertVehicleInfo(vehicleInfo);
                                syncCarCount++;
                                log.info("新增CMS车辆: {}", plateNo);
                            } else {
                                // 更新车辆 - 仅更新必要字段,避免触发关联表操作
                                vehicleInfo.setDeviceId(vehicle.getDid());
                                vehicleInfo.setPlatformCode("CMS");
                                vehicleInfo.setStatus("0");
                                vehicleInfo.setDeptIds(null); // 不更新部门关联,避免死锁
                                vehicleInfoService.updateVehicleInfo(vehicleInfo);
                                syncCarCount++;
                                log.debug("更新CMS车辆: {}", plateNo);
                            }
                            success = true;
                        } catch (org.springframework.dao.DeadlockLoserDataAccessException e) {
                            retryCount++;
                            if (retryCount < maxRetries) {
                                log.warn("同步车辆 {} 遇到死锁,第{}次重试", plateNo, retryCount);
                                // 随机等待50-200ms后重试,避免多个线程同时重试
                                Thread.sleep(50 + (long)(Math.random() * 150));
                            } else {
                                log.error("同步车辆 {} 失败: 死锁重试{}次后仍失败", plateNo, maxRetries);
                                throw e;
                            }
                        }
                    }
                } catch (Exception e) {
                    log.error("同步车辆 {} 失败: {}", vehicle.getVid(), e.getMessage());
                    // 继续处理下一个车辆
                }
                else{
                    vehicleInfo.setVehicleNo(plateNo);
                    vehicleInfo.setDeviceId(vehicle.getDid());
                    vehicleInfo.setPlatformCode("CMS");
                    vehicleInfo.setStatus("0");
                    vehicleInfoService.updateVehicleInfo(vehicleInfo);
                }
            });
            }
ruoyi-quartz/src/main/java/com/ruoyi/quartz/task/VehicleGpsSegmentMileageTask.java
@@ -2,6 +2,7 @@
import java.util.Calendar;
import java.util.Date;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +23,45 @@
    
    @Autowired
    private ISysConfigService configService;
    /**
     * 服务启动时执行补偿计算
     * 检查最近7天内未被处理的GPS坐标并进行补偿计算
     */
    @PostConstruct
    public void init() {
        // 启动后延迟执行,避免影响服务启动速度
        new Thread(() -> {
            try {
                // 延迟30秒启动,确保所有服务已就绪
                Thread.sleep(30000);
                logger.info("========== 开始执行GPS分段里程补偿计算 ==========");
                // 获取配置的回溯天数,默认7天
                int lookbackDays = 7;
                String lookbackConfig = configService.selectConfigByKey("gps.mileage.compensation.days");
                if (lookbackConfig != null && !lookbackConfig.isEmpty()) {
                    try {
                        lookbackDays = Integer.parseInt(lookbackConfig);
                    } catch (NumberFormatException e) {
                        logger.warn("补偿回溯天数配置错误,使用默认值7天");
                    }
                }
                // 执行补偿计算
                int successCount = segmentMileageService.compensateCalculation(lookbackDays);
                logger.info("========== GPS分段里程补偿计算完成 - 成功处理 {} 辆车 ==========", successCount);
            } catch (InterruptedException e) {
                logger.error("补偿计算线程被中断", e);
                Thread.currentThread().interrupt();
            } catch (Exception e) {
                logger.error("GPS分段里程补偿计算失败", e);
            }
        }, "GPS-Compensation-Thread").start();
    }
    /**
     * 计算最近一段时间的GPS分段里程
@@ -59,12 +99,14 @@
                }
            }
            
            // 计算时间范围
            // 计算时间范围(向前回溯指定分钟数)
            Calendar cal = Calendar.getInstance();
            Date endTime = cal.getTime();
            cal.add(Calendar.MINUTE, -minutes);
            Date startTime = cal.getTime();
            
            // 注意:此方法只计算最近时间段的数据,历史遗漏数据由补偿机制处理
            logger.info("开始计算GPS分段里程 - 时间范围: {} 到 {}, 时间段间隔: {}分钟", 
                       startTime, endTime, segmentMinutes);
            
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskEmergencyMapper.java
@@ -1,6 +1,9 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.SysTaskEmergency;
@@ -10,6 +13,7 @@
 * @author ruoyi
 * @date 2024-01-16
 */
@DataSource(DataSourceType.MASTER)
public interface SysTaskEmergencyMapper {
    
    /**
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskMapper.java
@@ -1,6 +1,9 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.SysTask;
import com.ruoyi.system.domain.vo.TaskQueryVO;
import com.ruoyi.system.domain.vo.TaskStatisticsVO;
@@ -11,6 +14,7 @@
 * @author ruoyi
 * @date 2024-01-15
 */
@DataSource(DataSourceType.MASTER)
public interface SysTaskMapper {
    
    /**
@@ -24,7 +28,7 @@
    /**
     * 查询任务管理列表
     * 
     * @param sysTask 任务管理
     * @param queryVO 任务管理
     * @return 任务管理集合
     */
    public List<SysTask> selectSysTaskList(TaskQueryVO queryVO);
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysTaskPaymentMapper.java
@@ -1,6 +1,9 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.SysTaskPayment;
import org.apache.ibatis.annotations.Param;
@@ -10,6 +13,7 @@
 * @author ruoyi
 * @date 2025-01-15
 */
@DataSource(DataSourceType.MASTER)
public interface SysTaskPaymentMapper {
    
    /**
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
@@ -1,6 +1,9 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.common.core.domain.entity.SysUser;
@@ -9,6 +12,7 @@
 * 
 * @author ruoyi
 */
@DataSource(DataSourceType.MASTER)
public interface SysUserMapper
{
    /**
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleGpsSegmentMileageMapper.java
@@ -75,4 +75,13 @@
     * 检查GPS点是否已被计算
     */
    public Long selectGpsCalculatedSegmentId(@Param("gpsId") Long gpsId);
    /**
     * 查询车辆在指定时间之前最后一个已处理的GPS坐标ID
     * @param vehicleId 车辆ID
     * @param beforeTime 截止时间(查询此时间之前的最后一个已处理GPS点)
     * @return GPS坐标ID,如果没有则返回null
     */
    public Long selectLastCalculatedGpsId(@Param("vehicleId") Long vehicleId,
                                           @Param("beforeTime") Date beforeTime);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/AdditionalFeeSyncServiceImpl.java
@@ -92,8 +92,6 @@
    }
    
    @Override
    @Transactional
    @DataSource(DataSourceType.SQLSERVER)
    public boolean syncAdditionalFeeToLegacy(Long feeId) {
        try {
            // 1. 查询新系统附加费用记录
@@ -176,7 +174,6 @@
    }
    
    @Override
    @Transactional
    public boolean syncAdditionalFeeFromLegacy(Long paidMoneyAddId) {
        try {
            // 1. 查询旧系统PaidMoney_Add记录
@@ -278,8 +275,7 @@
        return successCount;
    }
    
    @Override
    @DataSource(DataSourceType.SQLSERVER)
   @Override
    public int batchSyncAdditionalFeeFromLegacy(Integer hours) {
        int successCount = 0;
        try {
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleGpsSegmentMileageServiceImpl.java
@@ -129,7 +129,7 @@
    @Override
    public int compensateCalculation(int lookbackDays) {
        try {
            // 计算时间范围
            // 计算时间范围(回溯指定天数)
            Calendar cal = Calendar.getInstance();
            Date endTime = cal.getTime();
            cal.add(Calendar.DAY_OF_MONTH, -lookbackDays);
@@ -162,9 +162,30 @@
                    logger.info("车辆 {} 发现 {} 个未计算的GPS点,开始补偿计算...", 
                               vehicleId, uncalculatedGps.size());
                    
                    // 获取未计算GPS数据的时间范围
                    Date uncalculatedStartTime = parseDateTime(uncalculatedGps.get(0).getCollectTime());
                    Date uncalculatedEndTime = parseDateTime(uncalculatedGps.get(uncalculatedGps.size() - 1).getCollectTime());
                    // 查找该时间段之前最后一个已处理的GPS坐标ID
                    Long lastCalculatedGpsId = segmentMileageMapper.selectLastCalculatedGpsId(vehicleId, uncalculatedStartTime);
                    if (lastCalculatedGpsId != null) {
                        logger.info("车辆 {} 找到最后一个已处理的GPS点ID: {},将与未处理数据一起计算", vehicleId, lastCalculatedGpsId);
                        // 将最后一个已处理的GPS点加入列表前面,作为前置点
                        VehicleGps lastCalculatedGps = vehicleGpsMapper.selectVehicleGpsById(lastCalculatedGpsId);
                        if (lastCalculatedGps != null) {
                            uncalculatedGps.add(0, lastCalculatedGps); // 插入到列表最前面
                            logger.info("已将GPS点 {} 作为前置点加入计算列表", lastCalculatedGpsId);
                        }
                    } else {
                        logger.info("车辆 {} 没有找到已处理的前置 GPS点,从第一个未处理点开始计算", vehicleId);
                    }
                    // 重新计算该车辆在该时间范围的分段里程
                    // 注意:这里会重新计算整个时间范围,确保边缘节点被正确处理
                    int segmentCount = calculateVehicleSegmentMileage(vehicleId, startTime, endTime);
                    int segmentCount = calculateVehicleSegmentMileageWithGpsList(
                        vehicleId, uncalculatedGps, uncalculatedStartTime, uncalculatedEndTime);
                    
                    if (segmentCount > 0) {
                        successCount++;
@@ -188,21 +209,6 @@
    @Override
    public int calculateVehicleSegmentMileage(Long vehicleId, Date startTime, Date endTime) {
        try {
            // 获取配置的时间间隔(分钟)
            int segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
                ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
                : 5;
            // 获取计算方式配置
            String calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
            if (calculateMethod == null || calculateMethod.isEmpty()) {
                calculateMethod = "tianditu";
            }
            // 获取是否跳过已计算GPS点的配置
            String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
            boolean skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
            // 查询车辆在时间范围内的GPS数据
            List<VehicleGps> gpsList = vehicleGpsMapper.selectGpsDataByTimeRange(vehicleId, startTime, endTime);
            
@@ -211,153 +217,47 @@
                return 0;
            }
            
            logger.info("车辆ID: {} 查询到 {} 条GPS数据", vehicleId, gpsList.size());
            logger.info("车辆ID: {} 查询到 {} 条GPS数据 startTime:{},endTime:{}", vehicleId, gpsList.size(),startTime,endTime);
            return calculateVehicleSegmentMileageWithGpsList(vehicleId, gpsList, startTime, endTime);
        } catch (Exception e) {
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    /**
     * 根据提供的GPS列表计算车辆分段里程
     * @param vehicleId 车辆ID
     * @param gpsList GPS列表(已按时间排序)
     * @param startTime 起始时间
     * @param endTime 结束时间
     * @return 生成的分段数量
     */
    private int calculateVehicleSegmentMileageWithGpsList(Long vehicleId, List<VehicleGps> gpsList,
                                                           Date startTime, Date endTime) {
        try {
            // 验证输入数据
            if (gpsList == null || gpsList.isEmpty()) {
                logger.debug("车辆ID: {} 在时间范围 {} 到 {} 内无GPS数据", vehicleId, startTime, endTime);
                return 0;
            }
            // 加载配置参数
            MileageCalculationConfig config = loadMileageCalculationConfig();
            
            // 按时间段分组GPS数据
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, segmentMinutes);
            Map<Date, List<VehicleGps>> segmentedData = segmentGpsDataByTime(gpsList, config.segmentMinutes);
            
            int savedCount = 0;
            VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
            // 遍历每个时间段,计算里程
            for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
                Date segmentStartTime = entry.getKey();
                List<VehicleGps> segmentGpsList = entry.getValue();
                if (segmentGpsList.size() < 2) {
                    // 如果本段只有1个点,但有上一段的最后一个点,仍可计算跨段距离
                    if (segmentGpsList.size() == 1 && previousSegmentLastPoint != null) {
                        // 保留当前点作为下一段的前置点,但不创建记录
                        previousSegmentLastPoint = segmentGpsList.get(0);
                    }
                    continue; // 至少需要2个点才能计算距离
                }
                // 检查是否已存在该时间段的记录
                VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
                if (existing != null) {
                    logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
                    // 更新上一段最后一个点
                    previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                    continue;
                }
                // 计算时间段的结束时间
                Calendar cal = Calendar.getInstance();
                cal.setTime(segmentStartTime);
                cal.add(Calendar.MINUTE, segmentMinutes);
                Date segmentEndTime = cal.getTime();
                // 计算该时间段的里程(包括跨段距离)
                BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, calculateMethod, previousSegmentLastPoint);
                // 收集GPS ID列表(包括上一段的最后一个点,因为跨段间隙距离也用到了它)
                List<Long> gpsIdList = new ArrayList<>();
                // 如果有上一段的最后一个点,先添加它的ID
                if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
                    gpsIdList.add(previousSegmentLastPoint.getGpsId());
                }
                // 再添加当前段的所有GPS点ID
                for (VehicleGps gps : segmentGpsList) {
                    if (gps.getGpsId() != null) {
                        gpsIdList.add(gps.getGpsId());
                    }
                }
                String gpsIds = gpsIdList.stream()
                    .map(String::valueOf)
                    .collect(java.util.stream.Collectors.joining(","));
                // 创建分段里程记录
                VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
                segment.setVehicleId(vehicleId);
                // 从GPS数据或车辆表获取车牌号
                String vehicleNo = segmentGpsList.get(0).getVehicleNo();
                if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
                    // GPS数据中没有车牌号,从车辆表查询
                    VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
                    if (vehicleInfo != null) {
                        vehicleNo = vehicleInfo.getVehicleNo();
                    }
                }
                segment.setVehicleNo(vehicleNo);
                segment.setSegmentStartTime(segmentStartTime);
                segment.setSegmentEndTime(segmentEndTime);
                // 起点坐标
                VehicleGps firstPoint = segmentGpsList.get(0);
                segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
                segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
                // 终点坐标
                VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
                segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
                segment.setSegmentDistance(distance);
                segment.setGpsPointCount(gpsIdList.size()); // GPS点数:包括边缘点 + 当前段的点
                segment.setGpsIds(gpsIds); // 设置GPS ID列表
                segment.setCalculateMethod(calculateMethod);
                // 查询并关联正在执行的任务
                associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
                // 保存到数据库
                segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
                // 更新上一段最后一个点,供下一段使用
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                // 记录已计算的GPS点到状态表(如果开启了重复计算控制)
                if (skipCalculated && segment.getSegmentId() != null) {
                    for (Long gpsId : gpsIdList) {
                        try {
                            segmentMileageMapper.insertGpsCalculated(gpsId, segment.getSegmentId(), vehicleId);
                        } catch (Exception e) {
                            // 忽略重复键异常,继续处理
                            logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
                        }
                    }
                }
                savedCount++;
                logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
                           vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
                           gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
            }
            // 处理每个时间段并计算里程
            int savedCount = processSegmentedGpsData(vehicleId, segmentedData, config);
            
            logger.info("车辆 {} 计算完成,保存了 {} 个时间段的里程数据", vehicleId, savedCount);
            
            // 自动触发汇总生成每日统计(如果有数据被保存)
            // 自动触发每日统计汇总
            if (savedCount > 0) {
                try {
                    // 获取涉及的日期范围,触发汇总
                    Set<Date> affectedDates = new HashSet<>();
                    Calendar cal = Calendar.getInstance();
                    for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
                        cal.setTime(entry.getKey());
                        cal.set(Calendar.HOUR_OF_DAY, 0);
                        cal.set(Calendar.MINUTE, 0);
                        cal.set(Calendar.SECOND, 0);
                        cal.set(Calendar.MILLISECOND, 0);
                        affectedDates.add(cal.getTime());
                    }
                    // 对每个涉及的日期,触发汇总
                    for (Date statDate : affectedDates) {
                        try {
                            mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
                            logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                        } catch (Exception e) {
                            logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                        }
                    }
                } catch (Exception e) {
                    logger.error("触发自动汇总失败", e);
                }
                triggerDailyMileageAggregation(vehicleId, segmentedData);
            }
            
            return savedCount;
@@ -366,6 +266,305 @@
            logger.error("计算车辆 {} 分段里程失败", vehicleId, e);
            throw new RuntimeException("计算分段里程失败: " + e.getMessage());
        }
    }
    /**
     * 加载里程计算配置参数
     */
    private MileageCalculationConfig loadMileageCalculationConfig() {
        MileageCalculationConfig config = new MileageCalculationConfig();
        // 获取时间间隔配置(分钟)
        config.segmentMinutes = configService.selectConfigByKey("gps.mileage.segment.minutes") != null
            ? Integer.parseInt(configService.selectConfigByKey("gps.mileage.segment.minutes"))
            : 5;
        // 获取计算方式配置
        config.calculateMethod = configService.selectConfigByKey("gps.mileage.calculate.method");
        if (config.calculateMethod == null || config.calculateMethod.isEmpty()) {
            config.calculateMethod = "tianditu";
        }
        // 获取是否跳过已计算GPS点的配置
        String skipCalculatedConfig = configService.selectConfigByKey("gps.mileage.skip.calculated");
        config.skipCalculated = skipCalculatedConfig == null || "true".equalsIgnoreCase(skipCalculatedConfig);
        logger.info("控制跳过重复计算标识: {}", config.skipCalculated);
        return config;
    }
    /**
     * 处理分段后的GPS数据并计算里程
     * @param vehicleId 车辆ID
     * @param segmentedData 分段后的GPS数据
     * @param config 计算配置
     * @return 成功保存的分段数量
     */
    private int processSegmentedGpsData(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData,
                                         MileageCalculationConfig config) {
        int savedCount = 0;
        VehicleGps previousSegmentLastPoint = null; // 记录上一个时间段的最后一个点
        // 遍历每个时间段,计算里程
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            Date segmentStartTime = entry.getKey();
            List<VehicleGps> segmentGpsList = entry.getValue();
            // 校验当前时间段数据
            if (!isSegmentValidForCalculation(segmentGpsList, previousSegmentLastPoint, vehicleId, segmentStartTime)) {
                // 保留当前点作为下一段的前置点(如果有的话)
                if (!segmentGpsList.isEmpty()) {
                    previousSegmentLastPoint = segmentGpsList.get(0);
                }
                continue;
            }
            // 检查是否已存在该时间段的记录
            if (isSegmentAlreadyCalculated(vehicleId, segmentStartTime, segmentGpsList)) {
                previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
                continue;
            }
            // 计算并保存分段里程
            boolean success = calculateAndSaveSegment(vehicleId, segmentStartTime, segmentGpsList,
                                                       previousSegmentLastPoint, config);
            if (success) {
                savedCount++;
            }
            // 更新上一段最后一个点,供下一段使用
            previousSegmentLastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        }
        return savedCount;
    }
    /**
     * 校验时间段数据是否有效
     */
    private boolean isSegmentValidForCalculation(List<VehicleGps> segmentGpsList,
                                                  VehicleGps previousSegmentLastPoint,
                                                  Long vehicleId, Date segmentStartTime) {
        // 如果当前段没有GPS点,跳过
        if (segmentGpsList == null || segmentGpsList.isEmpty()) {
            return false;
        }
        // 如果本段只有1个点,且没有上一段的最后一个点,无法计算距离
        if (segmentGpsList.size() == 1 && previousSegmentLastPoint == null) {
            logger.debug("车辆 {} 时间段 {} 只有1个GPS点且无前置点,暂存待下一段计算", vehicleId, segmentStartTime);
            return false;
        }
        return true;
    }
    /**
     * 检查时间段是否已被计算
     */
    private boolean isSegmentAlreadyCalculated(Long vehicleId, Date segmentStartTime, List<VehicleGps> segmentGpsList) {
        VehicleGpsSegmentMileage existing = segmentMileageMapper.selectByVehicleIdAndTime(vehicleId, segmentStartTime);
        if (existing != null) {
            logger.debug("车辆 {} 时间段 {} 的分段里程已存在,跳过", vehicleId, segmentStartTime);
            return true;
        }
        return false;
    }
    /**
     * 计算并保存单个时间段的里程
     */
    private boolean calculateAndSaveSegment(Long vehicleId, Date segmentStartTime,
                                             List<VehicleGps> segmentGpsList,
                                             VehicleGps previousSegmentLastPoint,
                                             MileageCalculationConfig config) {
        try {
            // 计算时间段的结束时间
            Date segmentEndTime = calculateSegmentEndTime(segmentStartTime, config.segmentMinutes);
            // 计算该时间段的里程(包括跨段距离)
            BigDecimal distance = calculateSegmentDistanceWithGap(segmentGpsList, config.calculateMethod, previousSegmentLastPoint);
            // 收集GPS ID列表
            List<Long> gpsIdList = collectGpsIds(segmentGpsList, previousSegmentLastPoint);
            String gpsIds = gpsIdList.stream()
                .map(String::valueOf)
                .collect(java.util.stream.Collectors.joining(","));
            // 创建分段里程记录
            VehicleGpsSegmentMileage segment = buildSegmentMileageRecord(
                vehicleId, segmentStartTime, segmentEndTime, segmentGpsList,
                distance, gpsIdList, gpsIds, config.calculateMethod);
            // 保存到数据库
            logger.info("保存车辆分时段里程到数据库中,车辆ID: {}, 时间段: {} 到 {}", vehicleId, segmentStartTime, segmentEndTime);
            segmentMileageMapper.insertVehicleGpsSegmentMileage(segment);
            // 记录已计算的GPS点(如果开启了重复计算控制)
            if (config.skipCalculated && segment.getSegmentId() != null) {
                recordCalculatedGpsPoints(gpsIdList, segment.getSegmentId(), vehicleId);
            }
            logger.debug("车辆 {} 时间段 {} 到 {} 里程: {}km, GPS点数: {}, GPS IDs: {}",
                       vehicleId, segmentStartTime, segmentEndTime, distance, segmentGpsList.size(),
                       gpsIds.length() > 50 ? gpsIds.substring(0, 50) + "..." : gpsIds);
            return true;
        } catch (Exception e) {
            logger.error("保存车辆 {} 时间段 {} 的里程记录失败", vehicleId, segmentStartTime, e);
            return false;
        }
    }
    /**
     * 计算时间段结束时间
     */
    private Date calculateSegmentEndTime(Date segmentStartTime, int segmentMinutes) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(segmentStartTime);
        cal.add(Calendar.MINUTE, segmentMinutes);
        return cal.getTime();
    }
    /**
     * 收集GPS ID列表(包括前置点)
     */
    private List<Long> collectGpsIds(List<VehicleGps> segmentGpsList, VehicleGps previousSegmentLastPoint) {
        List<Long> gpsIdList = new ArrayList<>();
        // 如果有上一段的最后一个点,先添加它的ID(用于计算跨段距离)
        if (previousSegmentLastPoint != null && previousSegmentLastPoint.getGpsId() != null) {
            gpsIdList.add(previousSegmentLastPoint.getGpsId());
        }
        // 再添加当前段的所有GPS点ID
        for (VehicleGps gps : segmentGpsList) {
            if (gps.getGpsId() != null) {
                gpsIdList.add(gps.getGpsId());
            }
        }
        return gpsIdList;
    }
    /**
     * 构建分段里程记录对象
     */
    private VehicleGpsSegmentMileage buildSegmentMileageRecord(Long vehicleId, Date segmentStartTime,
                                                                 Date segmentEndTime, List<VehicleGps> segmentGpsList,
                                                                 BigDecimal distance, List<Long> gpsIdList,
                                                                 String gpsIds, String calculateMethod) {
        VehicleGpsSegmentMileage segment = new VehicleGpsSegmentMileage();
        segment.setVehicleId(vehicleId);
        // 获取车牌号
        String vehicleNo = getVehicleNo(vehicleId, segmentGpsList.get(0));
        segment.setVehicleNo(vehicleNo);
        // 设置时间范围
        segment.setSegmentStartTime(segmentStartTime);
        segment.setSegmentEndTime(segmentEndTime);
        // 设置起点坐标
        VehicleGps firstPoint = segmentGpsList.get(0);
        segment.setStartLongitude(BigDecimal.valueOf(firstPoint.getLongitude()));
        segment.setStartLatitude(BigDecimal.valueOf(firstPoint.getLatitude()));
        // 设置终点坐标
        VehicleGps lastPoint = segmentGpsList.get(segmentGpsList.size() - 1);
        segment.setEndLongitude(BigDecimal.valueOf(lastPoint.getLongitude()));
        segment.setEndLatitude(BigDecimal.valueOf(lastPoint.getLatitude()));
        // 设置里程数据
        segment.setSegmentDistance(distance);
        segment.setGpsPointCount(gpsIdList.size());
        segment.setGpsIds(gpsIds);
        segment.setCalculateMethod(calculateMethod);
        // 查询并关联正在执行的任务
        associateActiveTask(segment, vehicleId, segmentStartTime, segmentEndTime);
        return segment;
    }
    /**
     * 获取车牌号
     */
    private String getVehicleNo(Long vehicleId, VehicleGps firstGps) {
        String vehicleNo = firstGps.getVehicleNo();
        if (vehicleNo == null || vehicleNo.trim().isEmpty()) {
            // GPS数据中没有车牌号,从车辆表查询
            VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoById(vehicleId);
            if (vehicleInfo != null) {
                vehicleNo = vehicleInfo.getVehicleNo();
            }
        }
        return vehicleNo;
    }
    /**
     * 记录已计算的GPS点到状态表
     */
    private void recordCalculatedGpsPoints(List<Long> gpsIdList, Long segmentId, Long vehicleId) {
        for (Long gpsId : gpsIdList) {
            try {
                segmentMileageMapper.insertGpsCalculated(gpsId, segmentId, vehicleId);
            } catch (Exception e) {
                // 忽略重复键异常,继续处理
                logger.debug("记录GPS计算状态失败,可能已存在: gpsId={}", gpsId);
            }
        }
    }
    /**
     * 触发每日里程统计汇总
     */
    private void triggerDailyMileageAggregation(Long vehicleId, Map<Date, List<VehicleGps>> segmentedData) {
        try {
            // 获取涉及的日期范围
            Set<Date> affectedDates = extractAffectedDates(segmentedData);
            // 对每个涉及的日期,触发汇总
            for (Date statDate : affectedDates) {
                try {
                    mileageStatsService.aggregateFromSegmentMileage(vehicleId, statDate);
                    logger.info("车辆 {} 日期 {} 的统计数据已自动汇总生成", vehicleId, statDate);
                } catch (Exception e) {
                    logger.error("车辆 {} 日期 {} 自动汇总统计失败", vehicleId, statDate, e);
                }
            }
        } catch (Exception e) {
            logger.error("触发自动汇总失败", e);
        }
    }
    /**
     * 提取受影响的日期列表(用于汇总统计)
     */
    private Set<Date> extractAffectedDates(Map<Date, List<VehicleGps>> segmentedData) {
        Set<Date> affectedDates = new HashSet<>();
        Calendar cal = Calendar.getInstance();
        for (Map.Entry<Date, List<VehicleGps>> entry : segmentedData.entrySet()) {
            cal.setTime(entry.getKey());
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            affectedDates.add(cal.getTime());
        }
        return affectedDates;
    }
    /**
     * 里程计算配置类
     */
    private static class MileageCalculationConfig {
        int segmentMinutes;          // 时间段间隔(分钟)
        String calculateMethod;      // 计算方式
        boolean skipCalculated;      // 是否跳过已计算的GPS点
    }
    /**
@@ -409,12 +608,13 @@
    /**
     * 计算一个时间段内的总里程(包括与上一段的间隙距离)
     * @param gpsList 当前时间段的GPS点列表
     * @param gpsList 当前时间段的GPS点列表(至少1个点)
     * @param calculateMethod 计算方式
     * @param previousLastPoint 上一个时间段的最后一个点(可为null)
     * @return 总里程(公里),保留3位小数
     */
    private BigDecimal calculateSegmentDistanceWithGap(List<VehicleGps> gpsList, String calculateMethod, VehicleGps previousLastPoint) {
        if (gpsList == null || gpsList.size() < 2) {
        if (gpsList == null || gpsList.isEmpty()) {
            return BigDecimal.ZERO;
        }
        
@@ -435,14 +635,17 @@
                String.format("%.3f", gapDistance));
        }
        
        // 2. 再计算当前段内部的距离
        BigDecimal segmentInternalDistance;
        if ("tianditu".equalsIgnoreCase(calculateMethod)) {
            segmentInternalDistance = calculateDistanceByTianditu(gpsList);
        } else {
            segmentInternalDistance = calculateDistanceByHaversine(gpsList);
        // 2. 再计算当前段内部的距离(如果有2个或以上GPS点)
        if (gpsList.size() >= 2) {
            BigDecimal segmentInternalDistance;
            if ("tianditu".equalsIgnoreCase(calculateMethod)) {
                segmentInternalDistance = calculateDistanceByTianditu(gpsList);
            } else {
                segmentInternalDistance = calculateDistanceByHaversine(gpsList);
            }
            totalDistance = totalDistance.add(segmentInternalDistance);
        }
        totalDistance = totalDistance.add(segmentInternalDistance);
        // 如果只有1个点,段内距离为0,只计算跨段距离
        
        return totalDistance.setScale(3, RoundingMode.HALF_UP);
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleInfoServiceImpl.java
@@ -115,15 +115,28 @@
     * @param vehicleInfo 车辆信息
     * @return 结果
     */
    /**
     * 修改车辆信息
     * 注意:
     * - 如果需要更新部门关联,请设置 vehicleInfo.deptIds
     * - 如果不需要更新部门关联(仅更新车辆基本信息),请保持 vehicleInfo.deptIds = null
     * - 这样可以避免并发同步时的死锁问题
     *
     * @param vehicleInfo 车辆信息
     * @return 结果
     */
    @Override
    @Transactional
    public int updateVehicleInfo(VehicleInfo vehicleInfo) {
        // 先删除旧的关联关系
        vehicleInfoMapper.deleteVehicleDeptByVehicleId(vehicleInfo.getVehicleId());
        // 如果选择了多个分公司,保存到关联表
        if (vehicleInfo.getDeptIds() != null && !vehicleInfo.getDeptIds().isEmpty()) {
            insertVehicleDept(vehicleInfo);
        // 只有当 deptIds 不为 null 时才更新部门关联(避免不必要的锁竞争)
        if (vehicleInfo.getDeptIds() != null) {
            // 先删除旧的关联关系
            vehicleInfoMapper.deleteVehicleDeptByVehicleId(vehicleInfo.getVehicleId());
            // 如果选择了多个分公司,保存到关联表
            if (!vehicleInfo.getDeptIds().isEmpty()) {
                insertVehicleDept(vehicleInfo);
            }
        }
        
        return vehicleInfoMapper.updateVehicleInfo(vehicleInfo);
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleMileageStatsServiceImpl.java
@@ -246,6 +246,16 @@
        // 计算任务里程占比
        if (result.totalMileage.compareTo(BigDecimal.ZERO) > 0) {
            result.taskRatio = result.taskMileage.divide(result.totalMileage, 4, RoundingMode.HALF_UP);
            // 数据校验:占比应在0-1之间
            if (result.taskRatio.compareTo(BigDecimal.ONE) > 0) {
                logger.warn("任务里程占比异常: {} (任务里程:{}, 总里程:{}), 强制设为1.0",
                           result.taskRatio, result.taskMileage, result.totalMileage);
                result.taskRatio = BigDecimal.ONE;
            } else if (result.taskRatio.compareTo(BigDecimal.ZERO) < 0) {
                logger.warn("任务里程占比为负: {}, 强制设为0", result.taskRatio);
                result.taskRatio = BigDecimal.ZERO;
            }
        }
        
        // 保留两位小数
@@ -405,6 +415,17 @@
            BigDecimal taskRatio = BigDecimal.ZERO;
            if (totalMileage.compareTo(BigDecimal.ZERO) > 0) {
                taskRatio = taskMileage.divide(totalMileage, 4, RoundingMode.HALF_UP);
                // 数据校验:占比应在0-1之间,如果超出说明数据异常
                if (taskRatio.compareTo(BigDecimal.ONE) > 0) {
                    logger.warn("车辆ID: {} 日期: {} 任务里程占比异常: {} (任务里程:{}, 总里程:{}), 强制设为1.0",
                               vehicleId, statDate, taskRatio, taskMileage, totalMileage);
                    taskRatio = BigDecimal.ONE;
                } else if (taskRatio.compareTo(BigDecimal.ZERO) < 0) {
                    logger.warn("车辆ID: {} 日期: {} 任务里程占比为负: {}, 强制设为0",
                               vehicleId, statDate, taskRatio);
                    taskRatio = BigDecimal.ZERO;
                }
            }
            
            // 5. 查询或创建统计记录
ruoyi-system/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -231,7 +231,7 @@
    
    <!-- 根据编码列表查询分公司 -->
    <select id="selectBranchCompaniesByOrderCodes" resultMap="SysDeptResult">
        select dept_id, dept_name, parent_id, ancestors, service_order_class, dispatch_order_class
        select dept_id, dept_name, parent_id, ancestors, service_order_class, dispatch_order_class,departure_address, departure_longitude, departure_latitude
        from sys_dept
        where parent_id = 100
        and del_flag = '0'
ruoyi-system/src/main/resources/mapper/system/VehicleGpsSegmentMileageMapper.xml
@@ -154,4 +154,15 @@
    <select id="selectGpsCalculatedSegmentId" resultType="Long">
        SELECT segment_id FROM tb_vehicle_gps_calculated WHERE gps_id = #{gpsId} LIMIT 1
    </select>
    <!-- 查询车辆在指定时间之前最后一个已处理的GPS坐标ID -->
    <select id="selectLastCalculatedGpsId" resultType="Long">
        SELECT c.gps_id
        FROM tb_vehicle_gps_calculated c
        INNER JOIN tb_vehicle_gps g ON c.gps_id = g.gps_id
        WHERE c.vehicle_id = #{vehicleId}
          AND STR_TO_DATE(g.collect_time, '%Y-%m-%d %H:%i:%s') &lt; #{beforeTime}
        ORDER BY STR_TO_DATE(g.collect_time, '%Y-%m-%d %H:%i:%s') DESC
        LIMIT 1
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml
@@ -131,6 +131,7 @@
    <select id="selectVehicleInfoByPlateNumber" parameterType="String" resultMap="VehicleInfoResult">
        <include refid="selectVehicleInfoVo"/>
        where v.vehicle_no LIKE concat('%', #{plateNumber}, '%')
        limit 1
    </select>
    <select id="selectVehicleInfoByVehicleNo" parameterType="String" resultMap="VehicleInfoResult">
ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
@@ -77,12 +77,14 @@
                 s.data_source, s.create_time, s.update_time, vd.dept_id
    </select>
    <!-- 根据车辆ID和统计日期查询唯一统计记录 -->
    <select id="selectByVehicleIdAndDate" resultMap="VehicleMileageStatsResult">
        <include refid="selectVehicleMileageStatsVo"/>
        select s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
               s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
               s.data_source, s.create_time, s.update_time
        from tb_vehicle_mileage_stats s
        where s.vehicle_id = #{vehicleId} and s.stat_date = #{statDate}
        group by s.stats_id, s.vehicle_id, s.vehicle_no, s.stat_date, s.total_mileage, s.task_mileage,
                 s.non_task_mileage, s.task_ratio, s.gps_point_count, s.task_count, s.segment_count,
                 s.data_source, s.create_time, s.update_time, vd.dept_id
        limit 1
    </select>
    <select id="selectTaskTimeIntervals" resultMap="TaskTimeIntervalResult">
sql/dryad_payment_tables.sql
@@ -1,7 +1,6 @@
-- dryad-payment 支付模块数据表
-- 在主数据库 966120 中创建支付模块所需的表
USE `966120`;
-- 支付订单表
CREATE TABLE IF NOT EXISTS `pay_order` (
sql/fix_duplicate_vehicle_plates.sql
New file
@@ -0,0 +1,101 @@
-- ============================================
-- 修复重复车牌号问题
-- ============================================
-- 1. 查询重复的车牌号
SELECT
    vehicle_no,
    COUNT(*) as count,
    GROUP_CONCAT(vehicle_id ORDER BY vehicle_id) as vehicle_ids,
    GROUP_CONCAT(platform_code ORDER BY vehicle_id) as platform_codes,
    GROUP_CONCAT(create_time ORDER BY vehicle_id) as create_times
FROM tb_vehicle_info
GROUP BY vehicle_no
HAVING COUNT(*) > 1
ORDER BY count DESC;
-- 2. 查看具体重复记录的详情
SELECT
    vehicle_id,
    vehicle_no,
    platform_code,
    device_id,
    car_id,
    status,
    create_time,
    update_time
FROM tb_vehicle_info
WHERE vehicle_no IN (
    SELECT vehicle_no
    FROM tb_vehicle_info
    GROUP BY vehicle_no
    HAVING COUNT(*) > 1
)
ORDER BY vehicle_no, vehicle_id;
-- 3. 备份重复数据(在删除前)
CREATE TABLE IF NOT EXISTS tb_vehicle_info_duplicate_backup AS
SELECT * FROM tb_vehicle_info
WHERE vehicle_no IN (
    SELECT vehicle_no
    FROM tb_vehicle_info
    GROUP BY vehicle_no
    HAVING COUNT(*) > 1
);
-- 4. 删除重复记录(保留最早创建的记录)
-- 注意:执行前请先确认备份完成!
-- 取消下面的注释来执行删除操作:
/*
DELETE v1 FROM tb_vehicle_info v1
INNER JOIN tb_vehicle_info v2
WHERE v1.vehicle_no = v2.vehicle_no
  AND v1.vehicle_id > v2.vehicle_id;
*/
-- 5. 验证删除后的结果
SELECT
    vehicle_no,
    COUNT(*) as count
FROM tb_vehicle_info
GROUP BY vehicle_no
HAVING COUNT(*) > 1;
-- 6. 添加唯一索引(防止未来出现重复)
-- 注意:只有在确认没有重复数据后才能执行!
-- 取消下面的注释来添加唯一索引:
/*
ALTER TABLE tb_vehicle_info
ADD UNIQUE INDEX uk_vehicle_no (vehicle_no);
*/
-- 7. 查询统计信息
SELECT
    '总车辆数' as item,
    COUNT(*) as count
FROM tb_vehicle_info
UNION ALL
SELECT
    '唯一车牌数' as item,
    COUNT(DISTINCT vehicle_no) as count
FROM tb_vehicle_info
UNION ALL
SELECT
    'CMS平台车辆' as item,
    COUNT(*) as count
FROM tb_vehicle_info
WHERE platform_code = 'CMS'
UNION ALL
SELECT
    'GPS51平台车辆' as item,
    COUNT(*) as count
FROM tb_vehicle_info
WHERE platform_code = 'GPS51'
UNION ALL
SELECT
    'LEGACY平台车辆' as item,
    COUNT(*) as count
FROM tb_vehicle_info
WHERE platform_code = 'LEGACY';
sql/fixes/fix_duplicate_mileage_stats.sql
New file
@@ -0,0 +1,35 @@
-- ============================================
-- 修复车辆里程统计表中的重复数据
-- 问题: 同一车辆同一天存在多条统计记录
-- 原因: 历史数据或并发插入导致
-- 解决: 保留stats_id最大的记录,删除其他重复记录
-- ============================================
-- 1. 查看重复数据
SELECT vehicle_id, stat_date, COUNT(*) as count
FROM tb_vehicle_mileage_stats
GROUP BY vehicle_id, stat_date
HAVING COUNT(*) > 1
ORDER BY count DESC;
-- 2. 删除重复数据(保留stats_id最大的记录)
DELETE FROM tb_vehicle_mileage_stats
WHERE stats_id NOT IN (
    SELECT max_id FROM (
        SELECT MAX(stats_id) as max_id
        FROM tb_vehicle_mileage_stats
        GROUP BY vehicle_id, stat_date
    ) as temp
);
-- 3. 验证清理结果
SELECT vehicle_id, stat_date, COUNT(*) as count
FROM tb_vehicle_mileage_stats
GROUP BY vehicle_id, stat_date
HAVING COUNT(*) > 1;
-- 4. 确认唯一索引存在
SHOW INDEX FROM tb_vehicle_mileage_stats WHERE Key_name = 'uk_vehicle_date';
-- 5. 如果唯一索引不存在,则创建
-- ALTER TABLE tb_vehicle_mileage_stats ADD UNIQUE KEY `uk_vehicle_date` (`vehicle_id`, `stat_date`);
sql/fixes/fix_task_ratio_column.sql
New file
@@ -0,0 +1,26 @@
-- ============================================
-- 修复 task_ratio 字段范围限制问题
-- 问题: decimal(5,4) 最大值为 9.9999,可能不足以存储异常数据
-- 解决: 扩大为 decimal(6,4),最大值 99.9999
-- ============================================
-- 1. 查看当前字段定义
DESC tb_vehicle_mileage_stats;
-- 2. 修改字段类型
ALTER TABLE tb_vehicle_mileage_stats
MODIFY COLUMN task_ratio DECIMAL(6,4) DEFAULT 0.0000 COMMENT '任务里程占比(0-1,异常时可能超出)';
-- 3. 验证修改结果
DESC tb_vehicle_mileage_stats;
-- 4. 查询超出正常范围的数据(占比应在0-1之间)
SELECT stats_id, vehicle_id, vehicle_no, stat_date,
       total_mileage, task_mileage, task_ratio
FROM tb_vehicle_mileage_stats
WHERE task_ratio > 1.0 OR task_ratio < 0.0
ORDER BY task_ratio DESC;
-- 5. 修复异常数据(可选)
-- UPDATE tb_vehicle_mileage_stats SET task_ratio = 1.0 WHERE task_ratio > 1.0;
-- UPDATE tb_vehicle_mileage_stats SET task_ratio = 0.0 WHERE task_ratio < 0.0;
sql/gps_compensation_config.sql
New file
@@ -0,0 +1,15 @@
-- GPS分段里程补偿计算配置
-- 用于配置服务启动时的补偿计算回溯天数
-- 插入补偿回溯天数配置(默认7天)
INSERT INTO sys_config (config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark)
VALUES
('GPS里程补偿回溯天数', 'gps.mileage.compensation.days', '7', 'N', 'admin', NOW(), 'admin', NOW(),
 '服务启动时补偿计算的回溯天数,检查并处理该时间范围内未被计算的GPS坐标。默认7天。');
-- 说明:
-- 1. gps.mileage.compensation.days 控制服务启动时自动补偿计算的回溯天数
-- 2. 补偿机制会在服务启动30秒后自动执行
-- 3. 补偿计算会查找每辆车未被处理的GPS坐标,并与最后一个已处理的坐标一起计算距离
-- 4. 已处理过的GPS坐标会被记录到 tb_vehicle_gps_calculated 表中,避免重复计算
-- 5. 如果不希望启动时执行补偿计算,可将此配置值设为0
sql/resync_vehicle_personnel_job.sql
@@ -11,7 +11,7 @@
-- 重新同步车辆和人员变更的任务到旧系统
INSERT INTO sys_job (job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark)
VALUES 
('重新同步车辆人员变更', 'DEFAULT', 'legacySystemSyncTask.resyncVehicleAndPersonnel', '0 0/5 * * * ?', '3', '1', '0', 'admin', sysdate(),
('同步服务调度到旧系统变更', 'DEFAULT', 'legacySystemSyncTask.resyncVehicleAndPersonnel', '0 0/5 * * * ?', '3', '1', '0', 'admin', sysdate(),
'每5分钟自动重新同步车辆、人员、地址、成交价发生变更的任务到旧系统。当任务的车辆、人员、地址或费用被修改后,会标记need_resync=1,定时任务会将这些变更同步到旧系统调度单表。');
-- 说明:
sql/updates/add_segment_count_to_mileage_stats.sql
@@ -1,7 +1,7 @@
-- 为车辆里程统计表添加分段数量字段
-- 用途:记录当日关联的GPS分段数量,用于数据完整性校验和业务监控
USE `ry-vue`;
-- USE `ry-vue`;
-- 检查字段是否存在,如果不存在则添加
SET @dbname = DATABASE();
@@ -24,3 +24,6 @@
-- 验证字段是否添加成功
DESC tb_vehicle_mileage_stats;
alter table tb_vehicle_mileage_stats add column segment_count int(11) default 0 comment '关联的分段数量' after task_count;
sql/updates/add_task_id_to_segment_mileage.sql
@@ -1,7 +1,7 @@
-- 为车辆GPS分段里程表添加任务关联字段
-- 用途:在计算GPS里程时,自动关联车辆正在执行的任务,方便统计任务里程
USE ry-vue;
-- USE ry-vue;
-- 添加任务ID字段
ALTER TABLE tb_vehicle_gps_segment_mileage