easypan front-end learning (3)

file sharing

Overview of file sharing implementation process

  • Users can share their own files ( 单个文件或单个文件夹in a way) in the 链接 + 提取码form of , and other users (including users who share the file) need to enter the extraction code before they can view the files shared by the user
  • The process of obtaining shared files: the user enters the link shared by the user and jumps to it 文件分享的页面. The current page will immediately send a request to the background for verification 当前会话是否该分享输入正确过提取码. If the link has been entered, the shared file list information will be obtained normally. If it has not been entered, let the user jump to it 输入提取码的页面, and then send a request to the background to verify whether the extraction code is correct, and if it is correct, jump to the file sharing page.
  • When the user comes to the file sharing page, he can preview the files shared by the user and save the files shared by the user (single saving or multiple saving is possible)
  • Files shared by the current user can be unshared when viewing the file sharing page (the entire share is unshared)

Share files

ShareFile.vue

  • Object.assign({},data) completes assignment
  • Copy the sharing link and extraction code (implemented using vue-clipboard3)
  • You can window.location.originget the current domain, please refer to: Detailed explanation of location in the browser
<template>
  <div>
    <Dialog
      :show="dialogConfig.show"
      :title="dialogConfig.title"
      :buttons="dialogConfig.buttons"
      width="600px"
      :showCancel="showCancel"
      @close="dialogConfig.show = false"
    >
      <el-form
        :model="formData"
        :rules="rules"
        ref="formDataRef"
        label-width="100px"
        @submit.prevent
      >

        <el-form-item label="文件"> {
   
   { formData.fileName }} </el-form-item>

        <template v-if="showType == 0">

          <el-form-item label="有效期" prop="validType">
            <el-radio-group v-model="formData.validType">
              <el-radio :label="0">1天</el-radio>
              <el-radio :label="1">7天</el-radio>
              <el-radio :label="2">30天</el-radio>
              <el-radio :label="3">永久有效</el-radio>
            </el-radio-group>
          </el-form-item>

          <el-form-item label="提取码" prop="codeType">
            <el-radio-group v-model="formData.codeType">
              <el-radio :label="0">自定义</el-radio>
              <el-radio :label="1">系统生成</el-radio>
            </el-radio-group>
          </el-form-item>

          <el-form-item prop="code" v-if="formData.codeType == 0">
            <el-input
              clearable
              placeholder="请输入5位提取码"
              v-model.trim="formData.code"
              maxLength="5"
              :style="{ width: '130px' }"
            ></el-input>
          </el-form-item>

        </template>

        <template v-else>

          <el-form-item label="分享连接" prop="validType">
            {
   
   { shareUrl }}{
   
   { resultInfo.shareId }}
          </el-form-item>

          <el-form-item label="提取码" prop="validType">
            {
   
   { resultInfo.code }}
          </el-form-item>

          <el-form-item prop="validType">
            <el-button type="primary" @click="copy">复制链接极提取码</el-button>
          </el-form-item>

        </template>

      </el-form>
    </Dialog>
  </div>
</template>

<script setup>

import useClipboard from "vue-clipboard3";
const {
      
       toClipboard } = useClipboard();

import {
      
       ref, getCurrentInstance, nextTick } from "vue";
import {
      
       useRouter } from "vue-router";
const {
      
       proxy } = getCurrentInstance();
const router = useRouter();

// 分享链接的前缀(document.location.origin获取当前页面来源的域名的标准形式)(浏览器中location详解)[https://blog.csdn.net/qq_42880714/article/details/126167569]
const shareUrl = ref(document.location.origin + "/share/");

const api = {
      
      
  shareFile: "/share/shareFile",
};

// 分享之前的表单 - 0 / 分享之后的表单 - 1
const showType = ref(0);

// 分享表单数据
const formData = ref({
      
      });

// 分享表单引用
const formDataRef = ref();

// 表单校验规则
const rules = {
      
      
  validType: [{
      
       required: true, message: "请选择有效期" }],
  codeType: [{
      
       required: true, message: "请选择提取码类型" }],
  code: [
    {
      
       required: true, message: "请输入提取码" },
    {
      
       validator: proxy.Verify.shareCode, message: "提取码只能是数字字母" },
    {
      
       min: 5, message: "提取码最少5位" },
  ],
};

// 弹框是否显示取消按钮
const showCancel = ref(true);

// 弹框配置
const dialogConfig = ref({
      
      
  show: false,
  title: "分享",
  buttons: [
    {
      
      
      type: "primary",
      text: "确定",
      click: (e) => {
      
      
        share();
      },
    },
  ],
});

const resultInfo = ref({
      
      });

// 确定分享该文件
const share = async () => {
      
      

  // 当再次点击时, 关闭弹框
  if (Object.keys(resultInfo.value).length > 0) {
      
      
    dialogConfig.value.show = false;
    return;
  }

  // 校验表单
  formDataRef.value.validate(async (valid) => {
      
      

    // 校验不通过
    if (!valid) {
      
      
      return;
    }

    // 下面2句, 可以直接写成 let params = Object.assign({}, formData.value)
    let params = {
      
      };
    Object.assign(params, formData.value);

    let result = await proxy.Request({
      
      
      url: api.shareFile,
      params: params,
    });

    if (!result) {
      
      
      return;
    }

    // 标记已分享
    showType.value = 1;

    // 文件分享的信息
    resultInfo.value = result.data;

    // 将确定按钮改为关闭
    dialogConfig.value.buttons[0].text = "关闭";

    // 不显示弹框的取消按钮
    showCancel.value = false;
  });

};

// 展示分享弹框
const show = (data) => {
      
      

  // 弹框显示取消按钮
  showCancel.value = true;

  // 显示弹框
  dialogConfig.value.show = true;

  // 标记在未实际分享之前
  showType.value = 0;

  // 分享后的数据信息
  resultInfo.value = {
      
      };

  nextTick(() => {
      
      
    // 重置表单
    formDataRef.value.resetFields();
    // 将表格的数据, 复制给 分享表单
    formData.value = Object.assign({
      
      }, data);
  });

};

// 将show方法暴露出去
defineExpose({
      
       show });

// 复制功能实现
const copy = async () => {
      
      
  await toClipboard(
    `链接:${ 
        shareUrl.value}${ 
        resultInfo.value.shareId} 提取码: ${ 
        resultInfo.value.code}`
  );
  proxy.Message.success("复制成功");
};
</script>

<style lang="scss" scoped></style>

List of files shared by the user

Share.vue

  • Use @import to introduce css, such as:@import "@/assets/file.list.scss";
  • vue-clipboard3 implements text content copying
<template>
  <div>
  
    <div class="top">
    
      <el-button
        type="primary"
        :disabled="selectIdList.length == 0"
        @click="cancelShareBatch">
        <span class="iconfont icon-cancel"></span>取消分享
      </el-button>
      
    </div>
    
    <div class="file-list">
    
      <Table
        :columns="columns"
        :showPagination="true"
        :dataSource="tableData"
        :fetch="loadDataList"
        :options="tableOptions"
        @rowSelected="rowSelected"
      >
        <template #fileName="{ index, row }">
          <div
            class="file-item"
            @mouseenter="showOp(row)"
            @mouseleave="cancelShowOp(row)">
            
            <template v-if="(row.fileType == 3 || row.fileType == 1) && row.status !== 0 ">            
              <icon :cover="row.fileCover"></icon>              
            </template>
            
            <template v-else>
              <icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
              <icon v-if="row.folderType == 1" :fileType="0"></icon>
            </template>
            
            <span
              class="file-name"
              v-if="!row.showRename"
              :title="row.fileName">
              <span>{
   
   { row.fileName }}</span>
            </span>
            
            <span class="op">
              <template v-if="row.showOp && row.fileId">
                <span class="iconfont icon-link" @click="copy(row)">复制链接</span>
                <span class="iconfont icon-cancel" @click="cancelShare(row)">取消分享</span>
              </template>
            </span>
            
          </div>
          
        </template>
        
        <template #expireTime="{ index, row }">
          {
   
   { row.validType == 3 ? "永久" : row.expireTime }}
        </template>
        
      </Table>
    </div>
  </div>
</template>

<script setup>

import useClipboard from "vue-clipboard3";
const {
      
       toClipboard } = useClipboard();

import {
      
       ref, reactive, getCurrentInstance, watch } from "vue";
import {
      
       useRouter, useRoute } from "vue-router";
const {
      
       proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();

const api = {
      
      
  loadDataList: "/share/loadShareList",
  cancelShare: "/share/cancelShare",
};

const shareUrl = ref(document.location.origin + "/share/");

//列表
const columns = [
  {
      
      
    label: "文件名",
    prop: "fileName",
    scopedSlots: "fileName",
  },
  {
      
      
    label: "分享时间",
    prop: "shareTime",
    width: 200,
  },
  {
      
      
    label: "失效时间",
    prop: "expireTime",
    scopedSlots: "expireTime",
    width: 200,
  },
  {
      
      
    label: "浏览次数",
    prop: "showCount",
    width: 200,
  },
];

//搜索
const search = () => {
      
      
  showLoading.value = true;
  loadDataList();
};

//列表
const tableData = ref({
      
      });

const tableOptions = {
      
      
  extHeight: 20,
  selectType: "checkbox",
};


const loadDataList = async () => {
      
      

  let params = {
      
      
    pageNo: tableData.value.pageNo,
    pageSize: tableData.value.pageSize,
  };
  
  if (params.category !== "all") {
      
      
    delete params.filePid;
  }
  
  let result = await proxy.Request({
      
      
    url: api.loadDataList,
    params,
  });
  
  if (!result) {
      
      
    return;
  }
  
  tableData.value = result.data;
};

//展示操作按钮
const showOp = (row) => {
      
      

  tableData.value.list.forEach((element) => {
      
      
    element.showOp = false;
  });
  
  row.showOp = true;
};

const cancelShowOp = (row) => {
      
      
  row.showOp = false;
};

//复制链接
const copy = async (data) => {
      
      

  await toClipboard(`链接:${ 
        shareUrl.value}${ 
        data.shareId} 提取码: ${ 
        data.code}`);
  
  proxy.Message.success("复制成功");
};

//多选 批量选择
const selectIdList = ref([]);
const rowSelected = (rows) => {
      
      
  selectIdList.value = [];
  rows.forEach((item) => {
      
      
    selectIdList.value.push(item.shareId);
  });
};

//取消分享
const cancelShareIdList = ref([]);
const cancelShareBatch = () => {
      
      
  if (selectIdList.value.length == 0) {
      
      
    return;
  }
  cancelShareIdList.value = selectIdList.value;
  cancelShareDone();
};

const cancelShare = (row) => {
      
      
  cancelShareIdList.value = [row.shareId];
  cancelShareDone();
};

const cancelShareDone = async () => {
      
      
  proxy.Confirm(`你确定要取消分享吗?`, async () => {
      
      
    let result = await proxy.Request({
      
      
      url: api.cancelShare,
      params: {
      
      
        shareIds: cancelShareIdList.value.join(","),
      },
    });
    if (!result) {
      
      
      return;
    }
    proxy.Message.success("取消分享成功");
    loadDataList();
  });
};
</script>

<style lang="scss" scoped>

@import "@/assets/file.list.scss";

.file-list {
      
      
  margin-top: 10px;
  .file-item {
      
      
    .file-name {
      
      
      span {
      
      
        &:hover {
      
      
          color: #494944;
        }
      }
    }
    .op {
      
      
      width: 170px;
    }
  }
}
</style>

The user enters the sharing link and enters the verification page

  • After other users get the link, they enter the sharing link into the address bar. However, if the user enters it for the first time, it must not be verified (there is no such sharing record in the session), so jump to the sharing verification page and pass the verification Afterwards, the sharing record will be stored in the session, and when the user accesses the sharing page again, the sharing record will be detected from the session, and there is no need to verify again at this time.

ShareCheck.vue

<template>
  <div class="share">
    <div class="body-content">
      <div class="logo">
        <span class="iconfont icon-pan"></span>
        <span class="name">Easy云盘</span>
      </div>
      <div class="code-panel">
        <div class="file-info">
          <div class="avatar">
            <Avatar
              :userId="shareInfo.userId"
              :avatar="shareInfo.avatar"
              :width="50"
            ></Avatar>
          </div>
          <div class="share-info">
            <div class="user-info">
              <span class="nick-name">{
   
   { shareInfo.nickName }} </span>
              <span class="share-time">分享于 {
   
   { shareInfo.shareTime }}</span>
            </div>
            <div class="file-name">分享文件:{
   
   { shareInfo.fileName }}</div>
          </div>
        </div>
        <div class="code-body">
          <div class="tips">请输入提取码:</div>
          <div class="input-area">
            <el-form
              :model="formData"
              :rules="rules"
              ref="formDataRef"
              :maxLength="5"
              @submit.prevent
            >
              <!--input输入-->
              <el-form-item prop="code">
                <el-input
                  class="input"
                  v-model="formData.code"
                  @keyup.enter="checkShare"
                ></el-input>
                <el-button type="primary" @click="checkShare"
                  >提取文件</el-button
                >
              </el-form-item>
            </el-form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import {
      
       ref, reactive, getCurrentInstance, nextTick, watch } from "vue";
import {
      
       useRouter, useRoute } from "vue-router";
const {
      
       proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();

const api = {
      
      
  getShareInfo: "/showShare/getShareInfo",
  checkShareCode: "/showShare/checkShareCode",
};

const shareId = route.params.shareId;
const shareInfo = ref({
      
      });
const getShareInfo = async () => {
      
      
  let result = await proxy.Request({
      
      
    url: api.getShareInfo,
    params: {
      
      
      shareId,
    },
  });
  if (!result) {
      
      
    return;
  }
  shareInfo.value = result.data;
};
getShareInfo();

const formData = ref({
      
      });
const formDataRef = ref();
const rules = {
      
      
  code: [
    {
      
       required: true, message: "请输入提取码" },
    {
      
       min: 5, message: "提取码为5位" },
    {
      
       max: 5, message: "提取码为5位" },
  ],
};

const checkShare = async () => {
      
      
  formDataRef.value.validate(async (valid) => {
      
      
    if (!valid) {
      
      
      return;
    }
    let result = await proxy.Request({
      
      
      url: api.checkShareCode,
      params: {
      
      
        shareId: shareId,
        code: formData.value.code,
      },
    });
    if (!result) {
      
      
      return;
    }
    router.push(`/share/${ 
        shareId}`);
  });
};
</script>

<style lang="scss" scoped>
.share {
      
      
  height: calc(100vh);
  background: url("../../assets/share_bg.png");
  background-repeat: repeat-x;
  background-position: 0 bottom;
  background-color: #eef2f6;
  display: flex;
  justify-content: center;
  .body-content {
      
      
    margin-top: calc(100vh / 5);
    width: 500px;
    .logo {
      
      
      display: flex;
      align-items: center;
      justify-content: center;
      .icon-pan {
      
      
        font-size: 60px;
        color: #409eff;
      }
      .name {
      
      
        font-weight: bold;
        margin-left: 5px;
        font-size: 25px;
        color: #409eff;
      }
    }
    .code-panel {
      
      
      margin-top: 20px;
      background: #fff;
      border-radius: 5px;
      overflow: hidden;
      box-shadow: 0 0 7px 1px #5757574f;
      .file-info {
      
      
        padding: 10px 20px;
        background: #409eff;
        color: #fff;
        display: flex;
        align-items: center;
        .avatar {
      
      
          margin-right: 5px;
        }
        .share-info {
      
      
          .user-info {
      
      
            display: flex;
            align-items: center;
            .nick-name {
      
      
              font-size: 15px;
            }
            .share-time {
      
      
              margin-left: 20px;
              font-size: 12px;
            }
          }
          .file-name {
      
      
            margin-top: 10px;
            font-size: 12px;
          }
        }
      }
      .code-body {
      
      
        padding: 30px 20px 60px 20px;
        .tips {
      
      
          font-weight: bold;
        }
        .input-area {
      
      
          margin-top: 10px;
          .input {
      
      
            flex: 1;
            margin-right: 10px;
          }
        }
      }
    }
  }
}
</style>

After successful verification, enter the sharing page

  • Enter the sharing page, first still need to request the interface to judge whether the current session has entered the current shared extraction code, if not, jump to the verification page

Share.vue

<template>
  <div class="share">
    <div class="header">
      <div class="header-content">
        <div class="logo" @click="jump">
          <span class="iconfont icon-pan"></span>
          <span class="name">Easy云盘</span>
        </div>
      </div>
    </div>
    <div class="share-body">
      <template v-if="Object.keys(shareInfo).length == 0">
        <div
          v-loading="Object.keys(shareInfo).length == 0"
          class="loading"
        ></div>
      </template>
      <template v-else>
        <div class="share-panel">
          <div class="share-user-info">
            <div class="avatar">
              <Avatar
                :userId="shareInfo.userId"
                :avatar="shareInfo.avatar"
                :width="50"
              ></Avatar>
            </div>
            <div class="share-info">
              <div class="user-info">
                <span class="nick-name">{
   
   { shareInfo.nickName }} </span>
                <span class="share-time">分享于 {
   
   { shareInfo.shareTime }}</span>
              </div>
              <div class="file-name">分享文件:{
   
   { shareInfo.fileName }}</div>
            </div>
          </div>
          <div class="share-op-btn">
            <el-button
              type="primary"
              v-if="shareInfo.currentUser"
              @click="cancelShare"
              ><span class="iconfont icon-cancel"></span>取消分享</el-button
            >
            <el-button
              v-else
              type="primary"
              :disabled="selectFileIdList.length == 0"
              @click="save2MyPan"
              ><span class="iconfont icon-import"></span
              >保存到我的网盘</el-button
            >
          </div>
        </div>
        <!--导航-->
        <Navigation
          ref="navigationRef"
          @navChange="navChange"
          :shareId="shareId"
        ></Navigation>
        <div class="file-list">
          <Table
            :columns="columns"
            :showPagination="true"
            :dataSource="tableData"
            :fetch="loadDataList"
            :initFetch="false"
            :options="tableOptions"
            :showPageSize="false"
            @rowSelected="rowSelected"
          >
            <template #fileName="{ index, row }">
              <div
                class="file-item"
                @mouseenter="showOp(row)"
                @mouseleave="cancelShowOp(row)"
              >
                <template
                  v-if="
                    (row.fileType == 3 || row.fileType == 1) && row.status !== 0
                  "
                >
                  <icon :cover="row.fileCover"></icon>
                </template>
                <template v-else>
                  <icon
                    v-if="row.folderType == 0"
                    :fileType="row.fileType"
                  ></icon>
                  <icon v-if="row.folderType == 1" :fileType="0"></icon>
                </template>
                <span class="file-name" :title="row.fileName">
                  <span @click="preview(row)">{
   
   { row.fileName }}</span>
                </span>
                <span class="op">
                  <span
                    v-if="row.folderType == 0"
                    class="iconfont icon-download"
                    @click="download(row.fileId)"
                    >下载</span
                  >
                  <template v-if="row.showOp && !shareInfo.currentUser">
                    <span
                      class="iconfont icon-import"
                      @click="save2MyPanSingle(row)"
                      >保存到我的网盘</span
                    >
                  </template>
                </span>
              </div>
            </template>
            <template #fileSize="{ index, row }">
              <span v-if="row.fileSize">
                {
   
   { proxy.Utils.sizeToStr(row.fileSize) }}</span
              >
            </template>
          </Table>
        </div>
      </template>
      <!--选择目录-->
      <FolderSelect
        ref="folderSelectRef"
        @folderSelect="save2MyPanDone"
      ></FolderSelect>
      <!--预览-->
      <Preview ref="previewRef"> </Preview>
    </div>
  </div>
</template>

<script setup>
import {
      
       ref, reactive, getCurrentInstance, watch } from "vue";
import {
      
       useRouter, useRoute } from "vue-router";
const {
      
       proxy } = getCurrentInstance();
const router = useRouter();
const route = useRoute();
const api = {
      
      
  getShareLoginInfo: "/showShare/getShareLoginInfo",
  loadFileList: "/showShare/loadFileList",
  createDownloadUrl: "/showShare/createDownloadUrl",
  download: "/api/showShare/download",
  cancelShare: "/share/cancelShare",
  saveShare: "/showShare/saveShare",
};

const shareId = route.params.shareId;
const shareInfo = ref({
      
      });
const getShareInfo = async () => {
      
      
  let result = await proxy.Request({
      
      
    url: api.getShareLoginInfo,
    showLoading: false,
    params: {
      
      
      shareId,
    },
  });
  if (!result) {
      
      
    return;
  }
  if (result.data == null) {
      
      
    router.push("/shareCheck/" + shareId);
    return;
  }
  shareInfo.value = result.data;
};
getShareInfo();

//列表
const columns = [
  {
      
      
    label: "文件名",
    prop: "fileName",
    scopedSlots: "fileName",
  },
  {
      
      
    label: "修改时间",
    prop: "lastUpdateTime",
    width: 200,
  },
  {
      
      
    label: "大小",
    prop: "fileSize",
    scopedSlots: "fileSize",
    width: 200,
  },
];
const tableData = ref({
      
      });
const tableOptions = {
      
      
  extHeight: 80,
  selectType: "checkbox",
};

const loadDataList = async () => {
      
      
  let params = {
      
      
    pageNo: tableData.value.pageNo,
    pageSize: tableData.value.pageSize,
    shareId: shareId,
    filePid: currentFolder.value.fileId,
  };
  let result = await proxy.Request({
      
      
    url: api.loadFileList,
    params,
  });
  if (!result) {
      
      
    return;
  }
  tableData.value = result.data;
};
//展示操作按钮
const showOp = (row) => {
      
      
  tableData.value.list.forEach((element) => {
      
      
    element.showOp = false;
  });
  row.showOp = true;
};

const cancelShowOp = (row) => {
      
      
  row.showOp = false;
};

//多选 批量选择
const selectFileIdList = ref([]);
const rowSelected = (rows) => {
      
      
  selectFileIdList.value = [];
  rows.forEach((item) => {
      
      
    selectFileIdList.value.push(item.fileId);
  });
};

//目录
const currentFolder = ref({
      
       fileId: 0 });
const navChange = (data) => {
      
      
  const {
      
       curFolder } = data;
  currentFolder.value = curFolder;
  loadDataList();
};

//查看
const previewRef = ref();
const navigationRef = ref();
const preview = (data) => {
      
      
  if (data.folderType == 1) {
      
      
    navigationRef.value.openFolder(data);
    return;
  }
  data.shareId = shareId;
  previewRef.value.showPreview(data, 2);
};

//下载文件
const download = async (fileId) => {
      
      
  let result = await proxy.Request({
      
      
    url: api.createDownloadUrl + "/" + shareId + "/" + fileId,
  });
  if (!result) {
      
      
    return;
  }
  window.location.href = api.download + "/" + result.data;
};

//保存到我的网盘
const folderSelectRef = ref();
const save2MyPanFileIdArray = [];
const save2MyPan = () => {
      
      
  if (selectFileIdList.value.length == 0) {
      
      
    return;
  }
  if (!proxy.VueCookies.get("userInfo")) {
      
      
    router.push("/login?redirectUrl=" + route.path);
    return;
  }
  save2MyPanFileIdArray.values = selectFileIdList.value;
  folderSelectRef.value.showFolderDialog();
};
const save2MyPanSingle = (row) => {
      
      
  if (!proxy.VueCookies.get("userInfo")) {
      
      
    router.push("/login?redirectUrl=" + route.path);
    return;
  }
  save2MyPanFileIdArray.values = [row.fileId];
  folderSelectRef.value.showFolderDialog();
};
//执行保存操作
const save2MyPanDone = async (folderId) => {
      
      
  let result = await proxy.Request({
      
      
    url: api.saveShare,
    params: {
      
      
      shareId: shareId,
      shareFileIds: save2MyPanFileIdArray.values.join(","),
      myFolderId: folderId,
    },
  });
  if (!result) {
      
      
    return;
  }
  loadDataList();
  proxy.Message.success("保存成功");
  folderSelectRef.value.close();
};

//取消分享
const cancelShare = () => {
      
      
  proxy.Confirm(`你确定要取消分享吗?`, async () => {
      
      
    let result = await proxy.Request({
      
      
      url: api.cancelShare,
      params: {
      
      
        shareIds: shareId,
      },
    });
    if (!result) {
      
      
      return;
    }
    proxy.Message.success("取消分享成功");
    router.push("/");
  });
};

const jump = () => {
      
      
  router.push("/");
};
</script>

<style lang="scss" scoped>
@import "@/assets/file.list.scss";
.header {
      
      
  width: 100%;
  position: fixed;
  background: #0c95f7;
  height: 50px;
  .header-content {
      
      
    width: 70%;
    margin: 0px auto;
    color: #fff;
    line-height: 50px;
    .logo {
      
      
      display: flex;
      align-items: center;
      cursor: pointer;
      .icon-pan {
      
      
        font-size: 40px;
      }
      .name {
      
      
        font-weight: bold;
        margin-left: 5px;
        font-size: 25px;
      }
    }
  }
}
.share-body {
      
      
  width: 70%;
  margin: 0px auto;
  padding-top: 50px;
  .loading {
      
      
    height: calc(100vh / 2);
    width: 100%;
  }
  .share-panel {
      
      
    margin-top: 20px;
    display: flex;
    justify-content: space-around;
    border-bottom: 1px solid #ddd;
    padding-bottom: 10px;
    .share-user-info {
      
      
      flex: 1;
      display: flex;
      align-items: center;
      .avatar {
      
      
        margin-right: 5px;
      }
      .share-info {
      
      
        .user-info {
      
      
          display: flex;
          align-items: center;
          .nick-name {
      
      
            font-size: 15px;
          }
          .share-time {
      
      
            margin-left: 20px;
            font-size: 12px;
          }
        }
        .file-name {
      
      
          margin-top: 10px;
          font-size: 12px;
        }
      }
    }
  }
}

.file-list {
      
      
  margin-top: 10px;
  .file-item {
      
      
    .op {
      
      
      width: 170px;
    }
  }
}
</style>

WebShareController#loadFileList interface

  • When the user is successfully authenticated and comes to the sharing page, he needs to query the fileId of the currently shared file. This fileId may be a file or a folder. If it is a folder, when entering this folder, the navigation also enters this level at this time. After the navigation is entered, the navigation information is obtained according to the routing path, and then the parent component is notified to load the files and folders of the clicked folder. The parent component needs to use this folder as filePid to query the next-level files and folders under this filePid. Note that the backend at this time cannot directly use the filePid passed from the frontend, because this interface is open (even if the user You can also access it without logging in), so you need to verify this filePid, and you need to verify that this filePid is the fileId associated with this share (or the subfolder of this fileId), in order to avoid data security issues.
@RestController("webShareController")
@RequestMapping("/showShare")
public class WebShareController extends CommonFileController {
    
    

	
	/**
	 * 获取文件列表
	 *
	 * @param session
	 * @param shareId
	 * @return
	 */
	@RequestMapping("/loadFileList")
	@GlobalInterceptor(checkLogin = false, checkParams = true)
	public ResponseVO loadFileList(HttpSession session,
	                               @VerifyParam(required = true) String shareId, String filePid) {
    
    
	
	    // 根据 当前用户是否 输入过提取码,以及校验当前分享是否已失效
	    SessionShareDto shareSessionDto = checkShare(session, shareId);
	
	    FileInfoQuery query = new FileInfoQuery();
	
	    // filePid不为0, 则需要校验。因为要校验进入这个分享所关联的fileId及这个fileId下的所有文件夹
	    // (不能所有fileId都能访问,因此需要递归查询校验)
	    if (!StringTools.isEmpty(filePid) && !Constants.ZERO_STR.equals(filePid)) {
    
    
	
	        // 校验当前要查看的分享中的文件夹,是否包含在 分享的文件夹当中
	        fileInfoService.checkRootFilePid(shareSessionDto.getFileId(), shareSessionDto.getShareUserId(), filePid);
	
	        // 设置为filePid
	        query.setFilePid(filePid);
	
	    } else {
    
    
	        query.setFileId(shareSessionDto.getFileId());
	    }
	
	    // 查询文件记录
	    query.setUserId(shareSessionDto.getShareUserId());
	    query.setOrderBy("last_update_time desc");
	    query.setDelFlag(FileDelFlagEnums.USING.getFlag());
	    PaginationResultVO resultVO = fileInfoService.findListByPage(query);
	
	    return getSuccessResponseVO(convert2PaginationVO(resultVO, FileInfoVO.class));
	}
}
FileInfoServiceImpl#checkRootFilePid recursive verification
@Service("fileInfoService")
public class FileInfoServiceImpl implements FileInfoService {
    
    

	@Override
    public void checkRootFilePid(String rootFilePid, String userId, String fileId) {
    
    

        // rootFilePid 是用户分享记录中分享的文件id
        // fileId 是用户分享出来的文件中某个文件id

        // 这里就是在校验 当前用户正在查看的 fileId 是不是在 分享的根fileId的子级文件中(再怎么样,也不能越过分享的顶级fileId)

        if (StringTools.isEmpty(fileId)) {
    
    
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        // 如果就是查的顶级fileId, 校验通过
        if (rootFilePid.equals(fileId)) {
    
    
            return;
        }

        // 递归的去找fileId的父fileId, 一直到 查找到fileId就是 分享的根fileId(能找到) 或者是 到顶级fileId 0(找不到)
        checkFilePid(rootFilePid, fileId, userId);
    }

	private void checkFilePid(String rootFilePid, String fileId, String userId) {
    
    

        // 一直往上级fileId去找

        FileInfo fileInfo = this.fileInfoMapper.selectByFileIdAndUserId(fileId, userId);

        if (fileInfo == null) {
    
    
            // fileId未有记录
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        if (Constants.ZERO_STR.equals(fileInfo.getFilePid())) {
    
    
            // 一直到顶级,仍未找到
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        if (fileInfo.getFilePid().equals(rootFilePid)) {
    
    
            // 找到了 返回
            return;
        }
        // 递归的去找
        checkFilePid(rootFilePid, fileInfo.getFilePid(), userId);
    }
}

WebShareController#saveShare interface

  • Save files shared by other users to your own space
@RestController("webShareController")
@RequestMapping("/showShare")
public class WebShareController extends CommonFileController {
    
    
 
    // 将 其它用户分享的文件,保存到 自己的空间中
    /**
     * 保存分享
     *
     * @param session
     * @param shareId
     * @param shareFileIds
     * @param myFolderId
     * @return
     */
    @RequestMapping("/saveShare")
    @GlobalInterceptor(checkParams = true)
    public ResponseVO saveShare(HttpSession session,
                                @VerifyParam(required = true) String shareId,
                                @VerifyParam(required = true) String shareFileIds,
                                @VerifyParam(required = true) String myFolderId) {
    
    

        SessionShareDto shareSessionDto = checkShare(session, shareId);

        SessionWebUserDto webUserDto = getUserInfoFromSession(session);

        // 禁止用户把自己分享的文件保存到自己的网盘中(百度网盘中也是不允许的)
        if (shareSessionDto.getShareUserId().equals(webUserDto.getUserId())) {
    
    
            throw new BusinessException("自己分享的文件无法保存到自己的网盘");
        }

        // 分享的根文件id、当前分享中要保存的fileIds、保存到哪个文件夹中、谁分享的、当前用户
        fileInfoService.saveShare(shareSessionDto.getFileId(), shareFileIds, myFolderId, shareSessionDto.getShareUserId(), webUserDto.getUserId());

        return getSuccessResponseVO(null);
    }
    
}
FileInfoServiceImpl #saveShare Save files shared by others in recursive subfolders
/**
 * 文件信息 业务接口实现
 */
@Service("fileInfoService")
public class FileInfoServiceImpl implements FileInfoService {
    
    


    @Override
    public void saveShare(String shareRootFilePid, String shareFileIds, String myFolderId, String shareUserId, String cureentUserId) {
    
    

        // 待保存的分享fileId数组
        String[] shareFileIdArray = shareFileIds.split(",");

        //目标目录文件列表
        //(查看保存到当前用户的目标目录里下的所有文件和文件夹,为了后面的重命名)
        FileInfoQuery fileInfoQuery = new FileInfoQuery();
        fileInfoQuery.setUserId(cureentUserId);
        fileInfoQuery.setFilePid(myFolderId);
        List<FileInfo> currentFileList = this.fileInfoMapper.selectList(fileInfoQuery);
        // list转map,文件名作为key
        Map<String, FileInfo> currentFileMap = currentFileList.stream().collect(Collectors.toMap(FileInfo::getFileName, Function.identity(), (file1, file2) -> file2));

        //选择的文件
        //(用户在所分享的文件中选择要保存的文件集合)
        fileInfoQuery = new FileInfoQuery();
        fileInfoQuery.setUserId(shareUserId);
        fileInfoQuery.setFileIdArray(shareFileIdArray);
        List<FileInfo> shareFileList = this.fileInfoMapper.selectList(fileInfoQuery);

        // 最终需要将分享的文件 插入 到 当前用户的文件记录 的集合
        List<FileInfo> copyFileList = new ArrayList<>();

        Date curDate = new Date();

        // (遍历所有需要保存的 分享文件)
        for (FileInfo item : shareFileList) {
    
    

            // (在用户需要保存的目录里是否已经存在了 要保存的分享文件的名称)
            FileInfo haveFile = currentFileMap.get(item.getFileName());

            // (如果已经存在了的话,那就需要重命名当前分享文件的名称)
            if (haveFile != null) {
    
    
                item.setFileName(StringTools.rename(item.getFileName()));
            }

            // (将当前分享文件 的所有子级文件也拷贝给 当前用户,都暂存到 copyFileList 中)
            // (如果 这里,我自己写递归的话,有可能,在当前代码块,就把item添加到了copyFileList,
            //   然后,再调用递归方法,在递归方法里面,再判断是不是个文件夹,是文件夹的话,遍历文件夹里的每项,再调用递归方法,
            //   这样比起这里来说,有一段添加到copyFileList的逻辑写到当前代码块里了,而没有都在递归里面,不是很好。这里封装的比较好,可以多体会下递归。)
            // 应该说是2者对递归的方法定义或者说思路不同:
            // 我的对递归的思路是:这个递归方法 可以判断当前的文件是否为文件夹,如果是文件夹的话,那么遍历这个文件夹中的每一项,然后将每一项都添加到结果集合中,然后对每一项再进行递归。如果不是文件夹,则直接添加,不递归。
            // 他的思路是:这个递归方法,可以把传入的文件添加到结果集合中,然后判断当前是否为文件夹,如果是文件夹的话,那么遍历这个文件夹的每一项,对每一项再调用递归,如果不是文件夹的话,则略过。
            findAllSubFile(copyFileList, item, shareUserId, cureentUserId, curDate, myFolderId);
        }

        System.out.println(copyFileList.size());

        this.fileInfoMapper.insertBatch(copyFileList);
    }

    private void findAllSubFile(List<FileInfo> copyFileList, FileInfo fileInfo, String sourceUserId, String currentUserId, Date curDate, String newFilePid) {
    
    

        // 先将当前的文件或者是文件夹 (需要设置一个新的fileId,一个新的filePid,当前用户userId),存入copyFileList
        String sourceFileId = fileInfo.getFileId();
        fileInfo.setCreateTime(curDate);
        fileInfo.setLastUpdateTime(curDate);
        fileInfo.setFilePid(newFilePid);
        fileInfo.setUserId(currentUserId);
        String newFileId = StringTools.getRandomString(Constants.LENGTH_10);
        fileInfo.setFileId(newFileId);

        copyFileList.add(fileInfo); // 此时,将fileInfo添加到copyFileList中

        // (如果当前存入的是一个文件夹,那么需要递归的将这个文件夹下面的所有文件和这个文件夹下面的所有文件夹(包括子文件和子文件夹),都加到存入copyFileList中)
        if (FileFolderTypeEnums.FOLDER.getType().equals(fileInfo.getFolderType())) {
    
    

            FileInfoQuery query = new FileInfoQuery();
            query.setFilePid(sourceFileId);
            query.setUserId(sourceUserId);

            // 查询当前文件夹的所有 直接子文件和直接子文件夹
            List<FileInfo> sourceFileList = this.fileInfoMapper.selectList(query);

            // 遍历当前文件夹的所有 直接子文件和直接子文件夹
            for (FileInfo item : sourceFileList) {
    
    
                // 递归处理
                findAllSubFile(copyFileList, item, sourceUserId, currentUserId, curDate, newFileId);
            }
        }
    }

}

Guess you like

Origin blog.csdn.net/qq_16992475/article/details/131657862