wlzboy
2025-11-07 2aebbc9601ab439707f69b08e467808df9f7549c
feat: weixin
13个文件已修改
16个文件已添加
16个文件已删除
4764 ■■■■ 已修改文件
app/api/task.js 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/pages/task/detail.vue 514 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/clean.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/package.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/run.bat 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/966120API接口规范.pdf 补丁 | 查看 | 原始文档 | blame | 历史
doc/966120生成服务单接口.pdf 补丁 | 查看 | 原始文档 | blame | 历史
doc/java8.rar 补丁 | 查看 | 原始文档 | blame | 历史
doc/jdk-8u291-windows-x64.exe 补丁 | 查看 | 原始文档 | blame | 历史
doc/sqlncli.msi 补丁 | 查看 | 原始文档 | blame | 历史
doc/广州非急救转运对接第三方接口文档(1).docx 补丁 | 查看 | 原始文档 | blame | 历史
doc/广州非急救转运对接第三方接口文档(1).pdf 补丁 | 查看 | 原始文档 | blame | 历史
doc/若依环境使用手册.docx 补丁 | 查看 | 原始文档 | blame | 历史
doc/订单状态回调接口文档.docx 补丁 | 查看 | 原始文档 | blame | 历史
doc/订单详情.png 补丁 | 查看 | 原始文档 | blame | 历史
doc/订单详情.rp 补丁 | 查看 | 原始文档 | blame | 历史
docs/任务状态变更GPS定位功能说明.md 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/地理编码服务使用说明.md 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/imagedata/ImageDataController.java 1074 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/wechat/WechatController.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-dev.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/config/WechatConfig.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/config/WechatMpConfig.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/InputStreamBase64Converter.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/ImageData.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/ImageTypeEnum.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/file/FileUploadResponse.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/file/FileUploadServiceImpl.java 549 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/file/IFileUploadService.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/IImageDataService.java 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/ImageDataServiceImpl.java 644 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/WxImageUploadRequest.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/ImageDataMapper.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/ImageDataMapper.xml 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/update_attachment_category.sql 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/api/task.js
@@ -61,11 +61,14 @@
}
// é™„件管理API
export function uploadAttachment(taskId, file) {
export function uploadAttachment(taskId, file, category) {
  const formData = new FormData()
  formData.append('file', file)
  if (category) {
    formData.append('category', category)
  }
  return request({
    url: '/task/' + taskId + '/attachment',
    url: '/task/attachment/upload/' + taskId,
    method: 'post',
    data: formData,
    headers: {
@@ -74,6 +77,24 @@
  })
}
export function uploadAttachmentFromWechat(taskId, mediaId, category) {
  return request({
    url: '/task/attachment/uploadFromWechat/' + taskId,
    method: 'post',
    params: {
      mediaId: mediaId,
      category: category
    }
  })
}
export function getAttachmentList(taskId) {
  return request({
    url: '/task/attachment/list/' + taskId,
    method: 'get'
  })
}
export function deleteAttachment(attachmentId) {
  return request({
    url: '/task/attachment/' + attachmentId,
@@ -81,6 +102,14 @@
  })
}
// èŽ·å–å¾®ä¿¡AccessToken
export function getWechatAccessToken() {
  return request({
    url: '/wechat/accessToken',
    method: 'get'
  })
}
// ç»Ÿè®¡API
export function getTaskStatistics() {
  return request({
app/pages/task/detail.vue
@@ -194,6 +194,35 @@
        </view>
      </view>
      
      <!-- é™„件信息 -->
      <view class="detail-section">
        <view class="section-title">
          ä»»åС附件
          <button class="upload-btn" @click="showUploadDialog">上传附件</button>
        </view>
        <view v-if="attachmentList && attachmentList.length > 0">
          <view class="attachment-item" v-for="(item, index) in attachmentList" :key="item.attachmentId">
            <view class="attachment-info">
              <view class="attachment-category">
                <text class="category-tag">{{ getCategoryName(item.attachmentCategory) }}</text>
              </view>
              <view class="attachment-name">{{ item.fileName }}</view>
              <view class="attachment-meta">
                <text class="upload-time">{{ formatTime(item.uploadTime) }}</text>
                <text class="file-size">{{ formatFileSize(item.fileSize) }}</text>
              </view>
            </view>
            <view class="attachment-actions">
              <button class="action-btn view-btn" @click="viewAttachment(item)">查看</button>
              <button class="action-btn delete-btn" @click="deleteAttachment(item.attachmentId, index)">删除</button>
            </view>
          </view>
        </view>
        <view v-else class="no-attachment">
          <text>暂无附件</text>
        </view>
      </view>
      <!-- ç¦ç¥‰è½¦ä»»åŠ¡ç‰¹æœ‰ä¿¡æ¯ -->
      <view class="detail-section" v-if="taskDetail.taskType === 'WELFARE' && taskDetail.welfareInfo">
        <view class="section-title">乘客信息</view>
@@ -314,18 +343,66 @@
      
      <!-- å·²å®Œæˆ/已取消: ä¸æ˜¾ç¤ºæŒ‰é’® -->
    </view>
    <!-- é™„件上传对话框 -->
    <uni-popup ref="uploadPopup" type="bottom">
      <view class="upload-dialog">
        <view class="dialog-header">
          <text class="dialog-title">上传附件</text>
          <uni-icons type="closeempty" size="24" @click="closeUploadDialog"></uni-icons>
        </view>
        <view class="dialog-content">
          <view class="form-item">
            <view class="form-label">附件分类</view>
            <picker @change="onCategoryChange" :value="selectedCategoryIndex" :range="categoryList" range-key="label">
              <view class="picker-value">
                {{ categoryList[selectedCategoryIndex].label }}
                <uni-icons type="arrowdown" size="16"></uni-icons>
              </view>
            </picker>
          </view>
          <view class="form-item">
            <view class="form-label">选择图片</view>
            <button class="choose-image-btn" @click="chooseImage">
              <uni-icons type="image" size="20"></uni-icons>
              <text>点击选择</text>
            </button>
          </view>
          <view class="preview-area" v-if="tempImagePath">
            <image :src="tempImagePath" mode="aspectFit" class="preview-image"></image>
          </view>
        </view>
        <view class="dialog-footer">
          <button class="cancel-btn" @click="closeUploadDialog">取消</button>
          <button class="confirm-btn" @click="confirmUpload" :disabled="!tempImagePath">确定上传</button>
        </view>
      </view>
    </uni-popup>
  </view>
</template>
<script>
  import { getTask, changeTaskStatus } from '@/api/task'
  import { getAttachmentList, uploadAttachmentFromWechat, deleteAttachment, getWechatAccessToken } from '@/api/task'
  import { formatDateTime } from '@/utils/common'
  
  export default {
    data() {
      return {
        taskDetail: null,
        taskId: null
        taskId: null,
        attachmentList: [],
        categoryList: [
          { label: '知情同意书', value: '1' },
          { label: '病人资料', value: '2' },
          { label: '操作记录', value: '3' },
          { label: '出车前', value: '4' },
          { label: '出车后', value: '5' },
          { label: '系安全带', value: '6' }
        ],
        selectedCategoryIndex: 0,
        tempImagePath: null,
        isWechatMiniProgram: false // æ˜¯å¦æ˜¯å¾®ä¿¡å°ç¨‹åºçŽ¯å¢ƒ
      }
    },
    computed: {
@@ -387,6 +464,12 @@
    onLoad(options) {
      this.taskId = options.id
      this.loadTaskDetail()
      this.loadAttachmentList()
      // æ£€æµ‹æ˜¯å¦æ˜¯å¾®ä¿¡å°ç¨‹åºçŽ¯å¢ƒ
      // #ifdef MP-WEIXIN
      this.isWechatMiniProgram = true
      // #endif
    },
    methods: {
      // åŠ è½½ä»»åŠ¡è¯¦æƒ…
@@ -592,6 +675,249 @@
            })
          }
        })
      },
      // åŠ è½½é™„ä»¶åˆ—è¡¨
      loadAttachmentList() {
        if (!this.taskId) {
          return
        }
        getAttachmentList(this.taskId).then(response => {
          this.attachmentList = response.data || response || []
        }).catch(error => {
          console.error('加载附件列表失败:', error)
        })
      },
      // æ˜¾ç¤ºä¸Šä¼ å¯¹è¯æ¡†
      showUploadDialog() {
        this.selectedCategoryIndex = 0
        this.tempImagePath = null
        this.$refs.uploadPopup.open()
      },
      // å…³é—­ä¸Šä¼ å¯¹è¯æ¡†
      closeUploadDialog() {
        this.$refs.uploadPopup.close()
      },
      // åˆ†ç±»é€‰æ‹©å˜åŒ–
      onCategoryChange(e) {
        this.selectedCategoryIndex = e.detail.value
      },
      // é€‰æ‹©å›¾ç‰‡
      chooseImage() {
        const that = this
        uni.chooseImage({
          count: 1,
          sizeType: ['compressed'],
          sourceType: ['album', 'camera'],
          success: function(res) {
            that.tempImagePath = res.tempFilePaths[0]
          },
          fail: function(err) {
            console.error('选择图片失败:', err)
            that.$modal.showToast('选择图片失败')
          }
        })
      },
      // ç¡®è®¤ä¸Šä¼ 
      confirmUpload() {
        if (!this.tempImagePath) {
          this.$modal.showToast('请先选择图片')
          return
        }
        const that = this
        const category = this.categoryList[this.selectedCategoryIndex].value
        // å¾®ä¿¡å°ç¨‹åºçŽ¯å¢ƒï¼šå…ˆèŽ·å–AccessToken,再上传到微信服务器,最后提交mediaId到后端
        // #ifdef MP-WEIXIN
        if (this.isWechatMiniProgram) {
          uni.showLoading({
            title: '上传中...'
          })
          // ç¬¬ä¸€æ­¥ï¼šä»ŽåŽç«¯èŽ·å–AccessToken
          getWechatAccessToken().then(tokenResponse => {
            const accessToken = tokenResponse.data || tokenResponse
            if (!accessToken) {
              uni.hideLoading()
              that.$modal.showToast('获取AccessToken失败')
              return
            }
            // ç¬¬äºŒæ­¥ï¼šä¸Šä¼ åˆ°å¾®ä¿¡æœåС噍
            const uploadUrl = `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${accessToken}&type=image`
            uni.uploadFile({
              url: uploadUrl,
              filePath: that.tempImagePath,
              name: 'media',
              success: function(res) {
                console.log('微信上传响应:', res)
                try {
                  const data = JSON.parse(res.data)
                  if (data.media_id) {
                    // ç¬¬ä¸‰æ­¥ï¼šæäº¤mediaId到后端
                    uploadAttachmentFromWechat(that.taskId, data.media_id, category).then(response => {
                      uni.hideLoading()
                      that.$modal.showToast('上传成功')
                      that.closeUploadDialog()
                      that.loadAttachmentList()
                    }).catch(error => {
                      uni.hideLoading()
                      console.error('提交mediaId失败:', error)
                      that.$modal.showToast('上传失败:' + (error.msg || '请重试'))
                    })
                  } else {
                    uni.hideLoading()
                    const errMsg = data.errmsg || '未知错误'
                    console.error('微信返回错误:', data)
                    that.$modal.showToast('微信上传失败:' + errMsg)
                  }
                } catch (e) {
                  uni.hideLoading()
                  console.error('解析微信响应失败:', e, res.data)
                  that.$modal.showToast('上传失败:响应解析错误')
                }
              },
              fail: function(err) {
                uni.hideLoading()
                console.error('上传到微信失败:', err)
                that.$modal.showToast('上传失败:' + (err.errMsg || '请检查网络'))
              }
            })
          }).catch(error => {
            uni.hideLoading()
            console.error('获取AccessToken失败:', error)
            that.$modal.showToast('获取AccessToken失败')
          })
          return
        }
        // #endif
        // éžå¾®ä¿¡å°ç¨‹åºçŽ¯å¢ƒï¼šç›´æŽ¥ä¸Šä¼ åˆ°åŽç«¯æœåŠ¡å™¨
        uni.showLoading({
          title: '上传中...'
        })
        uni.uploadFile({
          url: that.$baseUrl + '/task/attachment/upload/' + that.taskId,
          filePath: that.tempImagePath,
          name: 'file',
          formData: {
            'category': category
          },
          header: {
            'Authorization': 'Bearer ' + uni.getStorageSync('token')
          },
          success: function(uploadRes) {
            uni.hideLoading()
            if (uploadRes.statusCode === 200) {
              const result = JSON.parse(uploadRes.data)
              if (result.code === 200) {
                that.$modal.showToast('上传成功')
                that.closeUploadDialog()
                that.loadAttachmentList()
              } else {
                that.$modal.showToast(result.msg || '上传失败')
              }
            } else {
              that.$modal.showToast('上传失败')
            }
          },
          fail: function(err) {
            uni.hideLoading()
            console.error('上传失败:', err)
            that.$modal.showToast('上传失败')
          }
        })
      },
      // æŸ¥çœ‹é™„ä»¶
      viewAttachment(item) {
        // å¦‚果是图片,使用图片预览
        const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp']
        const fileExt = item.fileName.split('.').pop().toLowerCase()
        if (imageTypes.includes(fileExt)) {
          // æž„建图片访问地址
          // å¦‚果是filePath是完整路径,需要通过下载接口访问
          const imageUrl = this.$baseUrl + '/task/attachment/download/' + item.attachmentId
          // å¾®ä¿¡å°ç¨‹åºä¸­é¢„览图片
          // #ifdef MP-WEIXIN
          // å¾®ä¿¡å°ç¨‹åºéœ€è¦å…ˆä¸‹è½½åˆ°æœ¬åœ°å†é¢„览
          uni.showLoading({ title: '加载中...' })
          uni.downloadFile({
            url: imageUrl,
            success: function(res) {
              uni.hideLoading()
              if (res.statusCode === 200) {
                uni.previewImage({
                  urls: [res.tempFilePath],
                  current: res.tempFilePath
                })
              } else {
                uni.showToast({ title: '加载图片失败', icon: 'none' })
              }
            },
            fail: function() {
              uni.hideLoading()
              uni.showToast({ title: '下载失败', icon: 'none' })
            }
          })
          // #endif
          // éžå¾®ä¿¡å°ç¨‹åºçŽ¯å¢ƒï¼Œç›´æŽ¥é¢„è§ˆ
          // #ifndef MP-WEIXIN
          uni.previewImage({
            urls: [imageUrl],
            current: imageUrl
          })
          // #endif
        } else {
          this.$modal.showToast('仅支持预览图片')
        }
      },
      // åˆ é™¤é™„ä»¶
      deleteAttachment(attachmentId, index) {
        const that = this
        this.$modal.confirm('确定要删除该附件吗?').then(() => {
          deleteAttachment(attachmentId).then(response => {
            that.$modal.showToast('删除成功')
            that.attachmentList.splice(index, 1)
          }).catch(error => {
            console.error('删除附件失败:', error)
            that.$modal.showToast('删除失败')
          })
        }).catch(() => {})
      },
      // èŽ·å–åˆ†ç±»åç§°
      getCategoryName(category) {
        const item = this.categoryList.find(c => c.value === category)
        return item ? item.label : '未分类'
      },
      // æ ¼å¼åŒ–æ—¶é—´
      formatTime(time) {
        if (!time) return ''
        return formatDateTime(time, 'YYYY-MM-DD HH:mm')
      },
      // æ ¼å¼åŒ–文件大小
      formatFileSize(size) {
        if (!size) return '0B'
        if (size < 1024) return size + 'B'
        if (size < 1024 * 1024) return (size / 1024).toFixed(2) + 'KB'
        return (size / 1024 / 1024).toFixed(2) + 'MB'
      }
    }
  }
@@ -647,6 +973,18 @@
        color: #333;
        border-bottom: 1rpx solid #f0f0f0;
        padding-bottom: 10rpx;
        display: flex;
        justify-content: space-between;
        align-items: center;
        .upload-btn {
          font-size: 24rpx;
          padding: 8rpx 20rpx;
          background-color: #007AFF;
          color: white;
          border-radius: 8rpx;
          border: none;
        }
      }
      
      .info-item {
@@ -694,6 +1032,84 @@
        background-color: #f9f9f9;
        padding: 20rpx;
        border-radius: 10rpx;
      }
      .no-attachment {
        text-align: center;
        padding: 40rpx 0;
        color: #999;
        font-size: 28rpx;
      }
      .attachment-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 20rpx;
        margin-bottom: 15rpx;
        background-color: #f9f9f9;
        border-radius: 10rpx;
        &:last-child {
          margin-bottom: 0;
        }
        .attachment-info {
          flex: 1;
          margin-right: 20rpx;
          .attachment-category {
            margin-bottom: 8rpx;
            .category-tag {
              display: inline-block;
              padding: 4rpx 12rpx;
              background-color: #007AFF;
              color: white;
              font-size: 22rpx;
              border-radius: 4rpx;
            }
          }
          .attachment-name {
            font-size: 28rpx;
            color: #333;
            margin-bottom: 8rpx;
            word-break: break-all;
          }
          .attachment-meta {
            font-size: 24rpx;
            color: #999;
            .upload-time {
              margin-right: 20rpx;
            }
          }
        }
        .attachment-actions {
          display: flex;
          flex-direction: column;
          gap: 10rpx;
          .action-btn {
            padding: 8rpx 20rpx;
            font-size: 24rpx;
            border-radius: 6rpx;
            border: none;
            &.view-btn {
              background-color: #007AFF;
              color: white;
            }
            &.delete-btn {
              background-color: #ff3b30;
              color: white;
            }
          }
        }
      }
    }
    
@@ -749,5 +1165,101 @@
        }
      }
    }
    .upload-dialog {
      background-color: white;
      border-radius: 20rpx 20rpx 0 0;
      padding: 30rpx;
      .dialog-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 30rpx;
        .dialog-title {
          font-size: 32rpx;
          font-weight: bold;
          color: #333;
        }
      }
      .dialog-content {
        .form-item {
          margin-bottom: 30rpx;
          .form-label {
            font-size: 28rpx;
            color: #333;
            margin-bottom: 15rpx;
          }
          .picker-value {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20rpx;
            background-color: #f5f5f5;
            border-radius: 10rpx;
            font-size: 28rpx;
            color: #333;
          }
          .choose-image-btn {
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 30rpx;
            background-color: #f5f5f5;
            border-radius: 10rpx;
            border: 2rpx dashed #ccc;
            color: #666;
            font-size: 28rpx;
            text {
              margin-left: 10rpx;
            }
          }
        }
        .preview-area {
          margin-top: 20rpx;
          .preview-image {
            width: 100%;
            height: 400rpx;
            border-radius: 10rpx;
          }
        }
      }
      .dialog-footer {
        display: flex;
        gap: 20rpx;
        margin-top: 30rpx;
        button {
          flex: 1;
          height: 80rpx;
          border-radius: 10rpx;
          font-size: 30rpx;
          border: none;
        }
        .cancel-btn {
          background-color: #f5f5f5;
          color: #666;
        }
        .confirm-btn {
          background-color: #007AFF;
          color: white;
          &:disabled {
            background-color: #ccc;
          }
        }
      }
    }
  }
</style>
bin/clean.bat
File was deleted
bin/package.bat
File was deleted
bin/run.bat
File was deleted
doc/966120API½Ó¿Ú¹æ·¶.pdf
Binary files differ
doc/966120Éú³É·þÎñµ¥½Ó¿Ú.pdf
Binary files differ
doc/java8.rar
Binary files differ
doc/jdk-8u291-windows-x64.exe
Binary files differ
doc/sqlncli.msi
Binary files differ
doc/¹ãÖݷǼ±¾ÈתÔ˶ԽӵÚÈý·½½Ó¿ÚÎĵµ(1).docx
Binary files differ
doc/¹ãÖݷǼ±¾ÈתÔ˶ԽӵÚÈý·½½Ó¿ÚÎĵµ(1).pdf
Binary files differ
doc/ÈôÒÀ»·¾³Ê¹ÓÃÊÖ²á.docx
Binary files differ
doc/¶©µ¥×´Ì¬»Øµ÷½Ó¿ÚÎĵµ.docx
Binary files differ
doc/¶©µ¥ÏêÇé.png
Binary files differ
doc/¶©µ¥ÏêÇé.rp
Binary files differ
docs/ÈÎÎñ״̬±ä¸üGPS¶¨Î»¹¦ÄÜ˵Ã÷.md
File was deleted
docs/µØÀí±àÂë·þÎñʹÓÃ˵Ã÷.md
File was deleted
ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java
@@ -16,7 +16,7 @@
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  è‹¥ä¾å¯åŠ¨æˆåŠŸ   áƒš(´ڡ`ლ)゙  \n" +
        System.out.println("(♥◠‿◠)ノ゙  æ°‘航启动成功   áƒš(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java
@@ -8,13 +8,13 @@
import com.ruoyi.common.utils.WechatUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.config.WechatMpConfig;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.system.domain.EvaluationDetail;
import com.ruoyi.system.service.IEvaluationDimensionService;
import com.ruoyi.system.domain.EvaluationDimension;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@@ -47,6 +47,9 @@
    @Autowired
    private IEvaluationDimensionService evaluationDimensionService;
    @Autowired
    private WechatMpConfig wechatMpConfig;
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„价列表
@@ -150,12 +153,6 @@
        }
    }
    @Value("${wechat.appId}")
    private String wechatAppId;
    @Value("${wechat.appSecret}")
    private String wechatAppSecret;
    /**
     * ç”Ÿæˆå¾®ä¿¡æŽˆæƒURL
     */
@@ -170,7 +167,12 @@
            logger.info("生成微信授权URL - åŽŸå§‹redirectUri: {}", redirectUri);
            
            // ç”Ÿæˆå¾®ä¿¡æŽˆæƒURL,使用snsapi_userinfo获取用户信息
            String authUrl = WechatUtils.generateAuthUrl(wechatAppId, redirectUri, "snsapi_userinfo", state);
            String authUrl = WechatUtils.generateAuthUrl(
                wechatMpConfig.getAppId(),
                redirectUri,
                "snsapi_userinfo",
                state
            );
            if (authUrl == null) {
                return error("生成微信授权URL失败");
            }
@@ -180,7 +182,7 @@
            Map<String, String> result = new HashMap<>();
            result.put("authUrl", authUrl);
            result.put("originalRedirectUri", redirectUri);
            result.put("appId", wechatAppId);
            result.put("appId", wechatMpConfig.getAppId());
            return success(result);
        } catch (Exception e) {
            logger.error("生成微信授权URL失败", e);
@@ -201,7 +203,11 @@
            }
            // èŽ·å–ç½‘é¡µæŽˆæƒAccess Token
            JSONObject tokenInfo = WechatUtils.getWebAccessToken(wechatAppId, wechatAppSecret, code);
            JSONObject tokenInfo = WechatUtils.getWebAccessToken(
                wechatMpConfig.getAppId(),
                wechatMpConfig.getAppSecret(),
                code
            );
            if (tokenInfo == null) {
                return error("获取微信授权信息失败");
            }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/imagedata/ImageDataController.java
New file
@@ -0,0 +1,1074 @@
package com.ruoyi.web.controller.imagedata;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.config.LegacySystemConfig;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.InputStreamBase64Converter;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.file.FileUploadResponse;
import com.ruoyi.system.file.IFileUploadService;
import com.ruoyi.system.imagedata.IImageDataService;
import com.ruoyi.system.domain.ImageData;
import com.ruoyi.system.domain.enums.ImageTypeEnum;
import com.ruoyi.system.imagedata.WxImageUploadRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.util.*;
import java.util.List;
/**
 * å›¾ç‰‡æ•°æ®æŽ§åˆ¶å™¨
 *
 * @author mhyl
 * @date 2024-01-01
 */
@RestController
@DataSource(DataSourceType.SQLSERVER)
@RequestMapping("/hospital/imagedata")
public class ImageDataController extends BaseController {
    private static final Logger log = LoggerFactory.getLogger(ImageDataController.class);
    @Autowired
    private IImageDataService imageDataService;
    @Autowired
    private IFileUploadService fileUploadService;
    @Autowired
    private LegacySystemConfig legacyConfig;
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®åˆ—表
     */
//    @PreAuthorize("@ss.hasPermi('hospital:imagedata:list')")
    @GetMapping("/list")
    public TableDataInfo list(ImageData imageData) {
//        startPage();
        List<ImageData> list = imageDataService.selectImageDataList(imageData);
        return getDataTable(list);
    }
    /**
     * å¯¼å‡ºå›¾ç‰‡æ•°æ®åˆ—表
     */
    @Log(title = "图片数据", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, ImageData imageData) {
        List<ImageData> list = imageDataService.selectImageDataList(imageData);
        ExcelUtil<ImageData> util = new ExcelUtil<ImageData>(ImageData.class);
        util.exportExcel(response, list, "图片数据数据");
    }
    /**
     * èŽ·å–å›¾ç‰‡æ•°æ®è¯¦ç»†ä¿¡æ¯
     */
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id) {
        return AjaxResult.success(imageDataService.selectImageDataById(id));
    }
    /**
     * æ–°å¢žå›¾ç‰‡æ•°æ®
     */
    @Log(title = "图片数据", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody ImageData imageData) {
        return toAjax(imageDataService.insertImageData(imageData));
    }
    /**
     * ä¿®æ”¹å›¾ç‰‡æ•°æ®
     */
    @Log(title = "图片数据", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody ImageData imageData) {
        return toAjax(imageDataService.updateImageData(imageData));
    }
    /**
     * åˆ é™¤å›¾ç‰‡æ•°æ®
     */
    @Log(title = "图片数据", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ids}")
    public AjaxResult remove(@PathVariable Long[] ids) {
        return toAjax(imageDataService.deleteImageDataByIds(ids));
    }
    /**
     * æ ¹æ®è°ƒåº¦å•ID查询图片数据
     */
    @GetMapping("/byDispatchOrder/{dispatchOrdID}")
    public AjaxResult getByDispatchOrder(@PathVariable("dispatchOrdID") Long dispatchOrdID) {
        List<ImageData> list = imageDataService.selectImageDataByDOrdIDDt(dispatchOrdID);
        return AjaxResult.success(list);
    }
    /**
     * æ ¹æ®è°ƒåº¦å•ID和图片类型获取有效图片(包含完整链接)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡æ•°æ®ï¼ˆåŒ…含完整链接)
     */
//    @PreAuthorize("@ss.hasPermi('hospital:imagedata:query')")
    @Anonymous()
    @GetMapping("/byDispatchOrderAndType/{dispatchOrdID}/{imageType}")
    public AjaxResult getByDispatchOrderAndType(@PathVariable("dispatchOrdID") Long dispatchOrdID,
                                                @PathVariable("imageType") Integer imageType) {
        try {
            // æŸ¥è¯¢æŒ‡å®šè°ƒåº¦å•和类型的有效图片
            List<ImageData> imageDataList = imageDataService.selectImageDataByDOrdIDDtAndType(dispatchOrdID, imageType);
            // è¿‡æ»¤æœ‰æ•ˆå›¾ç‰‡ï¼ˆæœªåˆ é™¤çš„)
            List<ImageData> validImages = imageDataList.stream()
                    .filter(img -> img.getImageDel() == null || img.getImageDel() == 0)
                    .collect(java.util.stream.Collectors.toList());
            // ä¸ºæ¯ä¸ªå›¾ç‰‡æ·»åŠ å®Œæ•´é“¾æŽ¥
            for (ImageData imageData : validImages) {
                // æ·»åŠ åŽŸå§‹å›¾ç‰‡å®Œæ•´é“¾æŽ¥
                if (StringUtils.hasText(imageData.getImageUrl())) {
                    String fullImageUrl = buildFullImageUrl(imageData.getImageUrl());
                    imageData.setImageUrl(fullImageUrl);
                }
                // æ·»åŠ ç¼©ç•¥å›¾å®Œæ•´é“¾æŽ¥
                if (StringUtils.hasText(imageData.getImageUrls())) {
                    String fullThumbnailUrl = buildFullImageUrl(imageData.getImageUrls());
                    imageData.setImageUrls(fullThumbnailUrl);
                }
            }
            return AjaxResult.success("获取图片成功", validImages);
        } catch (Exception e) {
            return AjaxResult.error("获取图片失败:" + e.getMessage());
        }
    }
    /**
     * æ ¹æ®è°ƒåº¦å•ID和图片类型获取有效图片(简化版,只返回图片链接)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡é“¾æŽ¥åˆ—表
     */
    // @PreAuthorize("@ss.hasPermi('hospital:imagedata:query')")
    @Anonymous()
    @GetMapping("/links/byDispatchOrderAndType/{dispatchOrdID}/{imageType}")
    public AjaxResult getImageLinksByDispatchOrderAndType(@PathVariable("dispatchOrdID") Long dispatchOrdID,
                                                          @PathVariable("imageType") Integer imageType) {
        try {
            // æŸ¥è¯¢æŒ‡å®šè°ƒåº¦å•和类型的有效图片
            List<ImageData> imageDataList = imageDataService.selectImageDataByDOrdIDDtAndType(dispatchOrdID, imageType);
            // æž„建图片链接列表
            List<java.util.Map<String, String>> imageLinks = new java.util.ArrayList<>();
            for (ImageData imageData : imageDataList) {
                // åªå¤„理未删除的图片
                if (imageData.getImageDel() == null || imageData.getImageDel() == 0) {
                    java.util.Map<String, String> linkMap = new java.util.HashMap<>();
                    // åŽŸå§‹å›¾ç‰‡é“¾æŽ¥
                    if (StringUtils.hasText(imageData.getImageUrl())) {
                        linkMap.put("originalUrl", buildFullImageUrl(imageData.getImageUrl()));
                    }
                    // ç¼©ç•¥å›¾é“¾æŽ¥
                    if (StringUtils.hasText(imageData.getImageUrls())) {
                        linkMap.put("thumbnailUrl", buildFullImageUrl(imageData.getImageUrls()));
                    }
                    // å›¾ç‰‡ID
                    linkMap.put("imageId", String.valueOf(imageData.getId()));
                    // å›¾ç‰‡ç±»åž‹
                    linkMap.put("imageType", String.valueOf(imageData.getImageType()));
                    // ä¸Šä¼ æ—¶é—´
                    if (imageData.getUpImageTime() != null) {
                        linkMap.put("uploadTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, imageData.getUpImageTime()));
                    }
                    imageLinks.add(linkMap);
                }
            }
            return AjaxResult.success("获取图片链接成功", imageLinks);
        } catch (Exception e) {
            return AjaxResult.error("获取图片链接失败:" + e.getMessage());
        }
    }
    /**
     * æž„建完整的图片URL
     *
     * @param imagePath å›¾ç‰‡è·¯å¾„
     * @return å®Œæ•´çš„图片URL
     */
    private String buildFullImageUrl(String imagePath) {
        if (!StringUtils.hasText(imagePath)) {
            return "";
        }
        // å¦‚果已经是完整URL,直接返回
        if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
            return imagePath;
        }
        // ä»Žé…ç½®ä¸­è¯»å–文件服务器URL
        String fileServerUrl = legacyConfig.getFileServerUrl();
        // å¦‚果配置中没有设置,使用默认值
        if (!StringUtils.hasText(fileServerUrl)) {
            // è®°å½•警告日志
            System.err.println("警告:配置文件中未设置 legacy.system.fileServerUrl,使用默认值");
            fileServerUrl = "https://sync.966120.com.cn";
        }
        // ç¡®ä¿æ–‡ä»¶æœåС噍URL不以/结尾
        if (fileServerUrl.endsWith("/")) {
            fileServerUrl = fileServerUrl.substring(0, fileServerUrl.length() - 1);
        }
        // å¤„理图片路径:移除开头和结尾的空白字符
        imagePath = imagePath.trim();
        // ç§»é™¤å¼€å¤´å’Œç»“尾的斜杠
        while (imagePath.startsWith("/")) {
            imagePath = imagePath.substring(1);
        }
        while (imagePath.endsWith("/")) {
            imagePath = imagePath.substring(0, imagePath.length() - 1);
        }
        // å¤„理反斜杠:将反斜杠替换为正斜杠
        imagePath = imagePath.replace("\\", "/");
        // ç§»é™¤è·¯å¾„中可能的重复斜杠
        imagePath = imagePath.replaceAll("/+", "/");
        // ç¡®ä¿å›¾ç‰‡è·¯å¾„以/开头
        if (!imagePath.startsWith("/")) {
            imagePath = "/" + imagePath;
        }
        // æž„建完整URL
        String fullUrl = fileServerUrl + imagePath;
        // è®°å½•调试信息
        System.out.println("构建图片URL - é…ç½®URL: " + fileServerUrl + ", å›¾ç‰‡è·¯å¾„: " + imagePath + ", å®Œæ•´URL: " + fullUrl);
        return fullUrl;
    }
    /**
     * æ ¹æ®æœåŠ¡å•ID查询图片数据
     */
    @PreAuthorize("@ss.hasPermi('hospital:imagedata:query')")
    @GetMapping("/byServiceOrder/{serviceOrdID}")
    public AjaxResult getByServiceOrder(@PathVariable("serviceOrdID") Long serviceOrdID) {
        List<ImageData> list = imageDataService.selectImageDataBySOrdIDDt(serviceOrdID);
        return AjaxResult.success(list);
    }
    /**
     * æ ¹æ®å›¾ç‰‡ç±»åž‹æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     */
    @PreAuthorize("@ss.hasPermi('hospital:imagedata:query')")
    @GetMapping("/byType/{imageType}")
    public AjaxResult getByType(@PathVariable("imageType") Integer imageType) {
        List<ImageData> list = imageDataService.selectImageDataByType(imageType);
        return AjaxResult.success(list);
    }
    /**
     * æ ‡è®°å›¾ç‰‡ä¸ºåˆ é™¤çŠ¶æ€
     */
    @PreAuthorize("@ss.hasPermi('hospital:imagedata:remove')")
    @Log(title = "标记图片删除", businessType = BusinessType.DELETE)
    @PutMapping("/markDelete/{id}")
    public AjaxResult markDelete(@PathVariable("id") Long id) {
        return toAjax(imageDataService.markImageDataAsDeleted(id));
    }
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(原ASP代码转换)
     */
    @Log(title = "微信图片上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxImage")
    public AjaxResult uploadWxImage(@RequestBody WxImageUploadRequest request) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ID
        Integer adminId = getUserId().intValue();
        String result = imageDataService.uploadWxImage(
                request.getDispatchOrdID(),
                request.getServiceOrdID(),
                request.getOaid(),
                request.getMediaId(),
                request.getImageType(),
                adminId
        );
        if (result.contains("成功")) {
            return success(result);
        } else {
            return error(result);
        }
    }
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(兼容原有参数格式)
     */
    @Log(title = "微信图片上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxImageForm")
    public AjaxResult uploadWxImageForm(
            @RequestParam(value = "DispatchOrdID", required = false) Long dispatchOrdID,
            @RequestParam(value = "ServiceOrdID", required = false) Long serviceOrdID,
            @RequestParam(value = "OAID", required = false) Integer oaid,
            @RequestParam(value = "media_id", required = true) String mediaId,
            @RequestParam(value = "ImageType", required = false) Integer imageType) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ID
        Integer adminId = getUserId().intValue();
        String result = imageDataService.uploadWxImage(dispatchOrdID, serviceOrdID, oaid, mediaId, imageType, adminId);
        if (result.contains("成功")) {
            return success(result);
        } else {
            return error(result);
        }
    }
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(完整版本,包含文件下载和缩略图生成)
     */
    @Log(title = "微信图片上传完整版", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxImageWithDownload")
    public AjaxResult uploadWxImageWithDownload(@RequestBody WxImageUploadRequest request) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ID
        Integer adminId = getUserId().intValue();
        // è¿™é‡Œéœ€è¦ä»Žé…ç½®æˆ–请求中获取access_token
        // å®žé™…使用时应该从微信配置或缓存中获取
        String accessToken = request.getAccessToken(); // éœ€è¦åœ¨WxImageUploadRequest中添加此字段
        if (accessToken == null || accessToken.isEmpty()) {
            return error("缺少微信访问令牌");
        }
        String result = imageDataService.uploadWxImageWithDownload(
                accessToken,
                request.getMediaId(),
                request.getDispatchOrdID(),
                request.getOaid(),
                request.getImageType(),
                adminId
        );
        if (result.contains("成功")) {
            return success(result);
        } else {
            return error(result);
        }
    }
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(完整版本,兼容原有参数格式)
     */
    @Log(title = "微信图片上传完整版", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxImageWithDownloadForm")
    public AjaxResult uploadWxImageWithDownloadForm(
            @RequestParam(value = "access_token", required = true) String accessToken,
            @RequestParam(value = "media_id", required = true) String mediaId,
            @RequestParam(value = "DispatchOrdID", required = false) Long dispatchOrdID,
            @RequestParam(value = "ServiceOrdID", required = false) Long serviceOrdID,
            @RequestParam(value = "OAID", required = false) Integer oaid,
            @RequestParam(value = "ImageType", required = false) Integer imageType) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·ID
        Integer adminId = getUserId().intValue();
        String result = imageDataService.uploadWxImageWithDownload(
                accessToken, mediaId, dispatchOrdID, oaid, imageType, adminId);
        if (result.contains("成功")) {
            return success(result);
        } else {
            return error(result);
        }
    }
    /**
     * ç”Ÿæˆç¼©ç•¥å›¾
     */
    @Log(title = "生成缩略图", businessType = BusinessType.OTHER)
    @PostMapping("/createThumbnail")
    public AjaxResult createThumbnail(
            @RequestParam(value = "bigImgPath", required = true) String bigImgPath,
            @RequestParam(value = "width", required = true) int width,
            @RequestParam(value = "height", required = false, defaultValue = "0") int height,
            @RequestParam(value = "smallImgPath", required = true) String smallImgPath) {
        boolean result = imageDataService.createThumbnail(bigImgPath, width, height, smallImgPath);
        if (result) {
            return success("缩略图生成成功");
        } else {
            return error("缩略图生成失败");
        }
    }
    /**
     * æ£€æŸ¥æ–‡ä»¶å…¼å®¹æ€§
     */
    @GetMapping("/checkCompatibility")
    public AjaxResult checkFileCompatibility(@RequestParam("filePath") String filePath) {
        try {
            String result = imageDataService.checkFileCompatibility(filePath);
            return AjaxResult.success("兼容性检查完成", result);
        } catch (Exception e) {
            return AjaxResult.error("兼容性检查失败:" + e.getMessage());
        }
    }
    /**
     * éªŒè¯URL格式是否与旧系统兼容
     */
    @GetMapping("/checkUrlCompatibility")
    public AjaxResult checkUrlCompatibility(@RequestParam("url") String url) {
        try {
            boolean isCompatible = imageDataService.isUrlCompatible(url);
            return AjaxResult.success("URL兼容性检查完成", isCompatible);
        } catch (Exception e) {
            return AjaxResult.error("URL兼容性检查失败:" + e.getMessage());
        }
    }
    /**
     * ç”Ÿæˆä¸Žæ—§ç³»ç»Ÿå…¼å®¹çš„æ–‡ä»¶è·¯å¾„
     */
    @GetMapping("/generateCompatibleFilePath")
    public AjaxResult generateCompatibleFilePath(@RequestParam("dispatchOrdID") Long dispatchOrdID,
                                                 @RequestParam("mediaId") String mediaId,
                                                 @RequestParam(value = "isThumbnail", defaultValue = "false") boolean isThumbnail) {
        try {
            String filePath = imageDataService.generateCompatibleFilePath(dispatchOrdID, mediaId, isThumbnail);
            return AjaxResult.success("生成兼容文件路径成功", filePath);
        } catch (Exception e) {
            return AjaxResult.error("生成兼容文件路径失败:" + e.getMessage());
        }
    }
    /**
     * ç”Ÿæˆä¸Žæ—§ç³»ç»Ÿå…¼å®¹çš„访问URL
     */
    @GetMapping("/generateCompatibleUrl")
    public AjaxResult generateCompatibleUrl(@RequestParam("dispatchOrdID") Long dispatchOrdID,
                                            @RequestParam("mediaId") String mediaId,
                                            @RequestParam(value = "isThumbnail", defaultValue = "false") boolean isThumbnail) {
        try {
            String url = imageDataService.generateCompatibleUrl(dispatchOrdID, mediaId, isThumbnail);
            return AjaxResult.success("生成兼容URL成功", url);
        } catch (Exception e) {
            return AjaxResult.error("生成兼容URL失败:" + e.getMessage());
        }
    }
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(完整版本)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param thumbnailUrl ç¼©ç•¥å›¾URL
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å¤„理结果
     */
    @PreAuthorize("@ss.hasPermi('hospital:imagedata:add')")
    @Log(title = "图片URL上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadByUrl")
    public AjaxResult uploadImageByUrl(@RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                       @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                       @RequestParam(value = "oaid", required = false) Integer oaid,
                                       @RequestParam("imageUrl") String imageUrl,
                                       @RequestParam(value = "thumbnailUrl", required = false) String thumbnailUrl,
                                       @RequestParam(value = "imageType", defaultValue = "0") Integer imageType) {
        try {
            // èŽ·å–å½“å‰ç®¡ç†å‘˜ID
            Integer adminId = getUserId().intValue();
            // è°ƒç”¨å›¾ç‰‡æ•°æ®æœåŠ¡å¤„ç†ä¸Šä¼ 
            String result = imageDataService.uploadImageByUrl(dispatchOrdID, serviceOrdID, oaid,
                    imageUrl, thumbnailUrl, imageType, adminId);
            if (result.contains("成功")) {
                return AjaxResult.success("图片URL上传成功", result);
            } else {
                return AjaxResult.error(result);
            }
        } catch (Exception e) {
            return AjaxResult.error("图片URL上传失败:" + e.getMessage());
        }
    }
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(简化版本)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å¤„理结果
     */
    @Log(title = "图片URL上传简化版", businessType = BusinessType.INSERT)
    @PostMapping("/uploadByUrlSimple")
    public AjaxResult uploadImageByUrlSimple(@RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                             @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                             @RequestParam(value = "oaid", required = false) Integer oaid,
                                             @RequestParam("imageUrl") String imageUrl,
                                             @RequestParam(value = "imageType", defaultValue = "0") Integer imageType) {
        try {
            // èŽ·å–å½“å‰ç®¡ç†å‘˜ID
            Integer adminId = getUserId().intValue();
            // è°ƒç”¨å›¾ç‰‡æ•°æ®æœåŠ¡å¤„ç†ä¸Šä¼ ï¼ˆç®€åŒ–ç‰ˆï¼‰
            String result = imageDataService.uploadImageByUrlSimple(dispatchOrdID, serviceOrdID, oaid,
                    imageUrl, imageType, adminId);
            if (result.contains("成功")) {
                return AjaxResult.success("图片URL上传成功", result);
            } else {
                return AjaxResult.error(result);
            }
        } catch (Exception e) {
            return AjaxResult.error("图片URL上传失败:" + e.getMessage());
        }
    }
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(JSON格式)
     *
     * @param requestBody è¯·æ±‚体
     * @return å¤„理结果
     */
    @PreAuthorize("@ss.hasPermi('hospital:imagedata:add')")
    @Log(title = "图片URL上传JSON", businessType = BusinessType.INSERT)
    @PostMapping("/uploadByUrlJson")
    public AjaxResult uploadImageByUrlJson(@RequestBody ImageUrlUploadRequest requestBody) {
        try {
            // èŽ·å–å½“å‰ç®¡ç†å‘˜ID
            Integer adminId = getUserId().intValue();
            // è°ƒç”¨å›¾ç‰‡æ•°æ®æœåŠ¡å¤„ç†ä¸Šä¼ 
            String result = imageDataService.uploadImageByUrl(requestBody.getDispatchOrdID(),
                    requestBody.getServiceOrdID(), requestBody.getOaid(), requestBody.getImageUrl(),
                    requestBody.getThumbnailUrl(), requestBody.getImageType(), adminId);
            if (result.contains("成功")) {
                return AjaxResult.success("图片URL上传成功", result);
            } else {
                return AjaxResult.error(result);
            }
        } catch (Exception e) {
            return AjaxResult.error("图片URL上传失败:" + e.getMessage());
        }
    }
    /**
     * å›¾ç‰‡URL上传请求对象
     */
    public static class ImageUrlUploadRequest {
        private Long dispatchOrdID;
        private Long serviceOrdID;
        private Integer oaid;
        private String imageUrl;
        private String thumbnailUrl;
        private Integer imageType;
        // getter和setter方法
        public Long getDispatchOrdID() {
            return dispatchOrdID;
        }
        public void setDispatchOrdID(Long dispatchOrdID) {
            this.dispatchOrdID = dispatchOrdID;
        }
        public Long getServiceOrdID() {
            return serviceOrdID;
        }
        public void setServiceOrdID(Long serviceOrdID) {
            this.serviceOrdID = serviceOrdID;
        }
        public Integer getOaid() {
            return oaid;
        }
        public void setOaid(Integer oaid) {
            this.oaid = oaid;
        }
        public String getImageUrl() {
            return imageUrl;
        }
        public void setImageUrl(String imageUrl) {
            this.imageUrl = imageUrl;
        }
        public String getThumbnailUrl() {
            return thumbnailUrl;
        }
        public void setThumbnailUrl(String thumbnailUrl) {
            this.thumbnailUrl = thumbnailUrl;
        }
        public Integer getImageType() {
            return imageType;
        }
        public void setImageType(Integer imageType) {
            this.imageType = imageType;
        }
    }
    /**
     * å›¾ç‰‡æ–‡ä»¶ä¸Šä¼ æŽ¥å£
     *
     * @param file ä¸Šä¼ çš„图片文件
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å¤„理结果
     */
//    @PreAuthorize("@ss.hasPermi('hospital:imagedata:add')")
    @Anonymous()
    @Log(title = "图片文件上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadFile")
    public AjaxResult uploadImageFile(@RequestParam("file") MultipartFile file,
                                      @RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                      @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                      @RequestParam(value = "imageType", defaultValue = "0", required = true) Integer imageType,
                                      @RequestParam(value = "adminId", defaultValue = "0", required = false) Integer adminId) {
        try {
            adminId = getUserId().intValue();
            // éªŒè¯æ–‡ä»¶
            if (file.isEmpty()) {
                return AjaxResult.error("请选择要上传的图片文件");
            }
            // éªŒè¯æ–‡ä»¶ç±»åž‹
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null || !isValidImageFile(originalFilename)) {
                return AjaxResult.error("只支持上传图片文件(jpg、jpeg、png、gif、bmp)");
            }
            // ç”Ÿæˆç›®æ ‡è·¯å¾„(使用年月目录结构)
            String yearMonth = DateUtils.datePath();
            String targetPath = dispatchOrdID.toString();
            //在结果另返回 fileUrl,thumbnailUrl,及mediaid
            // ä½¿ç”¨æ–‡ä»¶ä¸Šä¼ æœåŠ¡ä¿å­˜åˆ°æ–‡ä»¶æœåŠ¡å™¨ï¼ˆåŒ…å«ç¼©ç•¥å›¾ç”Ÿæˆï¼‰
            FileUploadResponse uploadResponse = fileUploadService.uploadMultipartFileWithThumbnail(file, targetPath);
            // æ·»åŠ è°ƒè¯•ä¿¡æ¯
            System.out.println("文件上传响应 - æˆåŠŸ: " + uploadResponse.isSuccess());
            System.out.println("文件上传响应 - æ¶ˆæ¯: " + uploadResponse.getMessage());
            System.out.println("文件上传响应 - æ–‡ä»¶è·¯å¾„: " + uploadResponse.getFilePath());
            System.out.println("文件上传响应 - ç¼©ç•¥å›¾è·¯å¾„: " + uploadResponse.getThumbnailPath());
            if (!uploadResponse.isSuccess()) {
                return AjaxResult.error("文件上传失败:" + uploadResponse.getMessage());
            }
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageType);
            imageData.setImageUrl(uploadResponse.getFilePath());
            imageData.setImageUrls(uploadResponse.getThumbnailPath()); // ç¼©ç•¥å›¾è·¯å¾„
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0);
            // æ’入数据库
            int result = imageDataService.insertImageData(imageData);
            if (result > 0) {
                // æ ¹æ®å›¾ç‰‡ç±»åž‹è¿›è¡Œç‰¹æ®Šå¤„理
                ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
                //返回全路径
                //在结果另返回 fileUrl,thumbnailUrl,及id
                HashMap<String, String> map = new HashMap<>();
                //加上fileServerUrl
                map.put("fileUrl", buildFullImageUrl(uploadResponse.getFilePath()));
                map.put("thumbnailUrl", buildFullImageUrl(uploadResponse.getThumbnailPath()));
                map.put("id", imageData.getId().toString());
                return AjaxResult.success("图片上传成功", map);
            } else {
                return AjaxResult.error("图片数据保存失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("图片上传失败:" + e.getMessage());
        }
    }
    @Anonymous()
    @Log(title = "图片文件上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadFileBase64")
    public AjaxResult uploadFileBase64(@RequestParam("fileContent") String fileContent,@RequestParam("filename") String filename,
                                       @RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                       @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                       @RequestParam(value = "imageType", defaultValue = "0", required = true) Integer imageType,
                                       @RequestParam(value = "adminId", defaultValue = "0", required = false) Integer adminId) {
        try {
            if( adminId == 0)
                adminId = getUserId().intValue();
            InputStream stream= InputStreamBase64Converter.base64ToInputStream(fileContent);
            // ç”Ÿæˆç›®æ ‡è·¯å¾„(使用年月目录结构)
            String yearMonth = DateUtils.datePath();
            String targetPath = dispatchOrdID.toString();
            //在结果另返回 fileUrl,thumbnailUrl,及mediaid
            // ä½¿ç”¨æ–‡ä»¶ä¸Šä¼ æœåŠ¡ä¿å­˜åˆ°æ–‡ä»¶æœåŠ¡å™¨ï¼ˆåŒ…å«ç¼©ç•¥å›¾ç”Ÿæˆï¼‰
            FileUploadResponse uploadResponse = fileUploadService.uploadInputStream(stream,filename, targetPath);
            // æ·»åŠ è°ƒè¯•ä¿¡æ¯
            System.out.println("文件上传响应 - æˆåŠŸ: " + uploadResponse.isSuccess());
            System.out.println("文件上传响应 - æ¶ˆæ¯: " + uploadResponse.getMessage());
            System.out.println("文件上传响应 - æ–‡ä»¶è·¯å¾„: " + uploadResponse.getFilePath());
            System.out.println("文件上传响应 - ç¼©ç•¥å›¾è·¯å¾„: " + uploadResponse.getThumbnailPath());
            if (!uploadResponse.isSuccess()) {
                return AjaxResult.error("文件上传失败:" + uploadResponse.getMessage());
            }
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageType);
            imageData.setImageUrl(uploadResponse.getFilePath());
            imageData.setImageUrls(uploadResponse.getThumbnailPath()); // ç¼©ç•¥å›¾è·¯å¾„
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0);
            // æ’入数据库
            int result = imageDataService.insertImageData(imageData);
            if (result > 0) {
                // æ ¹æ®å›¾ç‰‡ç±»åž‹è¿›è¡Œç‰¹æ®Šå¤„理
                ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
                //返回全路径
                //在结果另返回 fileUrl,thumbnailUrl,及id
                HashMap<String, String> map = new HashMap<>();
                //加上fileServerUrl
                map.put("fileUrl", buildFullImageUrl(uploadResponse.getFilePath()));
                map.put("thumbnailUrl", buildFullImageUrl(uploadResponse.getThumbnailPath()));
                map.put("id", imageData.getId().toString());
                return AjaxResult.success("图片上传成功", map);
            } else {
                return AjaxResult.error("图片数据保存失败");
            }
        } catch (Exception e) {
            return AjaxResult.error("图片上传失败:" + e.getMessage());
        }
    }
    /**
     * å¾®ä¿¡å°ç¨‹åºä¸“用图片文件上传接口
     * æ”¯æŒå¾®ä¿¡å°ç¨‹åºçš„ wx.uploadFile API
     *
     * @param request HttpServletRequest è¯·æ±‚对象
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId ç®¡ç†å‘˜ID
     * @return å¤„理结果
     */
    @Anonymous()
    @Log(title = "微信小程序图片上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxFile")
    public AjaxResult uploadWxFile(HttpServletRequest request,
                                   @RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                   @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                   @RequestParam(value = "imageType", defaultValue = "0", required = true) Integer imageType,
                                   @RequestParam(value = "adminId", defaultValue = "0", required = false) Integer adminId) {
        try {
            adminId = getUserId().intValue();
            // èŽ·å–æ–‡ä»¶å‚æ•° - å¾®ä¿¡å°ç¨‹åºä¸“用
            MultipartFile file = null;
            // æ£€æŸ¥æ˜¯å¦ä¸º multipart è¯·æ±‚
            if (request instanceof MultipartHttpServletRequest) {
                MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
                // å¾®ä¿¡å°ç¨‹åºé€šå¸¸ä½¿ç”¨ "file" ä½œä¸ºæ–‡ä»¶å‚数名
                file = multipartRequest.getFile("file");
                // å¦‚果没找到,尝试其他可能的参数名
                if (file == null || file.isEmpty()) {
                    String[] possibleFileParams = {"image", "photo", "upload"};
                    for (String paramName : possibleFileParams) {
                        file = multipartRequest.getFile(paramName);
                        if (file != null && !file.isEmpty()) {
                            break;
                        }
                    }
                }
                // å¦‚果还是没找到文件,获取第一个文件
                if (file == null || file.isEmpty()) {
                    Iterator<String> fileNames = multipartRequest.getFileNames();
                    if (fileNames.hasNext()) {
                        file = multipartRequest.getFile(fileNames.next());
                    }
                }
            }
            // éªŒè¯æ–‡ä»¶
            if (file == null || file.isEmpty()) {
                return AjaxResult.error("请选择要上传的图片文件");
            }
            // éªŒè¯æ–‡ä»¶ç±»åž‹
            String originalFilename = file.getOriginalFilename();
            if (originalFilename == null || !isValidImageFile(originalFilename)) {
                return AjaxResult.error("只支持上传图片文件(jpg、jpeg、png、gif、bmp)");
            }
            // ç”Ÿæˆç›®æ ‡è·¯å¾„
            String targetPath = dispatchOrdID != null ? dispatchOrdID.toString() : "default";
            // ä½¿ç”¨æ–‡ä»¶ä¸Šä¼ æœåŠ¡ä¿å­˜åˆ°æ–‡ä»¶æœåŠ¡å™¨ï¼ˆåŒ…å«ç¼©ç•¥å›¾ç”Ÿæˆï¼‰
            FileUploadResponse uploadResponse = fileUploadService.uploadMultipartFileWithThumbnail(file, targetPath);
            // æ·»åŠ è°ƒè¯•ä¿¡æ¯
            System.out.println("微信小程序文件上传响应 - æˆåŠŸ: " + uploadResponse.isSuccess());
            System.out.println("微信小程序文件上传响应 - æ¶ˆæ¯: " + uploadResponse.getMessage());
            System.out.println("微信小程序文件上传响应 - æ–‡ä»¶è·¯å¾„: " + uploadResponse.getFilePath());
            System.out.println("微信小程序文件上传响应 - ç¼©ç•¥å›¾è·¯å¾„: " + uploadResponse.getThumbnailPath());
            if (!uploadResponse.isSuccess()) {
                return AjaxResult.error("文件上传失败:" + uploadResponse.getMessage());
            }
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageType);
            imageData.setImageUrl(uploadResponse.getFilePath());
            imageData.setImageUrls(uploadResponse.getThumbnailPath()); // ç¼©ç•¥å›¾è·¯å¾„
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0);
            // æ’入数据库
            int result = imageDataService.insertImageData(imageData);
            if (result > 0) {
                // æ ¹æ®å›¾ç‰‡ç±»åž‹è¿›è¡Œç‰¹æ®Šå¤„理
                ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
                // è¿”回微信小程序需要的格式
                HashMap<String, Object> map = new HashMap<>();
                map.put("fileUrl", buildFullImageUrl(uploadResponse.getFilePath()));
                map.put("thumbnailUrl", buildFullImageUrl(uploadResponse.getThumbnailPath()));
                map.put("id", imageData.getId());
                map.put("success", true);
                map.put("message", "图片上传成功");
                return AjaxResult.success("图片上传成功", map);
            } else {
                return AjaxResult.error("图片数据保存失败");
            }
        } catch (Exception e) {
            log.error("微信小程序图片上传失败", e);
            return AjaxResult.error("图片上传失败:" + e.getMessage());
        }
    }
    /**
     * å¾®ä¿¡å°ç¨‹åºBase64图片上传接口
     * æ”¯æŒå¾®ä¿¡å°ç¨‹åºå°†å›¾ç‰‡è½¬æ¢ä¸ºBase64后上传
     *
     * @param fileContent Base64编码的图片内容
     * @param filename æ–‡ä»¶å
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId ç®¡ç†å‘˜ID
     * @return å¤„理结果
     */
    @Anonymous()
    @Log(title = "微信小程序Base64图片上传", businessType = BusinessType.INSERT)
    @PostMapping("/uploadWxBase64")
    public AjaxResult uploadWxBase64(@RequestParam("fileContent") String fileContent,
                                     @RequestParam("filename") String filename,
                                     @RequestParam(value = "dispatchOrdID", required = false) Long dispatchOrdID,
                                     @RequestParam(value = "serviceOrdID", required = false) Long serviceOrdID,
                                     @RequestParam(value = "imageType", defaultValue = "0", required = true) Integer imageType,
                                     @RequestParam(value = "adminId", defaultValue = "0", required = false) Integer adminId) {
        try {
            if (adminId == 0) {
                adminId = getUserId().intValue();
            }
            // éªŒè¯Base64内容
            if (fileContent == null || fileContent.trim().isEmpty()) {
                return AjaxResult.error("图片内容不能为空");
            }
            // éªŒè¯æ–‡ä»¶ç±»åž‹
            if (filename == null || !isValidImageFile(filename)) {
                return AjaxResult.error("只支持上传图片文件(jpg、jpeg、png、gif、bmp)");
            }
            // å°†Base64转换为InputStream
            InputStream stream = InputStreamBase64Converter.base64ToInputStream(fileContent);
            // ç”Ÿæˆç›®æ ‡è·¯å¾„
            String targetPath = dispatchOrdID != null ? dispatchOrdID.toString() : "default";
            // ä½¿ç”¨æ–‡ä»¶ä¸Šä¼ æœåŠ¡ä¿å­˜åˆ°æ–‡ä»¶æœåŠ¡å™¨
            FileUploadResponse uploadResponse = fileUploadService.uploadInputStream(stream, filename, targetPath);
            // æ·»åŠ è°ƒè¯•ä¿¡æ¯
            System.out.println("微信小程序Base64上传响应 - æˆåŠŸ: " + uploadResponse.isSuccess());
            System.out.println("微信小程序Base64上传响应 - æ¶ˆæ¯: " + uploadResponse.getMessage());
            System.out.println("微信小程序Base64上传响应 - æ–‡ä»¶è·¯å¾„: " + uploadResponse.getFilePath());
            System.out.println("微信小程序Base64上传响应 - ç¼©ç•¥å›¾è·¯å¾„: " + uploadResponse.getThumbnailPath());
            if (!uploadResponse.isSuccess()) {
                return AjaxResult.error("文件上传失败:" + uploadResponse.getMessage());
            }
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageType);
            imageData.setImageUrl(uploadResponse.getFilePath());
            imageData.setImageUrls(uploadResponse.getThumbnailPath()); // ç¼©ç•¥å›¾è·¯å¾„
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0);
            // æ’入数据库
            int result = imageDataService.insertImageData(imageData);
            if (result > 0) {
                // æ ¹æ®å›¾ç‰‡ç±»åž‹è¿›è¡Œç‰¹æ®Šå¤„理
                ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
                // è¿”回微信小程序需要的格式
                HashMap<String, Object> map = new HashMap<>();
                map.put("fileUrl", buildFullImageUrl(uploadResponse.getFilePath()));
                map.put("thumbnailUrl", buildFullImageUrl(uploadResponse.getThumbnailPath()));
                map.put("id", imageData.getId());
                map.put("success", true);
                map.put("message", "图片上传成功");
                return AjaxResult.success("图片上传成功", map);
            } else {
                return AjaxResult.error("图片数据保存失败");
            }
        } catch (Exception e) {
            log.error("微信小程序Base64图片上传失败", e);
            return AjaxResult.error("图片上传失败:" + e.getMessage());
        }
    }
    /**
     * éªŒè¯æ˜¯å¦ä¸ºæœ‰æ•ˆçš„图片文件
     */
    private boolean isValidImageFile(String filename) {
        String[] allowedExtensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp"};
        String lowerFilename = filename.toLowerCase();
        for (String ext : allowedExtensions) {
            if (lowerFilename.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }
    /**
     * èŽ·å–æ–‡ä»¶æ‰©å±•å
     */
    private String getFileExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf('.');
        if (lastDotIndex > 0) {
            return filename.substring(lastDotIndex + 1);
        }
        return "jpg"; // é»˜è®¤æ‰©å±•名
    }
    /**
     * èŽ·å–ä¸Šä¼ è·¯å¾„
     */
    private String getUploadPath() {
        // è¿™é‡Œå¯ä»¥æ ¹æ®å®žé™…配置返回上传路径
        // å¯ä»¥ä»Žé…ç½®æ–‡ä»¶ä¸­è¯»å–或使用默认路径
        return System.getProperty("user.dir") + "/upload";
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/SysTaskAttachmentController.java
@@ -1,8 +1,13 @@
package com.ruoyi.web.controller.task;
import java.util.List;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.WechatUtils;
import com.ruoyi.common.config.WechatConfig;
import com.ruoyi.system.domain.SysTask;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +38,9 @@
    
    @Autowired
    private ISysTaskService sysTaskService;
    @Autowired
    private WechatConfig wechatConfig;
    /**
     * æŸ¥è¯¢ä»»åŠ¡é™„ä»¶åˆ—è¡¨
@@ -50,9 +58,11 @@
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务附件", businessType = BusinessType.INSERT)
    @PostMapping("/upload/{taskId}")
    public AjaxResult upload(@PathVariable("taskId") Long taskId, @RequestParam("file") MultipartFile file) {
    public AjaxResult upload(@PathVariable("taskId") Long taskId,
                            @RequestParam("file") MultipartFile file,
                            @RequestParam(value = "category", required = false) String category) {
        try {
            int result = sysTaskService.uploadAttachment(taskId, file);
            int result = sysTaskService.uploadAttachment(taskId, file, category);
            if (result > 0) {
                return success("上传成功");
            } else {
@@ -81,4 +91,74 @@
            return error("删除失败:" + e.getMessage());
        }
    }
    /**
     * ä»Žå¾®ä¿¡mediaId上传附件(微信小程序专用)
     */
    @PreAuthorize("@ss.hasPermi('task:general:edit')")
    @Log(title = "任务附件", businessType = BusinessType.INSERT)
    @PostMapping("/uploadFromWechat/{taskId}")
    public AjaxResult uploadFromWechat(@PathVariable("taskId") Long taskId,
                                       @RequestParam("mediaId") String mediaId,
                                       @RequestParam(value = "category", required = false) String category) {
        try {
            // èŽ·å–å¾®ä¿¡AccessToken
            String accessToken = WechatUtils.getAccessToken(
                wechatConfig.getAppId(),
                wechatConfig.getAppSecret()
            );
            if (accessToken == null || accessToken.isEmpty()) {
                return error("获取微信AccessToken失败");
            }
            // é€šè¿‡mediaId上传附件
            int result = sysTaskService.uploadAttachmentFromWechat(taskId, accessToken, mediaId, category);
            if (result > 0) {
                return success("上传成功");
            } else {
                return error("上传失败");
            }
        } catch (Exception e) {
            return error("上传失败:" + e.getMessage());
        }
    }
    /**
     * ä¸‹è½½é™„ä»¶
     */
    @GetMapping("/download/{attachmentId}")
    public void downloadAttachment(@PathVariable("attachmentId") Long attachmentId, HttpServletResponse response) {
        try {
            SysTaskAttachment attachment = sysTaskService.getAttachmentById(attachmentId);
            if (attachment == null) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            File file = new File(attachment.getFilePath());
            if (!file.exists()) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            // è®¾ç½®å“åº”头
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=" + attachment.getFileName());
            response.setContentLengthLong(file.length());
            // è¯»å–文件并输出
            try (FileInputStream fis = new FileInputStream(file);
                 OutputStream os = response.getOutputStream()) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    os.write(buffer, 0, bytesRead);
                }
                os.flush();
            }
        } catch (Exception e) {
            logger.error("下载附件失败", e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/wechat/WechatController.java
New file
@@ -0,0 +1,42 @@
package com.ruoyi.web.controller.wechat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.config.WechatConfig;
import com.ruoyi.common.utils.WechatUtils;
/**
 * å¾®ä¿¡æŽ¥å£Controller
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/wechat")
public class WechatController extends BaseController {
    @Autowired
    private WechatConfig wechatConfig;
    /**
     * èŽ·å–å¾®ä¿¡AccessToken
     */
    @GetMapping("/accessToken")
    public AjaxResult getAccessToken() {
        try {
            String accessToken = WechatUtils.getAccessToken(
                wechatConfig.getAppId(),
                wechatConfig.getAppSecret()
            );
            if (accessToken == null || accessToken.isEmpty()) {
                return error("获取微信AccessToken失败");
            }
            return success(accessToken);
        } catch (Exception e) {
            return error("获取微信AccessToken异常:" + e.getMessage());
        }
    }
}
ruoyi-admin/src/main/resources/application-dev.yml
@@ -97,6 +97,10 @@
    # æ—§ç³»ç»ŸåŸºç¡€URL (必须配置)
    # ç¤ºä¾‹: http://192.168.1.100:8080 æˆ– http://legacy.yourdomain.com
    base-url: http://120.25.98.119:8083
    # æ–‡ä»¶ä¸Šä¼ URL
    fileUploadUrl: http://120.25.98.119:8083/weixin/upload_file.php
    # æ–‡ä»¶ä¸‹è½½URL
    fileServerUrl: http://120.25.98.119:8083
    
    # æ€¥æ•‘转运创建接口路径 (可选,默认值如下)
    emergency-create-path: /admin_save_19.gds
ruoyi-admin/src/main/resources/application.yml
@@ -56,7 +56,7 @@
    basename: i18n/messages
  profiles:
    # çŽ¯å¢ƒ dev|test|prod
    active: prod
    active: dev
  # æ–‡ä»¶ä¸Šä¼ 
  servlet:
    multipart:
@@ -146,11 +146,14 @@
  apiUrl: http://120.25.98.119:8084/v1/   #测试环境:localhost:8011
# å¾®ä¿¡é…ç½®
wechat:
evaluationWechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: http://yourdomain.com/evaluation
# è°ƒåº¦ç”¨çš„weixin配置
transferConfigWeixin:
  appId: wx40692cc44953a8cb
  appSecret: 9638b7d8bb988e4daaac7ac35457f296
# è…¾è®¯åœ°å›¾é…ç½®
tencent:
  map:
ruoyi-common/src/main/java/com/ruoyi/common/config/LegacySystemConfig.java
@@ -24,6 +24,10 @@
    /** ä»»åŠ¡çŠ¶æ€æŸ¥è¯¢æŽ¥å£è·¯å¾„ï¼ˆå·²å¼ƒç”¨ï¼Œç›´æŽ¥æŸ¥è¯¢SQL Server数据库) */
    @Deprecated
    private String statusQueryPath = "/task_status_query.asp";
    private String fileUploadUrl;
    private String fileServerUrl;
    
    /** è¿žæŽ¥è¶…æ—¶æ—¶é—´(毫秒) */
    private int connectTimeout = 30000;
@@ -41,6 +45,21 @@
        return baseUrl;
    }
    public String getFileUploadUrl(){
        return fileUploadUrl;
    }
    public void setFileUploadUrl(String fileUploadUrl){
        this.fileUploadUrl = fileUploadUrl;
    }
    public String getFileServerUrl(){
        return fileServerUrl;
    }
    public void setFileServerUrl(String fileServerUrl){
        this.fileServerUrl = fileServerUrl;
        }
    public void setBaseUrl(String baseUrl) {
        this.baseUrl = baseUrl;
    }
ruoyi-common/src/main/java/com/ruoyi/common/config/WechatConfig.java
New file
@@ -0,0 +1,37 @@
package com.ruoyi.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * å¾®ä¿¡å°ç¨‹åºé…ç½®ç±»ï¼ˆç”¨äºŽé™„件上传等小程序功能)
 * é…ç½®æ¥æºï¼štransferConfig.weixin
 *
 * @author ruoyi
 */
@Component
@ConfigurationProperties(prefix = "transfer-config-weixin")
public class WechatConfig {
    /** å¾®ä¿¡å°ç¨‹åºAppID */
    private String appId;
    /** å¾®ä¿¡å°ç¨‹åºAppSecret */
    private String appSecret;
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/config/WechatMpConfig.java
New file
@@ -0,0 +1,47 @@
package com.ruoyi.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * å¾®ä¿¡å…¬ä¼—号配置类(用于网页授权、评价等公众号功能)
 *
 * @author ruoyi
 */
@Component
@ConfigurationProperties(prefix = "evaluation-wechat")
public class WechatMpConfig {
    /** å¾®ä¿¡å…¬ä¼—号AppID */
    private String appId;
    /** å¾®ä¿¡å…¬ä¼—号AppSecret */
    private String appSecret;
    /** å¾®ä¿¡æŽˆæƒå›žè°ƒåœ°å€ */
    private String redirectUri;
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
    public String getRedirectUri() {
        return redirectUri;
    }
    public void setRedirectUri(String redirectUri) {
        this.redirectUri = redirectUri;
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/InputStreamBase64Converter.java
New file
@@ -0,0 +1,35 @@
package com.ruoyi.common.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
public class InputStreamBase64Converter {
    /**
     * å°†InputStream转换为Base64字符串
     */
    public static String inputStreamToBase64(InputStream inputStream) throws Exception {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            outputStream.write(buffer, 0, bytesRead);
        }
        byte[] bytes = outputStream.toByteArray();
        return Base64.getEncoder().encodeToString(bytes);
    }
    /**
     * å°†Base64字符串转换回InputStream
     */
    public static InputStream base64ToInputStream(String base64String) {
        byte[] bytes = Base64.getDecoder().decode(base64String);
        return new ByteArrayInputStream(bytes);
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
@@ -1,16 +1,10 @@
package com.ruoyi.common.utils.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@@ -33,6 +27,43 @@
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
    public static final int DEFAULT_READ_TIMEOUT = 60000;
    private static final String BOUNDARY_PREFIX = "----WebKitFormBoundary";
    private static final String LINE_END = "\r\n";
    private static final String TWO_HYPHENS = "--";
    /**
     * ç”Ÿæˆå”¯ä¸€çš„boundary
     */
    private static String generateBoundary() {
        return BOUNDARY_PREFIX + System.currentTimeMillis();
    }
    /**
     * æ ¹æ®æ–‡ä»¶åèŽ·å–Content-Type
     */
    private static String getContentType(String fileName) {
        if (fileName == null) {
            return "application/octet-stream";
        }
        String extension = fileName.toLowerCase();
        if (extension.endsWith(".jpg") || extension.endsWith(".jpeg")) {
            return "image/jpeg";
        } else if (extension.endsWith(".png")) {
            return "image/png";
        } else if (extension.endsWith(".gif")) {
            return "image/gif";
        } else if (extension.endsWith(".pdf")) {
            return "application/pdf";
        } else if (extension.endsWith(".doc")) {
            return "application/msword";
        } else if (extension.endsWith(".docx")) {
            return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
        } else {
            return "application/octet-stream";
        }
    }
    /**
     * å‘指定 URL å‘送GET方法的请求
@@ -299,4 +330,138 @@
            return true;
        }
    }
    /**
     * å‘送文件上传请求
     *
     * @param url è¯·æ±‚URL
     * @param params è¯·æ±‚参数(包含文件流)
     * @return å“åº”内容
     */
    public static String postFile(String url, Map<String, Object> params) {
        return postFile(url, params, null);
    }
    /**
     * å‘送文件上传请求(支持自定义文件名)
     *
     * @param url è¯·æ±‚URL
     * @param params è¯·æ±‚参数(包含文件流)
     * @param fileName æ–‡ä»¶åï¼ˆå¯é€‰ï¼Œå¦‚果为null则使用参数键名)
     * @return å“åº”内容
     */
    public static String postFile(String url, Map<String, Object> params, String fileName) {
        HttpURLConnection connection = null;
        try {
            // ç”Ÿæˆå”¯ä¸€çš„boundary
            String boundary = generateBoundary();
            log.info("开始文件上传 - URL: {}, fileName: {}, boundary: {}", url, fileName, boundary);
            log.info("参数数量: {}", params.size());
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                log.info("参数: {} = {}", entry.getKey(), entry.getValue() instanceof InputStream ? "InputStream" : entry.getValue());
            }
            // åˆ›å»ºè¿žæŽ¥
            URL requestUrl = new URL(url);
            connection = (HttpURLConnection) requestUrl.openConnection();
            // è®¾ç½®è¯·æ±‚属性
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Accept-Charset", "utf-8");
            connection.setConnectTimeout(30000);
            connection.setReadTimeout(60000);
            // æž„建请求体
            try (OutputStream outputStream = connection.getOutputStream();
                 PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true)) {
                // å†™å…¥æ™®é€šå‚æ•°
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    Object value = entry.getValue();
                    if (!(value instanceof InputStream)) {
                        writer.append(TWO_HYPHENS).append(boundary).append(LINE_END);
                        writer.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"").append(LINE_END);
                        writer.append("Content-Type: text/plain; charset=UTF-8").append(LINE_END);
                        writer.append(LINE_END);
                        writer.append(value.toString()).append(LINE_END);
                        writer.flush();
                    }
                }
                // å†™å…¥æ–‡ä»¶å‚æ•°
                for (Map.Entry<String, Object> entry : params.entrySet()) {
                    Object value = entry.getValue();
                    if (value instanceof InputStream) {
                        // ç¡®å®šæ–‡ä»¶å
                        String actualFileName = fileName != null ? fileName : entry.getKey();
                        writer.append(TWO_HYPHENS).append(boundary).append(LINE_END);
                        writer.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"; filename=\"").append(actualFileName).append("\"").append(LINE_END);
                        writer.append("Content-Type: ").append(getContentType(actualFileName)).append(LINE_END);
                        writer.append(LINE_END);
                        writer.flush();
                        // å†™å…¥æ–‡ä»¶å†…容
                        try (InputStream inputStream = (InputStream) value) {
                            byte[] buffer = new byte[4096];
                            int bytesRead;
                            while ((bytesRead = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, bytesRead);
                            }
                            outputStream.flush();
                        }
                        writer.append(LINE_END);
                        writer.flush();
                    }
                }
                // å†™å…¥ç»“束标记
                writer.append(TWO_HYPHENS).append(boundary).append(TWO_HYPHENS).append(LINE_END);
                writer.flush();
            }
            // èŽ·å–å“åº”
            int responseCode = connection.getResponseCode();
            log.info("文件上传响应码:{}", responseCode);
            // è¯»å–响应内容
            StringBuilder response = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(
                            responseCode == HttpURLConnection.HTTP_OK ?
                                    connection.getInputStream() :
                                    connection.getErrorStream(),
                            StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                }
            }
            if (responseCode != HttpURLConnection.HTTP_OK) {
                log.error("文件上传失败,响应码:{},响应内容:{}", responseCode, response.toString());
                throw new RuntimeException("文件上传失败,响应码: " + responseCode + ",响应内容: " + response.toString());
            }
            log.info("文件上传成功,响应:{}", response.toString());
            return response.toString();
        } catch (Exception e) {
            log.error("文件上传异常:{}", e.getMessage(), e);
            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/ImageData.java
New file
@@ -0,0 +1,92 @@
package com.ruoyi.system.domain;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import java.util.Date;
import com.ruoyi.common.annotation.Excel;
/**
 * å›¾ç‰‡æ•°æ®å¯¹è±¡ ImageData
 */
@Data
public class ImageData extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    private Long id;
    /**
     * è°ƒåº¦å•ID
     */
    @Excel(name = "调度单ID")
    private Long dOrdIDDt;
    /**
     * æœåŠ¡å•ID
     */
    @Excel(name = "服务单ID")
    private Long sOrdIDDt;
    /**
     * å›¾ç‰‡ç±»åž‹
     */
    @Excel(name = "图片类型")
    private Integer imageType;
    /**
     * å›¾ç‰‡URL
     */
    @Excel(name = "图片URL")
    private String imageUrl;
    /**
     * å›¾ç‰‡URLs
     */
    @Excel(name = "图片URLs")
    private String imageUrls;
    /**
     * å›¾ç‰‡åº¦æ•°
     */
    @Excel(name = "图片度数")
    private Integer imageDeg;
    /**
     * ä¸Šä¼ å›¾ç‰‡æ—¶é—´
     */
    @Excel(name = "上传图片时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date upImageTime;
    /**
     * ä¸Šä¼ å›¾ç‰‡OA用户ID
     */
    @Excel(name = "上传图片OA用户ID")
    private Integer upImageOAid;
    /**
     * å›¾ç‰‡åˆ é™¤æ ‡è®°
     */
    @Excel(name = "图片删除标记")
    private Integer imageDel;
    /**
     * æ˜¯å¦AP
     */
    @Excel(name = "是否AP")
    private Integer isAP;
    /**
     * AP ID
     */
    @Excel(name = "AP ID")
    private Integer isAP_ID;
    /**
     * AP时间
     */
    @Excel(name = "AP时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date isAP_Time;
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/SysTaskAttachment.java
@@ -37,6 +37,10 @@
    @Excel(name = "文件类型")
    private String fileType;
    /** é™„件分类 */
    @Excel(name = "附件分类")
    private String attachmentCategory;
    /** ä¸Šä¼ æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "上传时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
@@ -94,6 +98,14 @@
        return fileType;
    }
    public void setAttachmentCategory(String attachmentCategory) {
        this.attachmentCategory = attachmentCategory;
    }
    public String getAttachmentCategory() {
        return attachmentCategory;
    }
    public void setUploadTime(Date uploadTime) {
        this.uploadTime = uploadTime;
    }
@@ -119,6 +131,7 @@
                ", filePath='" + filePath + '\'' +
                ", fileSize=" + fileSize +
                ", fileType='" + fileType + '\'' +
                ", attachmentCategory='" + attachmentCategory + '\'' +
                ", uploadTime=" + uploadTime +
                ", uploadBy='" + uploadBy + '\'' +
                '}';
ruoyi-system/src/main/java/com/ruoyi/system/domain/enums/ImageTypeEnum.java
New file
@@ -0,0 +1,64 @@
package com.ruoyi.system.domain.enums;
public enum ImageTypeEnum {
    DEFAULT(0, "默认"),
    INFORMED_CONSENT(1, "知情同意书"),
    BRZL(2, "病人资料"),
    CZJL(3, "操作记录"),
    CCQ(4, "出车前"),
    CCH(5, "出车后"),
    SEAT_BELT(6, "绑安全带图片");
//    çŸ¥æƒ…同意书    1
//    ç—…人资料    2
//    æ“ä½œè®°å½•    3
//    å‡ºè½¦å‰    4
//    å‡ºè½¦åŽ    5
//    ç³»å®‰å…¨å¸¦    6
    private final Integer code;
    private final String description;
    ImageTypeEnum(Integer code, String description) {
        this.code = code;
        this.description = description;
    }
    public Integer getCode() {
        return code;
    }
    public String getDescription() {
        return description;
    }
    /**
     * æ ¹æ®ä»£ç èŽ·å–æžšä¸¾
     */
    public static ImageTypeEnum getByCode(Integer code) {
        if (code == null) {
            return DEFAULT;
        }
        for (ImageTypeEnum type : values()) {
            if (type.getCode().equals(code)) {
                return type;
            }
        }
        return DEFAULT;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºçŸ¥æƒ…同意书
     */
    public boolean isInformedConsent() {
        return this == INFORMED_CONSENT;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºç»‘安全带图片
     */
    public boolean isSeatBelt() {
        return this == SEAT_BELT;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/file/FileUploadResponse.java
New file
@@ -0,0 +1,41 @@
package com.ruoyi.system.file;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class FileUploadResponse implements Serializable {
    private static final long serialVersionUID = 1L;
    @JsonProperty("success")
    private boolean success;
    @JsonProperty("message")
    private String message;
    @JsonProperty("filePath")
    private String filePath;
    @JsonProperty("thumbnailPath")
    private String thumbnailPath;
    public static FileUploadResponse error(String error) {
        FileUploadResponse response = new FileUploadResponse();
        response.setSuccess(false);
        response.setMessage(error);
        return response;
    }
    public static FileUploadResponse success(String file,String message) {
        FileUploadResponse response = new FileUploadResponse();
        response.setSuccess(true);
        response.setFilePath(file);
        response.setMessage(message);
        return response;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/file/FileUploadServiceImpl.java
New file
@@ -0,0 +1,549 @@
package com.ruoyi.system.file;
import com.ruoyi.common.config.LegacySystemConfig;
import com.ruoyi.common.utils.http.HttpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.io.*;
@Service
public class FileUploadServiceImpl implements IFileUploadService {
    private static final Logger log = LoggerFactory.getLogger(FileUploadServiceImpl.class);
    @Autowired
    private LegacySystemConfig legacyConfig;
    @Override
    public FileUploadResponse uploadLocalFile(File file, String targetPath) {
        if (file == null || !file.exists()) {
            return FileUploadResponse.error("文件不存在");
        }
        try (FileInputStream fis = new FileInputStream(file)) {
            return uploadInputStream(fis, file.getName(), targetPath);
        } catch (IOException e) {
            log.error("读取本地文件失败: {}", file.getAbsolutePath(), e);
            return FileUploadResponse.error("读取文件失败: " + e.getMessage());
        }
    }
    @Override
    public FileUploadResponse uploadMultipartFile(MultipartFile multipartFile, String targetPath) {
        if (multipartFile == null || multipartFile.isEmpty()) {
            return FileUploadResponse.error("文件为空");
        }
        try (InputStream inputStream = multipartFile.getInputStream()) {
            return uploadInputStream(inputStream, multipartFile.getOriginalFilename(), targetPath);
        } catch (IOException e) {
            log.error("读取MultipartFile失败", e);
            return FileUploadResponse.error("读取文件失败: " + e.getMessage());
        }
    }
    @Override
    public FileUploadResponse uploadBytes(byte[] fileBytes, String fileName, String targetPath) {
        if (fileBytes == null || fileBytes.length == 0) {
            return FileUploadResponse.error("文件字节数组为空");
        }
        try (ByteArrayInputStream bis = new ByteArrayInputStream(fileBytes)) {
            return uploadInputStream(bis, fileName, targetPath);
        } catch (IOException e) {
            log.error("处理字节数组失败", e);
            return FileUploadResponse.error("处理文件失败: " + e.getMessage());
        }
    }
    @Override
    public FileUploadResponse uploadInputStream(InputStream inputStream, String fileName, String targetPath) {
        if (inputStream == null) {
            return FileUploadResponse.error("输入流为空");
        }
        try {
            // æž„建请求参数
            Map<String, Object> params = new HashMap<>();
            params.put("file", inputStream);
            params.put("uploadFileName", targetPath);
            log.info("开始上传文件到PHP接口: fileName={}, targetPath={}", fileName, targetPath);
            // è°ƒç”¨PHP上传接口
            String response = HttpUtils.postFile(legacyConfig.getFileServerUrl(), params, fileName);
            log.info("PHP接口响应: {}", response);
            // è§£æžå“åº”
            return parseUploadResponse(response);
        } catch (Exception e) {
            log.error("上传文件到PHP接口失败: fileName={}, targetPath={}", fileName, targetPath, e);
            return FileUploadResponse.error("上传失败: " + e.getMessage());
        }
    }
    @Override
    public FileUploadResponse uploadFromUrl(String fileUrl, String targetPath) {
        if (fileUrl == null || fileUrl.trim().isEmpty()) {
            return FileUploadResponse.error("文件URL为空");
        }
        try {
            // ä»ŽURL下载文件
            byte[] fileBytes = downloadFromUrl(fileUrl);
            if (fileBytes == null || fileBytes.length == 0) {
                return FileUploadResponse.error("从URL下载文件失败");
            }
            // ä»ŽURL中提取文件名
            String fileName = extractFileNameFromUrl(fileUrl);
            // ä¸Šä¼ æ–‡ä»¶
            return uploadBytes(fileBytes, fileName, targetPath);
        } catch (Exception e) {
            log.error("从URL上传文件失败: fileUrl={}, targetPath={}", fileUrl, targetPath, e);
            return FileUploadResponse.error("从URL上传失败: " + e.getMessage());
        }
    }
    @Override
    public FileUploadResponse uploadFromWechat(String accessToken, String mediaId, String targetPath) {
        if (accessToken == null || mediaId == null) {
            return FileUploadResponse.error("微信参数为空");
        }
        try {
            // ä»Žå¾®ä¿¡API下载文件
            byte[] fileBytes = downloadFromWechat(accessToken, mediaId);
            if (fileBytes == null || fileBytes.length == 0) {
                return FileUploadResponse.error("从微信下载文件失败");
            }
            // ç”Ÿæˆæ–‡ä»¶å
            String fileName = "wechat_" + mediaId + ".jpg";
            // ä¸Šä¼ æ–‡ä»¶
            return uploadBytes(fileBytes, fileName, targetPath);
        } catch (Exception e) {
            log.error("从微信上传文件失败: mediaId={}, targetPath={}", mediaId, targetPath, e);
            return FileUploadResponse.error("从微信上传失败: " + e.getMessage());
        }
    }
    @Override
    public boolean fileExists(String filePath) {
        if (filePath == null || filePath.trim().isEmpty()) {
            return false;
        }
        try {
            // æž„建完整的文件路径
            String fullPath = legacyConfig.getFileServerUrl() + "/" + filePath;
            File file = new File(fullPath);
            return file.exists() && file.isFile();
        } catch (Exception e) {
            log.error("检查文件是否存在失败: {}", filePath, e);
            return false;
        }
    }
    @Override
    public boolean deleteFile(String filePath) {
        if (filePath == null || filePath.trim().isEmpty()) {
            return false;
        }
        try {
            // æž„建完整的文件路径
            String fullPath = legacyConfig.getFileServerUrl() + "/" + filePath;
            File file = new File(fullPath);
            if (file.exists() && file.isFile()) {
                return file.delete();
            }
            return false;
        } catch (Exception e) {
            log.error("删除文件失败: {}", filePath, e);
            return false;
        }
    }
    @Override
    public String getFileUrl(String filePath) {
        if (filePath == null || filePath.trim().isEmpty()) {
            return null;
        }
        // æž„建文件访问URL
        return legacyConfig.getFileServerUrl() + "/" + filePath;
    }
    /**
     * ä»ŽURL下载文件
     */
    private byte[] downloadFromUrl(String fileUrl) throws IOException {
        URL url = new URL(fileUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(30000);
        try (InputStream inputStream = connection.getInputStream();
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        } finally {
            connection.disconnect();
        }
    }
    /**
     * ä»Žå¾®ä¿¡API下载文件
     */
    private byte[] downloadFromWechat(String accessToken, String mediaId) throws IOException {
        String wechatUrl = String.format(
            "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s",
            accessToken, mediaId
        );
        return downloadFromUrl(wechatUrl);
    }
    /**
     * ä»ŽURL中提取文件名
     */
    private String extractFileNameFromUrl(String fileUrl) {
        try {
            String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
            // ç§»é™¤æŸ¥è¯¢å‚æ•°
            if (fileName.contains("?")) {
                fileName = fileName.substring(0, fileName.indexOf('?'));
            }
            return fileName.isEmpty() ? "downloaded_file" : fileName;
        } catch (Exception e) {
            return "downloaded_file";
        }
    }
    /**
     * è§£æžä¸Šä¼ å“åº”
     */
    private FileUploadResponse parseUploadResponse(String response) {
        if (response == null || response.trim().isEmpty()) {
            return FileUploadResponse.error("PHP接口返回空响应");
        }
        try {
            log.info("开始解析PHP响应: {}", response);
            // æ ¹æ®PHP接口的实际返回格式进行解析
            // PHP返回格式: {"success": true, "message": "文件上传成功", "data": {...}}
            if (response.contains("\"success\":true") || response.contains("success")) {
                // æå–文件路径和缩略图路径
                String filePath = extractFilePathFromResponse(response);
                String thumbnailPath = extractThumbnailPathFromResponse(response);
                log.info("解析结果 - filePath: {}, thumbnailPath: {}", filePath, thumbnailPath);
                // åˆ›å»ºå“åº”对象
                FileUploadResponse uploadResponse = FileUploadResponse.success(filePath, "上传成功");
                if (thumbnailPath != null && !thumbnailPath.isEmpty()) {
                    uploadResponse.setThumbnailPath(thumbnailPath);
                }
                return uploadResponse;
            } else {
                // æå–错误信息
                String errorMessage = extractErrorMessageFromResponse(response);
                log.error("上传失败,错误信息: {}", errorMessage);
                return FileUploadResponse.error(errorMessage != null ? errorMessage : "上传失败");
            }
        } catch (Exception e) {
            log.error("解析上传响应失败: {}", response, e);
            return FileUploadResponse.error("解析响应失败: " + e.getMessage());
        }
    }
    /**
     * ä»Žå“åº”中提取文件路径
     */
    private String extractFilePathFromResponse(String response) {
        // è§£æžPHP接口返回的JSON格式: {"success": true, "data": {"filePath": "..."}}
        try {
            if (response.contains("\"data\":")) {
                // æ‰¾åˆ°data对象
                int dataStart = response.indexOf("\"data\":{") + 7;
                if (dataStart > 6) {
                    // åœ¨data对象中查找filePath
                    String dataSection = response.substring(dataStart);
                    if (dataSection.contains("\"filePath\":")) {
                        int start = dataSection.indexOf("\"filePath\":\"") + 12;
                        int end = dataSection.indexOf("\"", start);
                        if (start > 11 && end > start) {
                            String filePath = dataSection.substring(start, end);
                            log.info("提取到文件路径: {}", filePath);
                            return filePath;
                        }
                    }
                }
            }
            // å…¼å®¹æ—§æ ¼å¼ï¼Œç›´æŽ¥æŸ¥æ‰¾filePath
            if (response.contains("\"filePath\":")) {
                int start = response.indexOf("\"filePath\":\"") + 12;
                int end = response.indexOf("\"", start);
                if (start > 11 && end > start) {
                    String filePath = response.substring(start, end);
                    log.info("提取到文件路径(旧格式): {}", filePath);
                    return filePath;
                }
            }
        } catch (Exception e) {
            log.error("提取文件路径失败: {}", response, e);
        }
        return null;
    }
    /**
     * ä»Žå“åº”中提取缩略图路径
     */
    private String extractThumbnailPathFromResponse(String response) {
        // è§£æžPHP接口返回的JSON格式: {"success": true, "data": {"thumbnailPath": "..."}}
        try {
            if (response.contains("\"data\":")) {
                // æ‰¾åˆ°data对象
                int dataStart = response.indexOf("\"data\":{") + 7;
                if (dataStart > 6) {
                    // åœ¨data对象中查找thumbnailPath
                    String dataSection = response.substring(dataStart);
                    if (dataSection.contains("\"thumbnailPath\":")) {
                        int start = dataSection.indexOf("\"thumbnailPath\":\"") + 17;
                        int end = dataSection.indexOf("\"", start);
                        if (start > 16 && end > start) {
                            String thumbnailPath = dataSection.substring(start, end);
                            log.info("提取到缩略图路径: {}", thumbnailPath);
                            return thumbnailPath;
                        }
                    }
                }
            }
            // å…¼å®¹æ—§æ ¼å¼ï¼Œç›´æŽ¥æŸ¥æ‰¾thumbnailPath
            if (response.contains("\"thumbnailPath\":")) {
                int start = response.indexOf("\"thumbnailPath\":\"") + 17;
                int end = response.indexOf("\"", start);
                if (start > 16 && end > start) {
                    String thumbnailPath = response.substring(start, end);
                    log.info("提取到缩略图路径(旧格式): {}", thumbnailPath);
                    return thumbnailPath;
                }
            }
        } catch (Exception e) {
            log.error("提取缩略图路径失败: {}", response, e);
        }
        return null;
    }
    /**
     * ä»Žå“åº”中提取错误信息
     */
    private String extractErrorMessageFromResponse(String response) {
        // è§£æžPHP接口返回的JSON格式: {"success": false, "message": "错误信息"}
        try {
            if (response.contains("\"message\":")) {
                int start = response.indexOf("\"message\":\"") + 11;
                int end = response.indexOf("\"", start);
                if (start > 10 && end > start) {
                    String errorMessage = response.substring(start, end);
                    log.info("提取到错误信息: {}", errorMessage);
                    return errorMessage;
                }
            }
        } catch (Exception e) {
            log.error("提取错误信息失败: {}", response, e);
        }
        return null;
    }
    /**
     * ç”Ÿæˆç¼©ç•¥å›¾
     *
     * @param sourcePath æºå›¾ç‰‡è·¯å¾„
     * @param targetPath ç›®æ ‡ç¼©ç•¥å›¾è·¯å¾„
     * @param width å®½åº¦
     * @param height é«˜åº¦ï¼ˆ0表示按比例缩放)
     * @return æ˜¯å¦ç”ŸæˆæˆåŠŸ
     */
    private boolean createThumbnail(String sourcePath, String targetPath, int width, int height) {
        try {
            // è¯»å–源图片
            BufferedImage sourceImage = ImageIO.read(new File(sourcePath));
            if (sourceImage == null) {
                log.error("无法读取源图片:{}", sourcePath);
                return false;
            }
            // è®¡ç®—缩略图尺寸
            int sourceWidth = sourceImage.getWidth();
            int sourceHeight = sourceImage.getHeight();
            if (height == 0) {
                height = (int) Math.floor((double) width / sourceWidth * sourceHeight);
            } else if (width == 0) {
                width = (int) Math.floor((double) height / sourceHeight * sourceWidth);
            }
            // åˆ›å»ºç¼©ç•¥å›¾
            BufferedImage thumbnailImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = thumbnailImage.createGraphics();
            // è®¾ç½®æ¸²æŸ“质量
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            // ç»˜åˆ¶ç¼©ç•¥å›¾
            g2d.drawImage(sourceImage, 0, 0, width, height, null);
            g2d.dispose();
            // ä¿å­˜ç¼©ç•¥å›¾
            String extension = getFileExtension(sourcePath);
            return ImageIO.write(thumbnailImage, extension, new File(targetPath));
        } catch (Exception e) {
            log.error("生成缩略图失败:sourcePath={}, targetPath={}", sourcePath, targetPath, e);
            return false;
        }
    }
    /**
     * èŽ·å–æ–‡ä»¶æ‰©å±•å
     */
    private String getFileExtension(String filePath) {
        int lastDotIndex = filePath.lastIndexOf('.');
        if (lastDotIndex > 0) {
            return filePath.substring(lastDotIndex + 1).toLowerCase();
        }
        return "jpg"; // é»˜è®¤æ‰©å±•名
    }
    /**
     * æœ¬åœ°æ–‡ä»¶ä¸Šä¼ ï¼ˆåŒ…含缩略图生成)
     */
    public FileUploadResponse uploadLocalFileWithThumbnail(File file, String targetPath) {
        if (file == null || !file.exists()) {
            return FileUploadResponse.error("文件不存在");
        }
        try {
            // æ£€æŸ¥æ˜¯å¦ä¸ºå›¾ç‰‡æ–‡ä»¶
            String fileName = file.getName().toLowerCase();
            boolean isImage = fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
                             fileName.endsWith(".png") || fileName.endsWith(".gif");
            // ä¸Šä¼ åŽŸæ–‡ä»¶
            FileUploadResponse uploadResponse = uploadLocalFile(file, targetPath);
            if (!uploadResponse.isSuccess()) {
                return uploadResponse;
            }
            // å¦‚果是图片,生成缩略图
            if (isImage && uploadResponse.getFilePath() != null) {
                String originalPath = uploadResponse.getFilePath();
                String thumbnailPath = generateThumbnailPath(originalPath);
                // ç”Ÿæˆç¼©ç•¥å›¾
                if (createThumbnail(originalPath, thumbnailPath, 100, 0)) {
                    uploadResponse.setThumbnailPath(thumbnailPath);
                    log.info("缩略图生成成功:{}", thumbnailPath);
                } else {
                    log.warn("缩略图生成失败:{}", originalPath);
                }
            }
            return uploadResponse;
        } catch (Exception e) {
            log.error("上传文件并生成缩略图失败:{}", file.getAbsolutePath(), e);
            return FileUploadResponse.error("上传失败:" + e.getMessage());
        }
    }
    /**
     * MultipartFile上传(包含缩略图生成)
     */
    public FileUploadResponse uploadMultipartFileWithThumbnail(MultipartFile multipartFile, String targetPath) {
        if (multipartFile == null || multipartFile.isEmpty()) {
            return FileUploadResponse.error("文件为空");
        }
        try {
            // æ£€æŸ¥æ˜¯å¦ä¸ºå›¾ç‰‡æ–‡ä»¶
            String originalFilename = multipartFile.getOriginalFilename();
            if (originalFilename != null) {
                String fileName = originalFilename.toLowerCase();
                boolean isImage = fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") ||
                                 fileName.endsWith(".png") || fileName.endsWith(".gif");
                // ä¸Šä¼ åŽŸæ–‡ä»¶
                FileUploadResponse uploadResponse = uploadMultipartFile(multipartFile, targetPath);
                if (!uploadResponse.isSuccess()) {
                    return uploadResponse;
                }
                return uploadResponse;
            }
            return uploadMultipartFile(multipartFile, targetPath);
        } catch (Exception e) {
            log.error("上传MultipartFile并生成缩略图失败", e);
            return FileUploadResponse.error("上传失败:" + e.getMessage());
        }
    }
    /**
     * ç”Ÿæˆç¼©ç•¥å›¾è·¯å¾„
     */
    private String generateThumbnailPath(String originalPath) {
        if (originalPath == null || originalPath.isEmpty()) {
            return null;
        }
        // åœ¨æ–‡ä»¶åå‰æ·»åŠ  "s_" å‰ç¼€
        int lastSlashIndex = originalPath.lastIndexOf('/');
        if (lastSlashIndex >= 0) {
            String directory = originalPath.substring(0, lastSlashIndex + 1);
            String fileName = originalPath.substring(lastSlashIndex + 1);
            return directory + "s_" + fileName;
        } else {
            return "s_" + originalPath;
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/file/IFileUploadService.java
New file
@@ -0,0 +1,108 @@
package com.ruoyi.system.file;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.InputStream;
public interface IFileUploadService {
    /**
     * ä¸Šä¼ æœ¬åœ°æ–‡ä»¶åˆ°PHP接口
     *
     * @param file è¦ä¸Šä¼ çš„æ–‡ä»¶
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadLocalFile(File file, String targetPath);
    /**
     * ä¸Šä¼ MultipartFile到PHP接口
     *
     * @param multipartFile è¦ä¸Šä¼ çš„æ–‡ä»¶
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadMultipartFile(MultipartFile multipartFile, String targetPath);
    /**
     * ä¸Šä¼ å­—节数组到PHP接口
     *
     * @param fileBytes æ–‡ä»¶å­—节数组
     * @param fileName æ–‡ä»¶å
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadBytes(byte[] fileBytes, String fileName, String targetPath);
    /**
     * ä¸Šä¼ è¾“入流到PHP接口
     *
     * @param inputStream æ–‡ä»¶è¾“入流
     * @param fileName æ–‡ä»¶å
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadInputStream(InputStream inputStream, String fileName, String targetPath);
    /**
     * ä»ŽURL下载文件并上传到PHP接口
     *
     * @param fileUrl æ–‡ä»¶URL
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadFromUrl(String fileUrl, String targetPath);
    /**
     * ä»Žå¾®ä¿¡API下载文件并上传到PHP接口
     *
     * @param accessToken å¾®ä¿¡è®¿é—®ä»¤ç‰Œ
     * @param mediaId å¾®ä¿¡åª’体ID
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadFromWechat(String accessToken, String mediaId, String targetPath);
    /**
     * æ£€æŸ¥æ–‡ä»¶æ˜¯å¦å­˜åœ¨
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return æ˜¯å¦å­˜åœ¨
     */
    boolean fileExists(String filePath);
    /**
     * åˆ é™¤æ–‡ä»¶
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return æ˜¯å¦åˆ é™¤æˆåŠŸ
     */
    boolean deleteFile(String filePath);
    /**
     * èŽ·å–æ–‡ä»¶è®¿é—®URL
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return è®¿é—®URL
     */
    String getFileUrl(String filePath);
    /**
     * ä¸Šä¼ æœ¬åœ°æ–‡ä»¶åˆ°PHP接口(包含缩略图生成)
     *
     * @param file è¦ä¸Šä¼ çš„æ–‡ä»¶
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadLocalFileWithThumbnail(File file, String targetPath);
    /**
     * ä¸Šä¼ MultipartFile到PHP接口(包含缩略图生成)
     *
     * @param multipartFile è¦ä¸Šä¼ çš„æ–‡ä»¶
     * @param targetPath ç›®æ ‡è·¯å¾„(相对于PHP上传目录)
     * @return ä¸Šä¼ ç»“æžœ
     */
    FileUploadResponse uploadMultipartFileWithThumbnail(MultipartFile multipartFile, String targetPath);
}
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/IImageDataService.java
New file
@@ -0,0 +1,200 @@
package com.ruoyi.system.imagedata;
import com.ruoyi.system.domain.ImageData;
import java.util.List;
/**
 * å›¾ç‰‡æ•°æ®Service接口
 */
public interface IImageDataService {
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return å›¾ç‰‡æ•°æ®
     */
    public ImageData selectImageDataById(Long id);
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®åˆ—表
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataList(ImageData imageData);
    /**
     * æ–°å¢žå›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    public int insertImageData(ImageData imageData);
    /**
     * ä¿®æ”¹å›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    public int updateImageData(ImageData imageData);
    /**
     * æ‰¹é‡åˆ é™¤å›¾ç‰‡æ•°æ®
     *
     * @param ids éœ€è¦åˆ é™¤çš„图片数据主键集合
     * @return ç»“æžœ
     */
    public int deleteImageDataByIds(Long[] ids);
    /**
     * åˆ é™¤å›¾ç‰‡æ•°æ®ä¿¡æ¯
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return ç»“æžœ
     */
    public int deleteImageDataById(Long id);
    /**
     * æ ¹æ®è°ƒåº¦å•ID查询图片数据
     *
     * @param dOrdIDDt è°ƒåº¦å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataByDOrdIDDt(Long dOrdIDDt);
    String generateCompatibleFilePath(Long dispatchOrdID, String mediaId, boolean isThumbnail);
    String generateCompatibleUrl(Long dispatchOrdID, String mediaId, boolean isThumbnail);
    /**
     * æ ¹æ®æœåŠ¡å•ID查询图片数据
     *
     * @param sOrdIDDt æœåŠ¡å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataBySOrdIDDt(Long sOrdIDDt);
    /**
     * æ ¹æ®å›¾ç‰‡ç±»åž‹æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     *
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataByType(Integer imageType);
    /**
     * æ ‡è®°å›¾ç‰‡ä¸ºåˆ é™¤çŠ¶æ€
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return ç»“æžœ
     */
    public int markImageDataAsDeleted(Long id);
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(原ASP代码转换)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param mediaId å¾®ä¿¡åª’体ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    public String uploadWxImage(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                String mediaId, Integer imageType, Integer adminId);
    /**
     * ä¿å­˜å¾®ä¿¡æ–‡ä»¶åˆ°æœ¬åœ°ï¼ˆåŽŸPHP代码转换)
     *
     * @param filename æ–‡ä»¶å
     * @param fileContent æ–‡ä»¶å†…容
     * @return æ˜¯å¦ä¿å­˜æˆåŠŸ
     */
    public boolean saveWeixinFile(String filename, byte[] fileContent);
    /**
     * ç”Ÿæˆç¼©ç•¥å›¾ï¼ˆåŽŸPHP代码转换)
     *
     * @param bigImgPath åŽŸå§‹å¤§å›¾è·¯å¾„
     * @param width ç¼©ç•¥å›¾å®½åº¦
     * @param height ç¼©ç•¥å›¾é«˜åº¦ï¼ˆ0表示按比例计算)
     * @param smallImgPath ç¼©ç•¥å›¾ä¿å­˜è·¯å¾„
     * @return æ˜¯å¦ç”ŸæˆæˆåŠŸ
     */
    public boolean createThumbnail(String bigImgPath, int width, int height, String smallImgPath);
    /**
     * æ£€æŸ¥æ–‡ä»¶å…¼å®¹æ€§ï¼ˆç¡®ä¿ä¸Žæ—§ç³»ç»Ÿå…¼å®¹ï¼‰
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return å…¼å®¹æ€§æ£€æŸ¥ç»“æžœ
     */
    public String checkFileCompatibility(String filePath);
    /**
     * éªŒè¯URL格式是否与旧系统兼容
     *
     * @param url å›¾ç‰‡URL
     * @return æ˜¯å¦å…¼å®¹
     */
    public boolean isUrlCompatible(String url);
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(允许直接传入图片URL)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param thumbnailUrl ç¼©ç•¥å›¾URL(可选)
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    public String uploadImageByUrl(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                   String imageUrl, String thumbnailUrl, Integer imageType, Integer adminId);
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(简化版本,自动生成缩略图URL)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    public String uploadImageByUrlSimple(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                         String imageUrl, Integer imageType, Integer adminId);
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(完整版本,包含文件下载和缩略图生成)
     *
     * @param accessToken å¾®ä¿¡è®¿é—®ä»¤ç‰Œ
     * @param mediaId å¾®ä¿¡åª’体ID
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param oaid OA用户ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    public String uploadWxImageWithDownload(String accessToken, String mediaId, Long dispatchOrdID,
                                            Integer oaid, Integer imageType, Integer adminId);
    /**
     * æ ¹æ®è°ƒåº¦å•ID和图片类型查询图片数据
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataByDOrdIDDtAndType(Long dispatchOrdID, Integer imageType);
}
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/ImageDataServiceImpl.java
New file
@@ -0,0 +1,644 @@
package com.ruoyi.system.imagedata;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.config.LegacySystemConfig;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.system.domain.ImageData;
import com.ruoyi.system.domain.enums.ImageTypeEnum;
import com.ruoyi.system.file.FileUploadResponse;
import com.ruoyi.system.file.IFileUploadService;
import com.ruoyi.system.mapper.ImageDataMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
@Service
@DataSource(DataSourceType.SQLSERVER)
public class ImageDataServiceImpl implements IImageDataService {
    private static final Logger log = LoggerFactory.getLogger(ImageDataServiceImpl.class);
    @Autowired
    private ImageDataMapper imageDataMapper;
    @Autowired
    private IFileUploadService fileUploadService;
    @Autowired
    private LegacySystemConfig legacyConfig;
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return å›¾ç‰‡æ•°æ®
     */
    @Override
    public ImageData selectImageDataById(Long id) {
        return imageDataMapper.selectImageDataById(id);
    }
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®åˆ—表
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return å›¾ç‰‡æ•°æ®
     */
    @Override
    public List<ImageData> selectImageDataList(ImageData imageData) {
        return imageDataMapper.selectImageDataList(imageData);
    }
    /**
     * æ–°å¢žå›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    @Override
    public int insertImageData(ImageData imageData) {
        return imageDataMapper.insertImageData(imageData);
    }
    /**
     * ä¿®æ”¹å›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    @Override
    public int updateImageData(ImageData imageData) {
        return imageDataMapper.updateImageData(imageData);
    }
    /**
     * æ‰¹é‡åˆ é™¤å›¾ç‰‡æ•°æ®
     *
     * @param ids éœ€è¦åˆ é™¤çš„图片数据主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteImageDataByIds(Long[] ids) {
        return imageDataMapper.deleteImageDataByIds(ids);
    }
    /**
     * åˆ é™¤å›¾ç‰‡æ•°æ®ä¿¡æ¯
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return ç»“æžœ
     */
    @Override
    public int deleteImageDataById(Long id) {
        return imageDataMapper.deleteImageDataById(id);
    }
    /**
     * æ ¹æ®è°ƒåº¦å•ID查询图片数据
     *
     * @param dOrdIDDt è°ƒåº¦å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    @Override
    public List<ImageData> selectImageDataByDOrdIDDt(Long dOrdIDDt) {
        return imageDataMapper.selectImageDataByDOrdIDDt(dOrdIDDt);
    }
    /**
     * ç”Ÿæˆä¸Žæ—§ç³»ç»Ÿå…¼å®¹çš„æ–‡ä»¶è·¯å¾„
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param mediaId åª’体ID
     * @param isThumbnail æ˜¯å¦ä¸ºç¼©ç•¥å›¾
     * @return å…¼å®¹çš„æ–‡ä»¶è·¯å¾„
     */
    @Override
    public String generateCompatibleFilePath(Long dispatchOrdID, String mediaId, boolean isThumbnail) {
        try {
            String yearMonth = DateUtils.dateTimeNow("yyyyMM");
            String fileName = dispatchOrdID + "_" + mediaId + ".jpg";
            if (isThumbnail) {
                fileName = "s_" + fileName;
            }
            return legacyConfig.getFileServerUrl() + "/" + yearMonth + "/" + fileName;
        } catch (Exception e) {
            log.error("生成兼容文件路径失败:{}", e.getMessage());
            return null;
        }
    }
    /**
     * ç”Ÿæˆä¸Žæ—§ç³»ç»Ÿå…¼å®¹çš„访问URL
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param mediaId åª’体ID
     * @param isThumbnail æ˜¯å¦ä¸ºç¼©ç•¥å›¾
     * @return å…¼å®¹çš„访问URL
     */
    @Override
    public String generateCompatibleUrl(Long dispatchOrdID, String mediaId, boolean isThumbnail) {
        try {
            String yearMonth = DateUtils.dateTimeNow("yyyyMM");
            String fileName = dispatchOrdID + "_" + mediaId + ".jpg";
            if (isThumbnail) {
                fileName = "s_" + fileName;
            }
            return legacyConfig.getFileServerUrl() + "/" + yearMonth + "/" + fileName;
        } catch (Exception e) {
            log.error("生成兼容URL失败:{}", e.getMessage());
            return null;
        }
    }
    /**
     * æ ¹æ®æœåŠ¡å•ID查询图片数据
     *
     * @param sOrdIDDt æœåŠ¡å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    @Override
    public List<ImageData> selectImageDataBySOrdIDDt(Long sOrdIDDt) {
        return imageDataMapper.selectImageDataBySOrdIDDt(sOrdIDDt);
    }
    /**
     * æ ¹æ®å›¾ç‰‡ç±»åž‹æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     *
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    @Override
    public List<ImageData> selectImageDataByType(Integer imageType) {
        ImageData imageData = new ImageData();
        imageData.setImageType(imageType);
        return imageDataMapper.selectImageDataList(imageData);
    }
    /**
     * æ ‡è®°å›¾ç‰‡ä¸ºåˆ é™¤çŠ¶æ€
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return ç»“æžœ
     */
    @Override
    public int markImageDataAsDeleted(Long id) {
        ImageData imageData = new ImageData();
        imageData.setId(id);
        imageData.setImageDel(1); // 1表示已删除
        return imageDataMapper.updateImageData(imageData);
    }
    /**
     * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ å¤„理(原ASP代码转换)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param mediaId å¾®ä¿¡åª’体ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    public String uploadWxImage(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                String mediaId, Integer imageType, Integer adminId) {
        try {
            // èŽ·å–å›¾ç‰‡ç±»åž‹æžšä¸¾
            ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
            // å¦‚果有调度单ID,则处理调度单相关图片
            if (dispatchOrdID != null && dispatchOrdID > 0) {
                return processDispatchOrderImage(dispatchOrdID, serviceOrdID, mediaId, imageTypeEnum, adminId);
            }
            // å¦‚果只有OA用户ID,则更新用户头像
            else if (oaid != null && oaid > 0) {
                return updateUserAvatar(oaid, mediaId);
            }
            else {
                return "参数错误:缺少必要的参数";
            }
        } catch (Exception e) {
            return "处理失败:" + e.getMessage();
        }
    }
    /**
     * å¤„理调度单相关图片上传
     */
    private String processDispatchOrderImage(Long dispatchOrdID, Long serviceOrdID,
                                             String mediaId, ImageTypeEnum imageTypeEnum, Integer adminId) {
        try {
            // ç”Ÿæˆå›¾ç‰‡URL路径
            String imageUrl = generateImageUrl(dispatchOrdID, mediaId, false);
            String imageUrls = generateImageUrl(dispatchOrdID, mediaId, true);
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageTypeEnum.getCode());
            imageData.setImageUrl(imageUrl);
            imageData.setImageUrls(imageUrls);
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0); // 0表示未删除
            // æ’入图片数据
            int result = imageDataMapper.insertImageData(imageData);
            if (result <= 0) {
                return "图片数据保存失败";
            }
                return "图片上传成功,ID: " + imageData.getId() + ",类型: " + imageTypeEnum.getDescription();
        } catch (Exception e) {
            return "处理调度单图片失败:" + e.getMessage();
        }
    }
    /**
     * æ›´æ–°ç”¨æˆ·å¤´åƒ
     */
    private String updateUserAvatar(Integer oaid, String mediaId) {
        try {
            // è¿™é‡Œéœ€è¦è°ƒç”¨OA用户服务来更新头像
            // ç”±äºŽæ²¡æœ‰OA用户服务的具体实现,这里只是示例
            String avatarUrl = "/upload/" + oaid + "_" + mediaId + ".jpg";
            // TODO: è°ƒç”¨OA用户服务更新头像
            // oaUserService.updateAvatar(oaid, avatarUrl);
            return "用户头像更新成功";
        } catch (Exception e) {
            return "更新用户头像失败:" + e.getMessage();
        }
    }
    /**
     * ç”Ÿæˆå›¾ç‰‡URL
     */
    private String generateImageUrl(Long dispatchOrdID, String mediaId, boolean isThumbnail) {
        Date now = new Date();
        int year = now.getYear() + 1900; // Java的年份需要加1900
        int month = now.getMonth() + 1;  // Java的月份从0开始
        String monthStr = String.format("%02d", month);
        String prefix = isThumbnail ? "/upload/" + year + monthStr + "/s_" : "/upload/" + year + monthStr + "/";
        return prefix + dispatchOrdID + "_" + mediaId + ".jpg";
    }
    /**
     * ä¿å­˜å¾®ä¿¡æ–‡ä»¶åˆ°æœ¬åœ°ï¼ˆåŽŸPHP代码转换)
     *
     * @param filename æ–‡ä»¶å
     * @param fileContent æ–‡ä»¶å†…容
     * @return æ˜¯å¦ä¿å­˜æˆåŠŸ
     */
    public boolean saveWeixinFile(String filename, byte[] fileContent) {
        FileOutputStream localFile = null;
        try {
            // ç¡®ä¿ç›®å½•存在
            File file = new File(filename);
            File parentDir = file.getParentFile();
            if (!parentDir.exists()) {
                parentDir.mkdirs();
            }
            localFile = new FileOutputStream(filename);
            localFile.write(fileContent);
            localFile.flush();
            log.info("成功保存微信文件:{}", filename);
            return true;
        } catch (Exception e) {
            log.error("保存微信文件失败:{}", e.getMessage(), e);
            return false;
        } finally {
            if (localFile != null) {
                try {
                    localFile.close();
                } catch (IOException e) {
                    log.error("关闭文件流失败:{}", e.getMessage());
                }
            }
        }
    }
    /**
     * ç”Ÿæˆç¼©ç•¥å›¾ï¼ˆåŽŸPHP代码转换)
     *
     * @param bigImgPath åŽŸå§‹å¤§å›¾è·¯å¾„
     * @param width ç¼©ç•¥å›¾å®½åº¦
     * @param height ç¼©ç•¥å›¾é«˜åº¦ï¼ˆ0表示按比例计算)
     * @param smallImgPath ç¼©ç•¥å›¾ä¿å­˜è·¯å¾„
     * @return æ˜¯å¦ç”ŸæˆæˆåŠŸ
     */
    public boolean createThumbnail(String bigImgPath, int width, int height, String smallImgPath) {
        try {
            // è¯»å–原始图片
            BufferedImage originalImage = ImageIO.read(new File(bigImgPath));
            if (originalImage == null) {
                log.error("无法读取原始图片:{}", bigImgPath);
                return false;
            }
            int srcWidth = originalImage.getWidth();
            int srcHeight = originalImage.getHeight();
            // è®¡ç®—缩略图尺寸
            if (height == 0) {
                height = (int) Math.floor((double) width / srcWidth * srcHeight);
            } else if (width == 0) {
                width = (int) Math.floor((double) height / srcHeight * srcWidth);
            }
            // åˆ›å»ºç¼©ç•¥å›¾
            BufferedImage thumbnail = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = thumbnail.createGraphics();
            // è®¾ç½®æ¸²æŸ“质量
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            // ç»˜åˆ¶ç¼©ç•¥å›¾
            g.drawImage(originalImage, 0, 0, width, height, null);
            g.dispose();
            // ç¡®ä¿ç›®å½•存在
            File smallImgFile = new File(smallImgPath);
            File parentDir = smallImgFile.getParentFile();
            if (!parentDir.exists()) {
                parentDir.mkdirs();
            }
            // ä¿å­˜ç¼©ç•¥å›¾
            ImageIO.write(thumbnail, "jpg", smallImgFile);
            log.info("成功生成缩略图:{} -> {} ({}x{})", bigImgPath, smallImgPath, width, height);
            return true;
        } catch (Exception e) {
            log.error("生成缩略图失败:{}", e.getMessage(), e);
            return false;
        }
    }
    /**
     * å¤„理调度单相关图片上传(包含文件URL)
     */
    private String processDispatchOrderImageWithFiles(Long dispatchOrdID, Long serviceOrdID,
                                                      String mediaId, ImageTypeEnum imageTypeEnum,
                                                      Integer adminId, String originalImageUrl, String thumbnailUrl) {
        try {
            // åˆ›å»ºå›¾ç‰‡æ•°æ®å¯¹è±¡
            ImageData imageData = new ImageData();
            imageData.setDOrdIDDt(dispatchOrdID);
            imageData.setSOrdIDDt(serviceOrdID);
            imageData.setImageType(imageTypeEnum.getCode());
            imageData.setImageUrl(originalImageUrl);
            imageData.setImageUrls(thumbnailUrl);
            imageData.setUpImageTime(new Date());
            imageData.setUpImageOAid(adminId);
            imageData.setImageDel(0); // 0表示未删除
            // æ’入图片数据
            int result = imageDataMapper.insertImageData(imageData);
            if (result <= 0) {
                return "图片数据保存失败";
            }
                return "图片上传成功,ID: " + imageData.getId() + ",类型: " + imageTypeEnum.getDescription() +
                        ",原始图片URL: " + originalImageUrl + ",缩略图URL: " + thumbnailUrl;
        } catch (Exception e) {
            return "处理调度单图片失败:" + e.getMessage();
        }
    }
    /**
     * æ›´æ–°ç”¨æˆ·å¤´åƒï¼ˆåŒ…含文件URL)
     */
    private String updateUserAvatarWithFile(Integer oaid, String mediaId, String imageUrl) {
        try {
            // è¿™é‡Œéœ€è¦è°ƒç”¨OA用户服务来更新头像
            // TODO: è°ƒç”¨OA用户服务更新头像
            // oaUserService.updateAvatar(oaid, imageUrl);
            return "用户头像更新成功,URL:" + imageUrl;
        } catch (Exception e) {
            return "更新用户头像失败:" + e.getMessage();
        }
    }
    /**
     * æ£€æŸ¥æ–‡ä»¶å…¼å®¹æ€§ï¼ˆç¡®ä¿ä¸Žæ—§ç³»ç»Ÿå…¼å®¹ï¼‰
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return å…¼å®¹æ€§æ£€æŸ¥ç»“æžœ
     */
    public String checkFileCompatibility(String filePath) {
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                return "文件不存在:" + filePath;
            }
            // æ£€æŸ¥æ–‡ä»¶å‘½åæ˜¯å¦ç¬¦åˆæ—§ç³»ç»Ÿè§„范
            String fileName = file.getName();
            if (!fileName.matches("^(\\d+|[A-Z]+)_[A-Za-z0-9_]+\\.jpg$")) {
                return "文件命名不符合旧系统规范:" + fileName;
            }
            // æ£€æŸ¥ç›®å½•结构是否符合旧系统规范
            String parentDir = file.getParentFile().getName();
            if (!parentDir.matches("^\\d{6}$")) { // å¹´æœˆæ ¼å¼ï¼š202412
                return "目录结构不符合旧系统规范:" + parentDir;
            }
            return "文件兼容性检查通过:" + filePath;
        } catch (Exception e) {
            return "兼容性检查失败:" + e.getMessage();
        }
    }
    /**
     * éªŒè¯URL格式是否与旧系统兼容
     *
     * @param url å›¾ç‰‡URL
     * @return æ˜¯å¦å…¼å®¹
     */
    public boolean isUrlCompatible(String url) {
        try {
            // æ£€æŸ¥URL格式是否符合旧系统规范
            // æ—§ç³»ç»Ÿæ ¼å¼ï¼š/upload/年月/文件名.jpg
            return url.matches("^/upload/\\d{6}/[A-Za-z0-9_]+\\.jpg$");
        } catch (Exception e) {
            log.error("URL兼容性检查失败:{}", e.getMessage());
            return false;
        }
    }
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(允许直接传入图片URL)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param thumbnailUrl ç¼©ç•¥å›¾URL(可选)
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    @Override
    public String uploadImageByUrl(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                   String imageUrl, String thumbnailUrl, Integer imageType, Integer adminId) {
        try {
            // éªŒè¯å‚æ•°
            if (imageUrl == null || imageUrl.trim().isEmpty()) {
                return "图片URL不能为空";
            }
            // å¤„理业务逻辑
            if (dispatchOrdID != null && dispatchOrdID > 0) {
                // è°ƒåº¦å•相关图片
                ImageTypeEnum imageTypeEnum = ImageTypeEnum.getByCode(imageType);
                return processDispatchOrderImageWithFiles(dispatchOrdID, serviceOrdID, null,
                        imageTypeEnum, adminId, imageUrl, thumbnailUrl);
            } else if (oaid != null && oaid > 0) {
                // OA用户头像
                return updateUserAvatarWithFile(oaid, null, imageUrl);
            } else {
                return "参数错误:缺少必要的参数(调度单ID或OA用户ID)";
            }
        } catch (Exception e) {
            log.error("通过URL上传图片失败:{}", e.getMessage(), e);
            return "处理失败:" + e.getMessage();
        }
    }
    /**
     * é€šè¿‡å›¾ç‰‡URL上传处理(简化版本,自动生成缩略图URL)
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param serviceOrdID æœåŠ¡å•ID
     * @param oaid OA用户ID
     * @param imageUrl å›¾ç‰‡URL
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @param adminId å½“前管理员ID
     * @return å¤„理结果
     */
    @Override
    public String uploadImageByUrlSimple(Long dispatchOrdID, Long serviceOrdID, Integer oaid,
                                         String imageUrl, Integer imageType, Integer adminId) {
        try {
            // éªŒè¯å‚æ•°
            if (imageUrl == null || imageUrl.trim().isEmpty()) {
                return "图片URL不能为空";
            }
            // è‡ªåŠ¨ç”Ÿæˆç¼©ç•¥å›¾URL(如果原图URL包含文件名)
            String thumbnailUrl = null;
            if (imageUrl.contains("/")) {
                String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
                if (fileName.contains(".")) {
                    String nameWithoutExt = fileName.substring(0, fileName.lastIndexOf("."));
                    String extension = fileName.substring(fileName.lastIndexOf("."));
                    String thumbnailFileName = "s_" + nameWithoutExt + extension;
                    thumbnailUrl = imageUrl.substring(0, imageUrl.lastIndexOf("/") + 1) + thumbnailFileName;
                }
            }
            // è°ƒç”¨å®Œæ•´ç‰ˆæœ¬çš„æ–¹æ³•
            return uploadImageByUrl(dispatchOrdID, serviceOrdID, oaid, imageUrl, thumbnailUrl, imageType, adminId);
        } catch (Exception e) {
            log.error("通过URL上传图片(简化版)失败:{}", e.getMessage(), e);
            return "处理失败:" + e.getMessage();
        }
    }
    @Override
    public String uploadWxImageWithDownload(String accessToken, String mediaId, Long dispatchOrdID, Integer oaid, Integer imageType, Integer adminId) {
        try {
            // ç¡®å®šç›®æ ‡è·¯å¾„
            String targetPath;
            if (dispatchOrdID != null && dispatchOrdID > 0) {
                // è°ƒåº¦å•相关图片:按年月组织目录
                String yearMonth = DateUtils.dateTimeNow("yyyyMM");
                targetPath = yearMonth + "/" + dispatchOrdID;
            } else if (oaid != null && oaid > 0) {
                // OA用户头像
                targetPath = "avatar/" + oaid;
            } else {
                return "参数错误:缺少必要的参数";
            }
            // ä½¿ç”¨æ–‡ä»¶ä¸Šä¼ æœåŠ¡ä»Žå¾®ä¿¡ä¸‹è½½å¹¶ä¸Šä¼ æ–‡ä»¶
            FileUploadResponse uploadResponse = fileUploadService.uploadFromWechat(accessToken, mediaId, targetPath);
            if (!uploadResponse.isSuccess()) {
                return "文件上传失败:" + uploadResponse.getMessage();
            }
            // èŽ·å–ä¸Šä¼ åŽçš„æ–‡ä»¶è·¯å¾„
            String originalImageUrl = uploadResponse.getFilePath();
            String thumbnailUrl = uploadResponse.getThumbnailPath();
            // å¤„理业务逻辑
            if (dispatchOrdID != null && dispatchOrdID > 0) {
                return processDispatchOrderImageWithFiles(dispatchOrdID, null, mediaId,
                        ImageTypeEnum.getByCode(imageType), adminId, originalImageUrl, thumbnailUrl);
            } else if (oaid != null && oaid > 0) {
                return updateUserAvatarWithFile(oaid, mediaId, originalImageUrl);
            }
            return "图片上传成功,文件路径:" + originalImageUrl;
        } catch (Exception e) {
            log.error("微信图片上传处理失败:{}", e.getMessage(), e);
            return "处理失败:" + e.getMessage();
        }
    }
    /**
     * æ ¹æ®è°ƒåº¦å•ID和图片类型查询图片数据
     *
     * @param dispatchOrdID è°ƒåº¦å•ID
     * @param imageType å›¾ç‰‡ç±»åž‹
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    @Override
    public List<ImageData> selectImageDataByDOrdIDDtAndType(Long dispatchOrdID, Integer imageType) {
        // åˆ›å»ºæŸ¥è¯¢æ¡ä»¶
        ImageData imageData = new ImageData();
        imageData.setDOrdIDDt(dispatchOrdID);
        imageData.setImageType(imageType);
        // æŸ¥è¯¢å›¾ç‰‡æ•°æ®
        return imageDataMapper.selectImageDataList(imageData);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/imagedata/WxImageUploadRequest.java
New file
@@ -0,0 +1,30 @@
package com.ruoyi.system.imagedata;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * å¾®ä¿¡å›¾ç‰‡ä¸Šä¼ è¯·æ±‚DTO
 */
@Data
public class WxImageUploadRequest {
    @JsonProperty("access_token")
    private String accessToken;
    @JsonProperty("DispatchOrdID")
    private Long dispatchOrdID;
    @JsonProperty("ServiceOrdID")
    private Long serviceOrdID;
    @JsonProperty("OAID")
    private Integer oaid;
    @JsonProperty("media_id")
    private String mediaId;
    @JsonProperty("ImageType")
    private Integer imageType;
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/ImageDataMapper.java
New file
@@ -0,0 +1,78 @@
package com.ruoyi.system.mapper;
import com.ruoyi.common.annotation.DataSource;
import com.ruoyi.common.enums.DataSourceType;
import com.ruoyi.system.domain.ImageData;
import java.util.List;
/**
 * å›¾ç‰‡æ•°æ®Mapper接口
 */
@DataSource(DataSourceType.SQLSERVER)
public interface ImageDataMapper {
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return å›¾ç‰‡æ•°æ®
     */
    public ImageData selectImageDataById(Long id);
    /**
     * æŸ¥è¯¢å›¾ç‰‡æ•°æ®åˆ—表
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataList(ImageData imageData);
    /**
     * æ–°å¢žå›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    public int insertImageData(ImageData imageData);
    /**
     * ä¿®æ”¹å›¾ç‰‡æ•°æ®
     *
     * @param imageData å›¾ç‰‡æ•°æ®
     * @return ç»“æžœ
     */
    public int updateImageData(ImageData imageData);
    /**
     * åˆ é™¤å›¾ç‰‡æ•°æ®
     *
     * @param id å›¾ç‰‡æ•°æ®ä¸»é”®
     * @return ç»“æžœ
     */
    public int deleteImageDataById(Long id);
    /**
     * æ‰¹é‡åˆ é™¤å›¾ç‰‡æ•°æ®
     *
     * @param ids éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
    public int deleteImageDataByIds(Long[] ids);
    /**
     * æ ¹æ®è°ƒåº¦å•ID查询图片数据
     *
     * @param dOrdIDDt è°ƒåº¦å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataByDOrdIDDt(Long dOrdIDDt);
    /**
     * æ ¹æ®æœåŠ¡å•ID查询图片数据
     *
     * @param sOrdIDDt æœåŠ¡å•ID
     * @return å›¾ç‰‡æ•°æ®é›†åˆ
     */
    public List<ImageData> selectImageDataBySOrdIDDt(Long sOrdIDDt);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysTaskService.java
@@ -96,9 +96,21 @@
     * 
     * @param taskId ä»»åŠ¡ID
     * @param file æ–‡ä»¶
     * @param category é™„件分类
     * @return ç»“æžœ
     */
    public int uploadAttachment(Long taskId, MultipartFile file);
    public int uploadAttachment(Long taskId, MultipartFile file, String category);
    /**
     * ä»Žå¾®ä¿¡mediaId上传任务附件
     *
     * @param taskId ä»»åŠ¡ID
     * @param accessToken å¾®ä¿¡AccessToken
     * @param mediaId å¾®ä¿¡mediaId
     * @param category é™„件分类
     * @return ç»“æžœ
     */
    public int uploadAttachmentFromWechat(Long taskId, String accessToken, String mediaId, String category);
    /**
     * åˆ é™¤ä»»åС附件
@@ -107,6 +119,14 @@
     * @return ç»“æžœ
     */
    public int deleteAttachment(Long attachmentId);
    /**
     * æ ¹æ®ID获取附件详情
     *
     * @param attachmentId é™„ä»¶ID
     * @return é™„件详情
     */
    public SysTaskAttachment getAttachmentById(Long attachmentId);
    /**
     * åˆ†é…è½¦è¾†ç»™ä»»åŠ¡
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysTaskServiceImpl.java
@@ -6,7 +6,11 @@
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import com.ruoyi.system.utils.TaskCodeGenerator;
import org.springframework.beans.factory.annotation.Autowired;
@@ -496,11 +500,12 @@
     * 
     * @param taskId ä»»åŠ¡ID
     * @param file æ–‡ä»¶
     * @param category é™„件分类
     * @return ç»“æžœ
     */
    @Override
    @Transactional
    public int uploadAttachment(Long taskId, MultipartFile file) {
    public int uploadAttachment(Long taskId, MultipartFile file, String category) {
        try {
            // ä¸Šä¼ æ–‡ä»¶
            String fileName = FileUploadUtils.upload("/task", file);
@@ -512,6 +517,7 @@
            attachment.setFilePath(filePath);
            attachment.setFileSize(file.getSize());
            attachment.setFileType(getFileType(file.getOriginalFilename()));
            attachment.setAttachmentCategory(category);
            attachment.setUploadTime(DateUtils.getNowDate());
            attachment.setUploadBy(SecurityUtils.getUsername());
            
@@ -519,14 +525,112 @@
            
            // è®°å½•操作日志
            if (result > 0) {
                String categoryDesc = getCategoryDesc(category);
                recordTaskLog(taskId, "UPDATE", "上传附件", null, 
                             "上传文件:" + file.getOriginalFilename(),
                             "上传文件:" + file.getOriginalFilename() + "(分类:" + categoryDesc + ")",
                             SecurityUtils.getUserId(), SecurityUtils.getUsername());
            }
            
            return result;
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败:" + e.getMessage());
        }
    }
    /**
     * ä»Žå¾®ä¿¡mediaId上传任务附件
     *
     * @param taskId ä»»åŠ¡ID
     * @param accessToken å¾®ä¿¡AccessToken
     * @param mediaId å¾®ä¿¡mediaId
     * @param category é™„件分类
     * @return ç»“æžœ
     */
    @Override
    @Transactional
    public int uploadAttachmentFromWechat(Long taskId, String accessToken, String mediaId, String category) {
        try {
            // ä»Žå¾®ä¿¡æœåŠ¡å™¨ä¸‹è½½æ–‡ä»¶
            String wechatUrl = String.format(
                "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s",
                accessToken, mediaId
            );
            byte[] fileBytes = downloadFromUrl(wechatUrl);
            if (fileBytes == null || fileBytes.length == 0) {
                throw new RuntimeException("从微信下载文件失败");
            }
            // ç”Ÿæˆæ–‡ä»¶åï¼ˆä½¿ç”¨mediaId作为文件名的一部分)
            String fileName = "wx_" + mediaId.substring(0, Math.min(20, mediaId.length())) + "_" + System.currentTimeMillis() + ".jpg";
            // ä¿å­˜åˆ°æœ¬åœ°
            String baseDir = FileUploadUtils.getDefaultBaseDir();
            String datePath = DateUtils.datePath();
            String uploadDir = baseDir + "/task/" + datePath;
            // åˆ›å»ºç›®å½•
            File uploadPath = new File(uploadDir);
            if (!uploadPath.exists()) {
                uploadPath.mkdirs();
            }
            // ä¿å­˜æ–‡ä»¶
            String filePath = uploadDir + "/" + fileName;
            File file = new File(filePath);
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(fileBytes);
            }
            // ä¿å­˜é™„件记录
            SysTaskAttachment attachment = new SysTaskAttachment();
            attachment.setTaskId(taskId);
            attachment.setFileName(fileName);
            attachment.setFilePath(filePath);
            attachment.setFileSize((long) fileBytes.length);
            attachment.setFileType("jpg");
            attachment.setAttachmentCategory(category);
            attachment.setUploadTime(DateUtils.getNowDate());
            attachment.setUploadBy(SecurityUtils.getUsername());
            int result = sysTaskAttachmentMapper.insertSysTaskAttachment(attachment);
            // è®°å½•操作日志
            if (result > 0) {
                String categoryDesc = getCategoryDesc(category);
                recordTaskLog(taskId, "UPDATE", "上传附件", null,
                             "通过微信上传文件:" + fileName + "(分类:" + categoryDesc + ")",
                             SecurityUtils.getUserId(), SecurityUtils.getUsername());
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException("从微信上传文件失败:" + e.getMessage());
        }
    }
    /**
     * ä»Ž URL ä¸‹è½½æ–‡ä»¶
     */
    private byte[] downloadFromUrl(String fileUrl) throws IOException {
        URL url = new URL(fileUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(10000);
        connection.setReadTimeout(30000);
        try (InputStream inputStream = connection.getInputStream()) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            return outputStream.toByteArray();
        } finally {
            connection.disconnect();
        }
    }
@@ -561,6 +665,17 @@
        }
        
        return result;
    }
    /**
     * æ ¹æ®ID获取附件详情
     *
     * @param attachmentId é™„ä»¶ID
     * @return é™„件详情
     */
    @Override
    public SysTaskAttachment getAttachmentById(Long attachmentId) {
        return sysTaskAttachmentMapper.selectSysTaskAttachmentByAttachmentId(attachmentId);
    }
    /**
@@ -1055,4 +1170,25 @@
        
        sysTaskWelfareMapper.insertSysTaskWelfare(welfareInfo);
    }
    /**
     * èŽ·å–é™„ä»¶åˆ†ç±»æè¿°
     *
     * @param category é™„件分类代码
     * @return åˆ†ç±»æè¿°
     */
    private String getCategoryDesc(String category) {
        if (category == null || category.isEmpty()) {
            return "未分类";
        }
        switch (category) {
            case "1": return "知情同意书";
            case "2": return "病人资料";
            case "3": return "操作记录";
            case "4": return "出车前";
            case "5": return "出车后";
            case "6": return "系安全带";
            default: return "其他";
        }
    }
}
ruoyi-system/src/main/resources/mapper/system/ImageDataMapper.xml
New file
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.ImageDataMapper">
    <resultMap type="ImageData" id="ImageDataResult">
        <result property="id" column="id"/>
        <result property="dOrdIDDt" column="DOrdIDDt"/>
        <result property="sOrdIDDt" column="SOrdIDDt"/>
        <result property="imageType" column="ImageType"/>
        <result property="imageUrl" column="ImageUrl"/>
        <result property="imageUrls" column="ImageUrls"/>
        <result property="imageDeg" column="ImageDeg"/>
        <result property="upImageTime" column="UpImageTime"/>
        <result property="upImageOAid" column="UpImageOAid"/>
        <result property="imageDel" column="ImageDel"/>
        <result property="isAP" column="isAP"/>
        <result property="isAP_ID" column="isAP_ID"/>
        <result property="isAP_Time" column="isAP_Time"/>
    </resultMap>
    <sql id="selectImageDataVo">
        select id, DOrdIDDt, SOrdIDDt, ImageType, ImageUrl, ImageUrls, ImageDeg, UpImageTime, UpImageOAid, ImageDel, isAP, isAP_ID, isAP_Time
        from ImageData
    </sql>
    <select id="selectImageDataList" parameterType="ImageData" resultMap="ImageDataResult">
        <include refid="selectImageDataVo"/>
        <where>
            <if test="dOrdIDDt != null "> and DOrdIDDt = #{dOrdIDDt}</if>
            <if test="sOrdIDDt != null "> and SOrdIDDt = #{sOrdIDDt}</if>
            <if test="imageType != null "> and ImageType = #{imageType}</if>
            <if test="imageUrl != null  and imageUrl != ''"> and ImageUrl like concat('%', #{imageUrl}, '%')</if>
            <if test="imageUrls != null  and imageUrls != ''"> and ImageUrls like concat('%', #{imageUrls}, '%')</if>
            <if test="imageDeg != null "> and ImageDeg = #{imageDeg}</if>
            <if test="upImageTime != null "> and UpImageTime = #{upImageTime}</if>
            <if test="upImageOAid != null "> and UpImageOAid = #{upImageOAid}</if>
            <if test="imageDel != null "> and ImageDel = #{imageDel}</if>
            <if test="isAP != null "> and isAP = #{isAP}</if>
            <if test="isAP_ID != null "> and isAP_ID = #{isAP_ID}</if>
            <if test="isAP_Time != null "> and isAP_Time = #{isAP_Time}</if>
        </where>
    </select>
    <select id="selectImageDataById" parameterType="Long" resultMap="ImageDataResult">
        <include refid="selectImageDataVo"/>
        where id = #{id}
    </select>
    <select id="selectImageDataByDOrdIDDt" parameterType="Long" resultMap="ImageDataResult">
        <include refid="selectImageDataVo"/>
        where DOrdIDDt = #{dOrdIDDt}
    </select>
    <select id="selectImageDataBySOrdIDDt" parameterType="Long" resultMap="ImageDataResult">
        <include refid="selectImageDataVo"/>
        where SOrdIDDt = #{sOrdIDDt}
    </select>
    <insert id="insertImageData" parameterType="ImageData" useGeneratedKeys="true" keyProperty="id">
        insert into ImageData
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="dOrdIDDt != null">DOrdIDDt,</if>
            <if test="sOrdIDDt != null">SOrdIDDt,</if>
            <if test="imageType != null">ImageType,</if>
            <if test="imageUrl != null">ImageUrl,</if>
            <if test="imageUrls != null">ImageUrls,</if>
            <if test="imageDeg != null">ImageDeg,</if>
            <if test="upImageTime != null">UpImageTime,</if>
            <if test="upImageOAid != null">UpImageOAid,</if>
            <if test="imageDel != null">ImageDel,</if>
            <if test="isAP != null">isAP,</if>
            <if test="isAP_ID != null">isAP_ID,</if>
            <if test="isAP_Time != null">isAP_Time,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="dOrdIDDt != null">#{dOrdIDDt},</if>
            <if test="sOrdIDDt != null">#{sOrdIDDt},</if>
            <if test="imageType != null">#{imageType},</if>
            <if test="imageUrl != null">#{imageUrl},</if>
            <if test="imageUrls != null">#{imageUrls},</if>
            <if test="imageDeg != null">#{imageDeg},</if>
            <if test="upImageTime != null">#{upImageTime},</if>
            <if test="upImageOAid != null">#{upImageOAid},</if>
            <if test="imageDel != null">#{imageDel},</if>
            <if test="isAP != null">#{isAP},</if>
            <if test="isAP_ID != null">#{isAP_ID},</if>
            <if test="isAP_Time != null">#{isAP_Time},</if>
         </trim>
    </insert>
    <update id="updateImageData" parameterType="ImageData">
        update ImageData
        <trim prefix="SET" suffixOverrides=",">
            <if test="dOrdIDDt != null">DOrdIDDt = #{dOrdIDDt},</if>
            <if test="sOrdIDDt != null">SOrdIDDt = #{sOrdIDDt},</if>
            <if test="imageType != null">ImageType = #{imageType},</if>
            <if test="imageUrl != null">ImageUrl = #{imageUrl},</if>
            <if test="imageUrls != null">ImageUrls = #{imageUrls},</if>
            <if test="imageDeg != null">ImageDeg = #{imageDeg},</if>
            <if test="upImageTime != null">UpImageTime = #{upImageTime},</if>
            <if test="upImageOAid != null">UpImageOAid = #{upImageOAid},</if>
            <if test="imageDel != null">ImageDel = #{imageDel},</if>
            <if test="isAP != null">isAP = #{isAP},</if>
            <if test="isAP_ID != null">isAP_ID = #{isAP_ID},</if>
            <if test="isAP_Time != null">isAP_Time = #{isAP_Time},</if>
        </trim>
        where id = #{id}
    </update>
    <delete id="deleteImageDataById" parameterType="Long">
        delete from ImageData where id = #{id}
    </delete>
    <delete id="deleteImageDataByIds" parameterType="String">
        delete from ImageData where id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/SysTaskAttachmentMapper.xml
@@ -11,12 +11,13 @@
        <result property="filePath"         column="file_path"         />
        <result property="fileSize"         column="file_size"         />
        <result property="fileType"         column="file_type"         />
        <result property="attachmentCategory" column="attachment_category" />
        <result property="uploadTime"       column="upload_time"       />
        <result property="uploadBy"         column="upload_by"         />
    </resultMap>
    <sql id="selectSysTaskAttachmentVo">
        select attachment_id, task_id, file_name, file_path, file_size, file_type, upload_time, upload_by
        select attachment_id, task_id, file_name, file_path, file_size, file_type, attachment_category, upload_time, upload_by
        from sys_task_attachment
    </sql>
@@ -26,6 +27,7 @@
            <if test="taskId != null "> and task_id = #{taskId}</if>
            <if test="fileName != null  and fileName != ''"> and file_name like concat('%', #{fileName}, '%')</if>
            <if test="fileType != null  and fileType != ''"> and file_type = #{fileType}</if>
            <if test="attachmentCategory != null and attachmentCategory != ''"> and attachment_category = #{attachmentCategory}</if>
            <if test="uploadBy != null  and uploadBy != ''"> and upload_by like concat('%', #{uploadBy}, '%')</if>
        </where>
        order by upload_time desc
@@ -50,6 +52,7 @@
            <if test="filePath != null and filePath != ''">file_path,</if>
            <if test="fileSize != null">file_size,</if>
            <if test="fileType != null">file_type,</if>
            <if test="attachmentCategory != null and attachmentCategory != ''">attachment_category,</if>
            <if test="uploadTime != null">upload_time,</if>
            <if test="uploadBy != null and uploadBy != ''">upload_by,</if>
         </trim>
@@ -59,6 +62,7 @@
            <if test="filePath != null and filePath != ''">#{filePath},</if>
            <if test="fileSize != null">#{fileSize},</if>
            <if test="fileType != null">#{fileType},</if>
            <if test="attachmentCategory != null and attachmentCategory != ''">#{attachmentCategory},</if>
            <if test="uploadTime != null">#{uploadTime},</if>
            <if test="uploadBy != null and uploadBy != ''">#{uploadBy},</if>
         </trim>
@@ -72,6 +76,7 @@
            <if test="filePath != null and filePath != ''">file_path = #{filePath},</if>
            <if test="fileSize != null">file_size = #{fileSize},</if>
            <if test="fileType != null">file_type = #{fileType},</if>
            <if test="attachmentCategory != null and attachmentCategory != ''">attachment_category = #{attachmentCategory},</if>
            <if test="uploadTime != null">upload_time = #{uploadTime},</if>
            <if test="uploadBy != null and uploadBy != ''">upload_by = #{uploadBy},</if>
        </trim>
sql/update_attachment_category.sql
New file
@@ -0,0 +1,8 @@
-- ä¸ºé™„件表添加附件分类字段
-- ----------------------------
ALTER TABLE sys_task_attachment
ADD COLUMN attachment_category VARCHAR(20) COMMENT '附件分类:1-知情同意书,2-病人资料,3-操作记录,4-出车前,5-出车后,6-系安全带'
AFTER file_type;
-- æ·»åŠ ç´¢å¼•
ALTER TABLE sys_task_attachment ADD INDEX idx_attachment_category (attachment_category);