大家好~我是米洛

我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持。

欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

回顾

上一节我们编写好了oss相关的crud接口,那这一节我们就得为oss数据的管理编写一个新的页面了。

即将做的是一个极度精简的文件管理页面

效果图

因为我每次都是写完一段代码,然后编写对应教程,所以效果图这种东西自然是不在话下:

图片可以点击下载,也可以删除

编写oss下载接口

在此之前还是先搞定下之前的作业

  • 编写随机获取文件名的方法

根据传入的文件名,配合pity的打乱顺序+当前时间戳,基本可以保证百分之90的文件名冲突问题。

  • 实现阿里云的下载文件方法

调用get_object_to_file即可,并返回新的文件路径。

  • 封装下载文件的方法

  • 编写下载文件接口
@router.get("/download")
async def download_oss_file(filepath: str):
"""
更新oss文件,路径不能变化
:param filepath:
:return:
"""
try:
client = OssClient.get_oss_client()
# 切割获取文件名
filename = filepath.split("/")[-1]
path = client.download_file(filepath, filename)
return PityResponse.file(path, filename)
except Exception as e:
return PityResponse.failed(f"下载失败: {e}")

注意: 由于oss里面的文件名都是带路径的(目录),所以我们split一下取出文件名

看看下载的效果

前端页面部分

前端页面表格那块自然是传统手艺,不需要多说了。

值得注意的是下面这几个地方:

  • 上传文件表单

    上传文件表单里面有个Upload组件,这个比较特殊,我们需要手动上传,我也踩了很久的坑。

    这里附上源码:

这些几乎都是根据antd官网的例子来的,其中Form.Item套了2层,demo就是这么写的,我试了下去掉一个还不行。

valuePropName固定要是fileList

  • 上传文件的http请求

    由于前端未使用axios这样的http请求库,而是umi-request(自己封装的)。

    所以这边还是说明一下怎么使用:

这是文件上传的方法,第一是要制造FormData对象,并把文件数据append进去。

第二个关键就是requestType要指定为form,这2点我摸索了挺久。

  • 下载文件

    用window.open或者a标签链接到我们的download接口接口:

    window.open(${CONFIG.URL}/oss/download?filepath=${record.key})

  • 全部代码

import {PageContainer} from "@ant-design/pro-layout";
import {Button, Card, Col, Divider, Form, Input, Modal, Row, Table, Upload} from "antd";
import {InboxOutlined, PlusOutlined} from "@ant-design/icons";
import {CONFIG} from "@/consts/config";
import {useEffect, useState} from "react";
import {connect} from 'umi';
import moment from "moment"; const Oss = ({loading, dispatch, gconfig}) => { const [form] = Form.useForm();
const {ossFileList, searchOssFileList} = gconfig;
const [visible, setVisible] = useState(false);
const [value, setValue] = useState(''); const onDeleteFile = key => {
dispatch({
type: 'gconfig/removeOssFile',
payload: {
filepath: key
}
})
} const listFile = () => {
dispatch({
type: 'gconfig/listOssFile',
})
} const columns = [
{
title: '文件路径',
key: 'key',
dataIndex: 'key',
render: key => <a href={`${CONFIG.URL}/oss/download?filepath=${key}`} target="_blank">{key}</a>
},
{
title: '大小',
key: 'size',
dataIndex: 'size',
render: size => `${Math.round(size / 1024)}kb`
},
{
title: '更新时间',
key: 'last_modified',
dataIndex: 'last_modified',
render: lastModified => moment(lastModified * 1000).subtract(moment().utcOffset() / 60 - 8, 'hours').format('YYYY-MM-DD HH:mm:ss') },
{
title: '操作',
key: 'ops',
render: (record) => <>
<a onClick={() => {
window.open(`${CONFIG.URL}/oss/download?filepath=${record.key}`)
}}>下载</a>
<Divider type="vertical"/>
<a onClick={() => {
onDeleteFile(record.key);
}}>删除</a>
</>
},
]
const normFile = (e) => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}; const onUpload = async () => {
const values = await form.validateFields();
const res = dispatch({
type: 'gconfig/uploadFile',
payload: values,
})
if (res) {
setVisible(false)
setValue('')
listFile();
}
} useEffect(() => {
if (value === '') {
dispatch({
type: 'gconfig/save',
payload: {searchOssFileList: ossFileList}
})
} else {
dispatch({
type: 'gconfig/save',
payload: {searchOssFileList: ossFileList.filter(v => v.key.toLowerCase().indexOf(value.toLowerCase()) > -1)}
})
}
}, [value]) useEffect(() => {
listFile();
}, []) return (
<PageContainer title="OSS文件管理" breadcrumb={false}>
<Card>
<Modal width={600} title="上传文件" visible={visible} onCancel={() => setVisible(false)} onOk={onUpload}>
<Form form={form} {...CONFIG.LAYOUT}>
<Form.Item label="文件路径" name="filepath"
rules={[{required: true, message: '请输入文件要存储的路径, 目录用/隔开'}]}>
<Input placeholder="请输入文件要存储的路径, 目录用/隔开"/>
</Form.Item>
<Form.Item label="文件" required>
<Form.Item name="files" valuePropName="fileList" getValueFromEvent={normFile} noStyle
rules={[{required: true, message: '请至少上传一个文件'}]}>
<Upload.Dragger name="files" maxCount={1}>
<p className="ant-upload-drag-icon">
<InboxOutlined/>
</p>
<p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
</Upload.Dragger>
</Form.Item>
</Form.Item>
</Form>
</Modal>
<Row gutter={[8, 8]} style={{marginBottom: 12}}>
<Col span={6}>
<Button type="primary" onClick={() => setVisible(true)}><PlusOutlined/>添加文件</Button>
</Col>
<Col span={12}/>
<Col span={6}>
<Input placeholder="输入要查找的文件名" value={value} onChange={e => {
setValue(e.target.value);
}}/>
</Col>
</Row>
<Table rowKey={record => record.key} dataSource={searchOssFileList} columns={columns}
loading={loading.effects['gconfig/listOssFile']}/>
</Card>
</PageContainer>
)
} export default connect(({loading, gconfig}) => ({loading, gconfig}))(Oss);

今天的内容就介绍到这里,如今我们已经可以通过oss去管理我们的文件了,那我们要怎么运用呢?

下一节给大家介绍,http请求支持文件上传功能。

测试平台系列(91) 编写oss管理页面的更多相关文章

  1. 测试平台系列(90) 编写oss客户端

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节我们编写了在线执行测试 ...

  2. 测试平台系列(79) 编写Redis配置功能(下)

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们提出了优化Dao ...

  3. 测试平台系列(81) 编写在线执行Redis功能

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们牛刀小试,编写了 ...

  4. 测试平台系列(80) 封装Redis客户端

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们编写了Redis ...

  5. 测试平台系列(82) 解决APScheduler重复执行的问题

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上一节我们编写了在线执行R ...

  6. 测试平台系列(83) 前置条件支持Redis语句

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持. 欢迎关注我的公众号测试开发坑货,获取最新文章教程! 回顾 上节我们打了个野,解决了一 ...

  7. 测试平台系列(92) 让http请求支持文件上传

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节呢,我们编写了oss的 ...

  8. 测试平台系列(95) 前置条件支持简单的python脚本

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节我们构思了一下怎么去支 ...

  9. 测试平台系列(97) 完善执行case部分

    大家好~我是米洛! 我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的教程,希望大家多多支持. 欢迎关注我的公众号米洛的测开日记,获取最新文章教程! 回顾 上一节我们讨论了怎么结束一个 ...

随机推荐

  1. Understanding C++ Modules In C++20 (1)

    Compiling evironment: linux (ubuntu 16.04)+ gcc-10.2. The Post will clarify and discuss what modules ...

  2. 【然天一】随机读写(4k)百盘天梯

    随机读写适用于大量小文件的读写,是最贴近办公和编程的使用场景.现在很多硬盘厂商只宣传它们的连续读写(Seq),但除了游戏和视频剪辑场景之外并没有什么卵用. 总结一下: 傲腾秒杀全部 NAND SLC ...

  3. js-reduce方法源码

    // 数组中的reduce方法源码复写 //先说明一下reduce原理:总的一句,reduce方法主要是把数组遍历, //然后把数组的每个元素传入回调函数中,回调函数怎么处理,就会的到什么样的效果 A ...

  4. 【转】MySql根据经纬度获取附近的商家

    创建geo表 create table geo( geo_id INT NOT NULL AUTO_INCREMENT, lng float NOT NULL, lat float NOT NULL, ...

  5. JDK及JRE简介

    JDK(Java Development Kit)是Sun Microsystems针对Java开发员的产品.自从Java推出以来,JDK已经成为使用最广泛的Java SDK.JDK 是整个Java的 ...

  6. ARC和MRC兼容和转换

    1.ARC模式下如何兼容非ARC的类 转变为非ARC -fno-objc-arc 转变为ARC的, -f-objc-arc (不常用) 2.如何将MRC转换为ARC

  7. NoSQL之Redis配置与优化

    NoSQL之Redis配置与优化 目录 NoSQL之Redis配置与优化 一.关系数据库和非关系数据库 1. 关系型数据库 2. 非关系型数据库 3. 非关系型数据库产生背景 4. 关系型数据库和非关 ...

  8. 线性结构和非线性结构、稀疏数组、队列、链表(LinkedList)

    一.线性结构和非线性结构 线性结构: 1)线性绪构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系 2)线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构.顺序存储的线性表称为顺 ...

  9. 了解Spark

    Apache Spark是一个开源的集群计算框架,主要用来处理实时生成的数据. Spark是建立在Hadoop的MapReduce顶部.它被优化到了内存中运行,而MapReduce等替代方法是将数据写 ...

  10. sublime运行错误

      这是由于没有保存文档导致   说明: [Finished in 19.4s with exit code 1]-表示执行时间 [shell_cmd: python3 -u "/Volum ...