最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端文件上傳同名沖突檢測(cè)的詳細(xì)解決方案

 更新時(shí)間:2026年02月26日 09:49:41   作者:dbt@L  
在上傳文件之前,我們需要先判斷目標(biāo)文件夾下是否已經(jīng)存在同名文件,這篇文章主要介紹了前端文件上傳同名沖突檢測(cè)的詳細(xì)解決方案,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

在檔案管理系統(tǒng)中,用戶在同一目錄下上傳同名文件時(shí),系統(tǒng)沒有任何提示,新文件被靜默忽略。本文記錄這個(gè)文件重復(fù)校驗(yàn)問題的前后端協(xié)同解決方案。

一、問題背景

1.1 問題現(xiàn)象

操作步驟預(yù)期結(jié)果實(shí)際結(jié)果
1. 選擇三級(jí)目錄"規(guī)章制度"--
2. 上傳文件 合同.pdf上傳成功上傳成功
3. 再次選擇同一目錄--
4. 上傳另一個(gè) 合同.pdf提示"已存在同個(gè)名稱的文件"無(wú)任何提示,新文件被忽略

用戶困惑:明明上傳了新文件,但列表里只有第一次上傳的內(nèi)容。

1.2 問題影響

  • 用戶誤以為上傳成功,實(shí)際新文件丟失
  • 無(wú)法通過上傳覆蓋更新文件
  • 用戶體驗(yàn)差,容易造成數(shù)據(jù)丟失

1.3 修復(fù)歷程

時(shí)間操作結(jié)果
12-11創(chuàng)建問題-
12-11AI分析并修復(fù)提交前端+后端代碼
12-12合并代碼驗(yàn)證通過
12-25驗(yàn)收關(guān)閉功能正常

激活次數(shù):0次(一次修復(fù)成功)

二、問題分析

2.1 業(yè)務(wù)場(chǎng)景

檔案管理采用三級(jí)目錄結(jié)構(gòu):

項(xiàng)目文檔
├── 規(guī)章制度
│   ├── 運(yùn)營(yíng)管理
│   │   ├── 合同.pdf      ← 已存在
│   │   └── 規(guī)定.docx
│   └── 用戶協(xié)議
│       └── 協(xié)議.pdf
└── 會(huì)議記錄
    └── ...

用戶上傳文件時(shí)需要:

  1. 選擇目標(biāo)分類(三級(jí)目錄)
  2. 輸入文件名稱
  3. 上傳文件

2.2 問題根因

問題1:前端去重邏輯缺陷

原有代碼

// 提交時(shí)的去重邏輯
const submit = async () => {
  const allFiles = [...categoryFiles, ...processedFiles]

  // 問題:按 fileName 去重,但 fileName 可能不含擴(kuò)展名
  const uniqueFiles = []
  const fileNameSet = new Set()

  for (const file of allFiles) {
    const name = file.fileName || ''  // 可能是 "合同" 而非 "合同.pdf"
    if (name && !fileNameSet.has(name)) {
      fileNameSet.add(name)
      uniqueFiles.push(file)
    }
  }

  // 問題:重復(fù)文件被靜默過濾,沒有任何提示!
}

問題分析

  1. fileName 字段可能不含擴(kuò)展名,導(dǎo)致 合同.pdf合同.docx 被視為同名
  2. 去重時(shí)直接過濾,沒有給用戶任何反饋
  3. 用戶以為上傳成功,實(shí)際新文件被忽略

問題2:后端校驗(yàn)缺失

原有代碼

public ResponseDTO<String> update(CommunityArchiveUpdateDTO dto) {
    // 問題:只檢查 dto.getFileName(),但這個(gè)字段通常為空
    // 實(shí)際的文件名在 files 數(shù)組的每個(gè)對(duì)象中
    if (StringUtils.isNotBlank(dto.getFileName())) {
        // 這個(gè)分支幾乎不會(huì)進(jìn)入
        checkDuplicate(dto.getFileName());
    }

    // 直接保存,沒有校驗(yàn) files 中的重復(fù)文件
    archiveMapper.update(dto);
    return ResponseDTO.ok();
}

問題分析

  1. 校驗(yàn)邏輯依賴 fileName 字段,但該字段通常為空
  2. 真正的文件名在 files 數(shù)組中,但沒有被校驗(yàn)
  3. 后端作為最后防線,沒有起到應(yīng)有的攔截作用

2.3 問題傳導(dǎo)鏈

用戶選擇同名文件上傳
       ↓
前端:fileName 不含擴(kuò)展名,比對(duì)失敗
       ↓
前端:靜默去重,不提示用戶
       ↓
后端:fileName 為空,跳過校驗(yàn)
       ↓
數(shù)據(jù)庫(kù):只保留第一個(gè)文件
       ↓
用戶:以為上傳成功,實(shí)際新文件丟失

三、解決方案

3.1 前端修復(fù):提交前校驗(yàn)同名文件

核心思路

  1. 獲取完整文件名(含擴(kuò)展名)
  2. 查找目標(biāo)分類下的現(xiàn)有文件
  3. 比對(duì)是否存在同名文件
  4. 存在則提示并阻斷提交

修復(fù)代碼

const submit = async () => {
  if (!isFormValid.value) return

  const userFileName = fileName.value.trim()

  // ========== 步驟1:獲取完整文件名(含擴(kuò)展名)==========
  const getFullFileName = (item) => {
    if (typeof item === 'string') {
      return userFileName
    }
    // 優(yōu)先使用上傳時(shí)構(gòu)造的完整文件名
    const directName = item.fileNameWithExt || item.originalName
    if (directName && directName.includes('.')) {
      return directName
    }
    // 兜底:從原始文件名中提取擴(kuò)展名后拼接
    const original = item.originalFileName || item.name || ''
    if (original.includes('.')) {
      const ext = original.substring(original.lastIndexOf('.'))
      return userFileName + ext
    }
    return item.fileName || userFileName
  }

  // 獲取所有新上傳文件的完整文件名
  const newFullFileNames = urls.value.map(getFullFileName)

  // ========== 步驟2:查找目標(biāo)分類下的現(xiàn)有文件 ==========
  let categoryFiles = []
  if (originalCategoryData.value && originalCategoryData.value.length > 0) {
    let targetNode = null

    // 遍歷三級(jí)目錄結(jié)構(gòu),找到目標(biāo)分類節(jié)點(diǎn)
    for (const firstLevel of originalCategoryData.value) {
      if (firstLevel.name === firstSelected.value) {
        if (!secondSelected.value) {
          targetNode = firstLevel
          break
        }

        if (firstLevel.children) {
          for (const secondLevel of firstLevel.children) {
            if (secondLevel.name === secondSelected.value) {
              if (!thirdSelected.value) {
                targetNode = secondLevel
                break
              }

              if (secondLevel.children) {
                for (const thirdLevel of secondLevel.children) {
                  if (thirdLevel.name === thirdSelected.value) {
                    targetNode = thirdLevel
                    break
                  }
                }
              }
            }
          }
        }
      }
    }

    // 獲取該分類下的現(xiàn)有文件
    if (targetNode && targetNode.files && Array.isArray(targetNode.files)) {
      categoryFiles = targetNode.files
    }
  }

  // ========== 步驟3:檢查是否存在同名文件 ==========
  const normalizeName = (name) => (name || '').trim().toLowerCase()

  // 構(gòu)建現(xiàn)有文件名集合(忽略大小寫)
  const existingNameSet = new Set(
    categoryFiles
      .map((f) => normalizeName(f.fileName || f.name || f.originalName))
      .filter((n) => n)
  )

  // 檢查新文件是否與現(xiàn)有文件重名
  const newNameSet = new Set()
  for (const fullName of newFullFileNames) {
    const normalized = normalizeName(fullName)
    if (!normalized) continue

    // 與現(xiàn)有文件重名,或與本次上傳的其他文件重名
    if (existingNameSet.has(normalized) || newNameSet.has(normalized)) {
      uni.showToast({
        title: '已存在同個(gè)名稱的文件,請(qǐng)重新上傳',
        icon: 'none',
        duration: 3000,
      })
      return  // 阻斷提交
    }
    newNameSet.add(normalized)
  }

  // ========== 步驟4:通過校驗(yàn),繼續(xù)提交 ==========
  // ... 后續(xù)提交邏輯
}

3.2 后端修復(fù):基于實(shí)際文件名校驗(yàn)

核心思路

  1. files 數(shù)組中提取所有文件名
  2. 與數(shù)據(jù)庫(kù)中該分類下的現(xiàn)有文件比對(duì)
  3. 發(fā)現(xiàn)重復(fù)則返回錯(cuò)誤提示

修復(fù)代碼

@Service
public class CommunityArchiveService {

    @Autowired
    private FileService fileService;

    @Autowired
    private CommunityArchiveMapper archiveMapper;

    /**
     * 更新檔案(添加文件)
     */
    public ResponseDTO<String> update(CommunityArchiveUpdateDTO dto) {
        // 獲取現(xiàn)有文件列表
        List<String> existingFileKeys = archiveMapper.getFileKeysByCategoryId(dto.getId());

        // 獲取新上傳的文件keys
        List<String> newFileKeys = dto.getFiles().stream()
            .map(FileDTO::getFileKey)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

        // 計(jì)算真正新增的文件(差集)
        List<String> addedFileKeys = newFileKeys.stream()
            .filter(key -> !existingFileKeys.contains(key))
            .collect(Collectors.toList());

        if (!addedFileKeys.isEmpty()) {
            // 獲取已有文件的真實(shí)文件名
            Set<String> existingFileNames = fileService.getFileNamesByKeys(existingFileKeys)
                .stream()
                .map(String::toLowerCase)
                .collect(Collectors.toSet());

            // 獲取新增文件的真實(shí)文件名
            List<String> newFileNames = fileService.getFileNamesByKeys(addedFileKeys);

            // 檢查是否有重復(fù)文件名
            for (String newName : newFileNames) {
                if (existingFileNames.contains(newName.toLowerCase())) {
                    return ResponseDTO.userErrorParam("已存在同個(gè)名稱的文件,請(qǐng)重新上傳");
                }
            }
        }

        // 通過校驗(yàn),執(zhí)行更新
        archiveMapper.update(dto);
        return ResponseDTO.ok();
    }
}

3.3 文件上傳時(shí)保存完整文件名

前端上傳成功后的處理

// 上傳成功后,保存完整的文件信息
uni.uploadFile({
  // ... 上傳配置
  success: (uploadRes) => {
    const res = JSON.parse(uploadRes.data)

    if (res.code === 0 && res.data) {
      // 關(guān)鍵:保存完整文件名(含擴(kuò)展名)
      const fileData = {
        ...res.data,
        fileName: userFileName,                    // 用戶輸入的名稱
        originalFileName: originalFileName,        // 原始文件名
        fileNameWithExt: userFileName + fileExtension  // 完整文件名(含擴(kuò)展名)
      }

      // 保存到文件列表
      selectFileList.value = [{
        name: originalFileName,
        displayName: userFileName,
        url: fileData.fileUrl,
        resData: fileData
      }]

      urls.value = [fileData]
    }
  }
})

四、完整Demo代碼

4.1 前端:文件上傳組件

<template>
  <view class="upload-page">
    <!-- 分類選擇 -->
    <view class="form-item" @click="showCategoryPicker">
      <text class="label">分類</text>
      <view class="value">
        <text>{{ selectedCategory || '請(qǐng)選擇文件分類' }}</text>
        <uni-icons type="right" size="16" color="#CCC"></uni-icons>
      </view>
    </view>

    <!-- 文件名輸入 -->
    <view class="form-item">
      <text class="label">名稱</text>
      <input
        class="input"
        v-model="fileName"
        placeholder="請(qǐng)輸入文件名稱"
        :maxlength="15"
      />
    </view>

    <!-- 上傳區(qū)域 -->
    <view class="upload-area" v-if="!uploadedFile">
      <view
        class="upload-btn"
        :class="{ disabled: !canUpload }"
        @click="handleUpload"
      >
        <uni-icons type="upload" size="40" :color="canUpload ? '#40E0D0' : '#CCC'" />
        <text class="upload-text">點(diǎn)擊上傳文件</text>
        <text class="upload-desc">支持常用文件格式,最大5MB</text>
      </view>
    </view>

    <!-- 已上傳文件展示 -->
    <view class="uploaded-file" v-else>
      <view class="file-info">
        <uni-icons type="paperclip" size="24" color="#40E0D0" />
        <text class="file-name">{{ uploadedFile.displayName }}</text>
        <text class="file-status">上傳成功</text>
      </view>
      <view class="file-actions">
        <text class="action-btn delete" @click="removeFile">刪除</text>
        <text class="action-btn reupload" @click="reUpload">重新上傳</text>
      </view>
    </view>

    <!-- 底部按鈕 -->
    <view class="footer">
      <button class="btn cancel" @click="cancel">取消</button>
      <button class="btn submit" :disabled="!isFormValid" @click="submit">確定</button>
    </view>
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'

// 表單數(shù)據(jù)
const fileName = ref('')
const selectedCategory = ref('')
const selectedCategoryId = ref(null)
const uploadedFile = ref(null)
const categoryTree = ref([])  // 分類樹數(shù)據(jù)

// 分類選擇狀態(tài)
const firstSelected = ref('')
const secondSelected = ref('')
const thirdSelected = ref('')

// 表單驗(yàn)證
const canUpload = computed(() => {
  return selectedCategory.value && fileName.value.trim()
})

const isFormValid = computed(() => {
  return canUpload.value && uploadedFile.value
})

/**
 * 處理文件上傳
 */
const handleUpload = () => {
  if (!canUpload.value) {
    uni.showToast({ title: '請(qǐng)先選擇分類和輸入文件名稱', icon: 'none' })
    return
  }

  const userFileName = fileName.value.trim()

  wx.chooseMessageFile({
    count: 1,
    type: 'all',
    success: (res) => {
      const tempFile = res.tempFiles[0]
      const originalFileName = tempFile.name

      // 獲取文件擴(kuò)展名
      const fileExtension = originalFileName.substring(originalFileName.lastIndexOf('.'))
      const fullFileName = userFileName + fileExtension

      // 檢查文件大小
      if (tempFile.size > 5 * 1024 * 1024) {
        uni.showToast({ title: '文件大小不能超過5MB', icon: 'none' })
        return
      }

      uni.showLoading({ title: '上傳中...', mask: true })

      // 上傳文件
      uni.uploadFile({
        url: '/api/upload',
        filePath: tempFile.path,
        name: 'file',
        success: (uploadRes) => {
          const data = JSON.parse(uploadRes.data)

          if (data.code === 0) {
            // 保存文件信息,關(guān)鍵是保存完整文件名
            uploadedFile.value = {
              ...data.data,
              fileName: userFileName,
              originalFileName: originalFileName,
              fileNameWithExt: fullFileName,  // 完整文件名(含擴(kuò)展名)
              displayName: originalFileName
            }

            uni.showToast({ title: '上傳成功', icon: 'success' })
          } else {
            uni.showToast({ title: data.msg || '上傳失敗', icon: 'none' })
          }
        },
        fail: () => {
          uni.showToast({ title: '網(wǎng)絡(luò)錯(cuò)誤', icon: 'none' })
        },
        complete: () => {
          uni.hideLoading()
        }
      })
    }
  })
}

/**
 * 獲取完整文件名(含擴(kuò)展名)
 */
const getFullFileName = (file) => {
  // 優(yōu)先使用已保存的完整文件名
  if (file.fileNameWithExt) {
    return file.fileNameWithExt
  }

  // 從原始文件名提取擴(kuò)展名
  const original = file.originalFileName || file.name || ''
  if (original.includes('.')) {
    const ext = original.substring(original.lastIndexOf('.'))
    return fileName.value.trim() + ext
  }

  return file.fileName || fileName.value.trim()
}

/**
 * 查找目標(biāo)分類下的現(xiàn)有文件
 */
const findCategoryFiles = () => {
  if (!categoryTree.value || categoryTree.value.length === 0) {
    return []
  }

  let targetNode = null

  // 遍歷三級(jí)目錄結(jié)構(gòu)
  for (const first of categoryTree.value) {
    if (first.name !== firstSelected.value) continue

    if (!secondSelected.value) {
      targetNode = first
      break
    }

    for (const second of first.children || []) {
      if (second.name !== secondSelected.value) continue

      if (!thirdSelected.value) {
        targetNode = second
        break
      }

      for (const third of second.children || []) {
        if (third.name === thirdSelected.value) {
          targetNode = third
          break
        }
      }
    }
  }

  return targetNode?.files || []
}

/**
 * 提交表單
 */
const submit = async () => {
  if (!isFormValid.value) return

  // ========== 重點(diǎn):提交前檢查同名文件 ==========
  const fullFileName = getFullFileName(uploadedFile.value)
  const categoryFiles = findCategoryFiles()

  // 文件名歸一化(忽略大小寫)
  const normalizeName = (name) => (name || '').trim().toLowerCase()

  // 構(gòu)建現(xiàn)有文件名集合
  const existingNames = new Set(
    categoryFiles
      .map(f => normalizeName(f.fileName || f.name))
      .filter(Boolean)
  )

  // 檢查是否存在同名文件
  if (existingNames.has(normalizeName(fullFileName))) {
    uni.showToast({
      title: '已存在同個(gè)名稱的文件,請(qǐng)重新上傳',
      icon: 'none',
      duration: 3000
    })
    return  // 阻斷提交
  }

  // 通過校驗(yàn),提交數(shù)據(jù)
  try {
    const data = {
      id: selectedCategoryId.value,
      files: [{
        ...uploadedFile.value,
        fileName: fullFileName  // 使用完整文件名
      }]
    }

    await addArchiveFile(data)

    uni.showToast({ title: '新增成功', icon: 'success' })
    setTimeout(() => {
      uni.navigateBack()
    }, 1500)
  } catch (error) {
    uni.showToast({ title: '新增失敗', icon: 'none' })
  }
}

/**
 * 移除文件
 */
const removeFile = () => {
  uploadedFile.value = null
}

/**
 * 重新上傳
 */
const reUpload = () => {
  uploadedFile.value = null
  handleUpload()
}

const cancel = () => {
  uni.navigateBack()
}
</script>

<style scoped lang="scss">
.upload-page {
  min-height: 100vh;
  background: #f5f5f5;
  padding: 16px;
}

.form-item {
  display: flex;
  align-items: center;
  padding: 16px;
  background: #fff;
  margin-bottom: 1px;

  .label {
    width: 60px;
    color: #666;
    font-size: 14px;
  }

  .value {
    flex: 1;
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #333;
  }

  .input {
    flex: 1;
    font-size: 14px;
  }
}

.upload-area {
  margin-top: 16px;
  background: #fff;
  border-radius: 8px;
  padding: 24px;
}

.upload-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 40px;
  border: 2px dashed #e5e5e5;
  border-radius: 8px;

  &.disabled {
    opacity: 0.5;
  }

  .upload-text {
    margin-top: 12px;
    font-size: 16px;
    color: #333;
  }

  .upload-desc {
    margin-top: 8px;
    font-size: 12px;
    color: #999;
  }
}

.uploaded-file {
  margin-top: 16px;
  background: #fff;
  border-radius: 8px;
  padding: 16px;

  .file-info {
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .file-name {
    flex: 1;
    font-size: 14px;
    color: #333;
  }

  .file-status {
    font-size: 12px;
    color: #52c41a;
  }

  .file-actions {
    display: flex;
    gap: 24px;
    margin-top: 16px;
    padding-top: 16px;
    border-top: 1px solid #eee;
  }

  .action-btn {
    font-size: 14px;

    &.delete { color: #ff4d4f; }
    &.reupload { color: #40E0D0; }
  }
}

.footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  display: flex;
  gap: 16px;
  padding: 16px;
  background: #fff;
  box-shadow: 0 -2px 8px rgba(0,0,0,0.05);

  .btn {
    flex: 1;
    height: 44px;
    border-radius: 8px;
    font-size: 16px;

    &.cancel {
      background: #f5f5f5;
      color: #666;
    }

    &.submit {
      background: #40E0D0;
      color: #fff;

      &:disabled {
        background: #ccc;
      }
    }
  }
}
</style>

4.2 后端:檔案服務(wù)

@Service
@Slf4j
public class ArchiveService {

    @Autowired
    private ArchiveMapper archiveMapper;

    @Autowired
    private FileService fileService;

    /**
     * 添加檔案文件
     * @param dto 檔案更新DTO
     * @return 操作結(jié)果
     */
    public ResponseDTO<String> addArchiveFile(ArchiveUpdateDTO dto) {
        // 獲取該分類下現(xiàn)有的文件keys
        List<String> existingFileKeys = archiveMapper.getFileKeysByCategoryId(dto.getId());

        // 提取新上傳的文件keys
        List<String> newFileKeys = dto.getFiles().stream()
            .map(FileDTO::getFileKey)
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

        // 計(jì)算真正新增的文件(排除已存在的)
        List<String> addedFileKeys = newFileKeys.stream()
            .filter(key -> !existingFileKeys.contains(key))
            .collect(Collectors.toList());

        if (!addedFileKeys.isEmpty()) {
            // 獲取現(xiàn)有文件的真實(shí)文件名(含擴(kuò)展名)
            Set<String> existingFileNames = fileService.getFileNamesByKeys(existingFileKeys)
                .stream()
                .map(String::toLowerCase)  // 忽略大小寫
                .collect(Collectors.toSet());

            // 獲取新增文件的真實(shí)文件名
            List<String> newFileNames = fileService.getFileNamesByKeys(addedFileKeys);

            // 校驗(yàn)是否有重復(fù)文件名
            for (String newName : newFileNames) {
                String normalizedName = newName.toLowerCase();
                if (existingFileNames.contains(normalizedName)) {
                    log.warn("文件名重復(fù): {}, 分類ID: {}", newName, dto.getId());
                    return ResponseDTO.userErrorParam("已存在同個(gè)名稱的文件,請(qǐng)重新上傳");
                }
            }
        }

        // 通過校驗(yàn),執(zhí)行更新
        archiveMapper.addFiles(dto.getId(), dto.getFiles());
        log.info("檔案文件添加成功, 分類ID: {}, 文件數(shù)量: {}", dto.getId(), dto.getFiles().size());

        return ResponseDTO.ok();
    }
}

4.3 API接口定義

// api/archive.js

/**
 * 獲取檔案分類樹
 * @param {string} communityId - 組織ID
 */
export function getArchiveTree(communityId) {
  return request({
    url: '/archive/tree',
    method: 'get',
    params: { communityId }
  })
}

/**
 * 添加檔案文件
 * @param {Object} data - 檔案數(shù)據(jù)
 * @param {number} data.id - 分類ID
 * @param {Array} data.files - 文件列表
 */
export function addArchiveFile(data) {
  return request({
    url: '/archive/add',
    method: 'post',
    data
  })
}

五、經(jīng)驗(yàn)總結(jié)

5.1 文件重復(fù)校驗(yàn)的要點(diǎn)

要點(diǎn)說明
使用完整文件名必須包含擴(kuò)展名,如 合同.pdf 而非 合同
忽略大小寫File.PDFfile.pdf 應(yīng)視為相同
前端先校驗(yàn)提升用戶體驗(yàn),避免無(wú)效請(qǐng)求
后端兜底作為最后防線,確保數(shù)據(jù)一致性
明確提示告知用戶具體原因,而非靜默失敗

5.2 文件名處理的最佳實(shí)踐

// 歸一化文件名(用于比較)
const normalizeName = (name) => {
  return (name || '')
    .trim()           // 去除首尾空格
    .toLowerCase()    // 統(tǒng)一小寫
}

// 獲取完整文件名(含擴(kuò)展名)
const getFullFileName = (userInput, originalFile) => {
  // 從原始文件名提取擴(kuò)展名
  const ext = originalFile.substring(originalFile.lastIndexOf('.'))
  return userInput + ext
}

// 校驗(yàn)文件名是否重復(fù)
const isDuplicate = (newName, existingNames) => {
  const normalized = normalizeName(newName)
  return existingNames.some(name => normalizeName(name) === normalized)
}

5.3 為什么需要前后端雙重校驗(yàn)?

層級(jí)作用優(yōu)勢(shì)
前端校驗(yàn)即時(shí)反饋用戶體驗(yàn)好,減少服務(wù)器壓力
后端校驗(yàn)數(shù)據(jù)安全防止繞過前端,確保數(shù)據(jù)一致

雙重校驗(yàn)的必要性

  1. 前端代碼可被繞過(直接調(diào)用API)
  2. 多端訪問(小程序、H5、PC)可能邏輯不一致
  3. 并發(fā)上傳時(shí)可能產(chǎn)生競(jìng)態(tài)條件

5.4 本問題的核心教訓(xùn)

問題教訓(xùn)
靜默去重任何數(shù)據(jù)處理都應(yīng)給用戶明確反饋
文件名不完整文件名必須包含擴(kuò)展名才能準(zhǔn)確比對(duì)
后端校驗(yàn)缺失關(guān)鍵業(yè)務(wù)邏輯必須有后端兜底
大小寫敏感文件名比較應(yīng)忽略大小寫

一句話總結(jié):文件上傳場(chǎng)景中,重復(fù)校驗(yàn)必須使用完整文件名(含擴(kuò)展名),且前后端都要做校驗(yàn),避免靜默失敗導(dǎo)致用戶數(shù)據(jù)丟失。

這個(gè)案例說明:任何影響用戶數(shù)據(jù)的操作,都不應(yīng)該靜默失敗。即使是"去重"這樣看似友好的功能,如果沒有明確提示,也會(huì)讓用戶困惑。前后端協(xié)同校驗(yàn)是保證數(shù)據(jù)安全和用戶體驗(yàn)的關(guān)鍵。

到此這篇關(guān)于前端文件上傳同名沖突檢測(cè)的詳細(xì)解決方案的文章就介紹到這了,更多相關(guān)前端文件上傳同名沖突檢測(cè)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

砚山县| 陆河县| 曲靖市| 辉南县| 古丈县| 宜都市| 廉江市| 和平县| 郑州市| 易门县| 色达县| 水城县| 荣昌县| 永丰县| 海宁市| 襄垣县| 曲水县| 桐柏县| 安岳县| 诏安县| 翁牛特旗| 桦南县| 万荣县| 神木县| 威宁| 临漳县| 罗定市| 大悟县| 岳阳市| 泌阳县| 武胜县| 大宁县| 阳江市| 苗栗市| 改则县| 鄂托克前旗| 同德县| 霍林郭勒市| 沙坪坝区| 辽阳市| 靖安县|