大家好~我是米洛

我正在从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. 「SDWC2018 Day1」网格

    题目当中有三条限制,我们来逐一考虑.对于第一条限制,每次走动的增加量 \(x_i \le M_x, y_i \le M_y\),可以发现一共走的步数是确定的,那么就相当于解这样两个方程组: \[x_1 ...

  2. Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的?

    Net6 DI源码分析Part5 在Kestrel内Di Scope生命周期是如何根据请求走的? 在asp.net core中的DI生命周期有一个Scoped是根据请求走的,也就是说在处理一次请求时, ...

  3. nginx开发: 模块nginx_tcp_proxy_module

    nginx支持ws\wss连接,接入nginx_tcp_proxy_module模块 nginx版本1.14.2,在nginx_tcp_proxy_module模块中的上下行数据接口中处理数据解包与打 ...

  4. Posix 信号

    转载请注明来源:https://www.cnblogs.com/hookjc/ 函数sem_open创建一个新的有名信号灯或打开一个已存在的有名信号灯.有名信号灯总是既可用于线程间的同步,又可以用于进 ...

  5. Shell循环练习题

    Shell循环练习题 目录 Shell循环练习题 1.计算从1到100所有整数的和 2.提示用户输入一个小于100的整数,并计算从1到该数之间所有整数的和 3.求从1到100所有整数的偶数和.奇数和 ...

  6. SQL性能优化技巧

    作者:IT王小二 博客:https://itwxe.com 这里就给小伙伴们带来工作中常用的一些 SQL 性能优化技巧总结,包括常见优化十经验.order by 与 group by 优化.分页查询优 ...

  7. 拔掉网线后, 原本的 TCP 连接还存在吗?

    大家好,我是小林. 今天,聊一个有趣的问题:拔掉网线几秒,再插回去,原本的 TCP 连接还存在吗? 可能有的同学会说,网线都被拔掉了,那说明物理层被断开了,那在上层的传输层理应也会断开,所以原本的 T ...

  8. 4、前端--浮动、定位、是否脱离文档流、溢出属性、z-index、透明度、JavaScript简介

    浮动 # ps:html代码时没有缩进一说的 全部写在一行也可以 """浮动主要就是用于页面布局的!!!""" # 浮动带来的负面影响 &q ...

  9. 基于6U VPX 的mSATA高性能数据存储板

    一.板卡概述 该产品系我司自主研发.基于标准6U VPX架构. 二.产品特性       大存储容量8TB       读写方式RAID0 ,读写速率2GB/s       四路x4 SRIO@5Gb ...

  10. Solution -「CF 1056G」Take Metro

    \(\mathcal{Description}\)   Link.   有 \(n\) 个站台在一个圆环上,顺时针编号 \(1\sim n\),其中 \(1\sim m\) 号站台只能乘坐顺时针转的环 ...