大家好~我是米洛

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

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

回顾

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

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

效果图

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

测试平台系列(91) 编写oss管理页面 - 图1

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

编写oss下载接口

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

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

测试平台系列(91) 编写oss管理页面 - 图2

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

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

测试平台系列(91) 编写oss管理页面 - 图3

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

  • 封装下载文件的方法

测试平台系列(91) 编写oss管理页面 - 图4

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

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

看看下载的效果

测试平台系列(91) 编写oss管理页面 - 图5

前端页面部分

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

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

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

测试平台系列(91) 编写oss管理页面 - 图6

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

valuePropName固定要是fileList

测试平台系列(91) 编写oss管理页面 - 图7

  • 上传文件的http请求
    由于前端未使用axios这样的http请求库,而是umi-request(自己封装的)。
    所以这边还是说明一下怎么使用:

测试平台系列(91) 编写oss管理页面 - 图8

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

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

  • 下载文件
    用window.open或者a标签链接到我们的download接口接口:
    window.open(测试平台系列(91) 编写oss管理页面 - 图9{record.key})
  • 全部代码
  1. import {PageContainer} from "@ant-design/pro-layout";
  2. import {Button, Card, Col, Divider, Form, Input, Modal, Row, Table, Upload} from "antd";
  3. import {InboxOutlined, PlusOutlined} from "@ant-design/icons";
  4. import {CONFIG} from "@/consts/config";
  5. import {useEffect, useState} from "react";
  6. import {connect} from 'umi';
  7. import moment from "moment";
  8. const Oss = ({loading, dispatch, gconfig}) => {
  9. const [form] = Form.useForm();
  10. const {ossFileList, searchOssFileList} = gconfig;
  11. const [visible, setVisible] = useState(false);
  12. const [value, setValue] = useState('');
  13. const onDeleteFile = key => {
  14. dispatch({
  15. type: 'gconfig/removeOssFile',
  16. payload: {
  17. filepath: key
  18. }
  19. })
  20. }
  21. const listFile = () => {
  22. dispatch({
  23. type: 'gconfig/listOssFile',
  24. })
  25. }
  26. const columns = [
  27. {
  28. title: '文件路径',
  29. key: 'key',
  30. dataIndex: 'key',
  31. render: key => <a href={`${CONFIG.URL}/oss/download?filepath=${key}`} target="_blank">{key}</a>
  32. },
  33. {
  34. title: '大小',
  35. key: 'size',
  36. dataIndex: 'size',
  37. render: size => `${Math.round(size / 1024)}kb`
  38. },
  39. {
  40. title: '更新时间',
  41. key: 'last_modified',
  42. dataIndex: 'last_modified',
  43. render: lastModified => moment(lastModified * 1000).subtract(moment().utcOffset() / 60 - 8, 'hours').format('YYYY-MM-DD HH:mm:ss')
  44. },
  45. {
  46. title: '操作',
  47. key: 'ops',
  48. render: (record) => <>
  49. <a onClick={() => {
  50. window.open(`${CONFIG.URL}/oss/download?filepath=${record.key}`)
  51. }}>下载</a>
  52. <Divider type="vertical"/>
  53. <a onClick={() => {
  54. onDeleteFile(record.key);
  55. }}>删除</a>
  56. </>
  57. },
  58. ]
  59. const normFile = (e) => {
  60. if (Array.isArray(e)) {
  61. return e;
  62. }
  63. return e && e.fileList;
  64. };
  65. const onUpload = async () => {
  66. const values = await form.validateFields();
  67. const res = dispatch({
  68. type: 'gconfig/uploadFile',
  69. payload: values,
  70. })
  71. if (res) {
  72. setVisible(false)
  73. setValue('')
  74. listFile();
  75. }
  76. }
  77. useEffect(() => {
  78. if (value === '') {
  79. dispatch({
  80. type: 'gconfig/save',
  81. payload: {searchOssFileList: ossFileList}
  82. })
  83. } else {
  84. dispatch({
  85. type: 'gconfig/save',
  86. payload: {searchOssFileList: ossFileList.filter(v => v.key.toLowerCase().indexOf(value.toLowerCase()) > -1)}
  87. })
  88. }
  89. }, [value])
  90. useEffect(() => {
  91. listFile();
  92. }, [])
  93. return (
  94. <PageContainer title="OSS文件管理" breadcrumb={false}>
  95. <Card>
  96. <Modal width={600} title="上传文件" visible={visible} onCancel={() => setVisible(false)} onOk={onUpload}>
  97. <Form form={form} {...CONFIG.LAYOUT}>
  98. <Form.Item label="文件路径" name="filepath"
  99. rules={[{required: true, message: '请输入文件要存储的路径, 目录用/隔开'}]}>
  100. <Input placeholder="请输入文件要存储的路径, 目录用/隔开"/>
  101. </Form.Item>
  102. <Form.Item label="文件" required>
  103. <Form.Item name="files" valuePropName="fileList" getValueFromEvent={normFile} noStyle
  104. rules={[{required: true, message: '请至少上传一个文件'}]}>
  105. <Upload.Dragger name="files" maxCount={1}>
  106. <p className="ant-upload-drag-icon">
  107. <InboxOutlined/>
  108. </p>
  109. <p className="ant-upload-text">点击或拖拽文件到此区域上传🎉</p>
  110. </Upload.Dragger>
  111. </Form.Item>
  112. </Form.Item>
  113. </Form>
  114. </Modal>
  115. <Row gutter={[8, 8]} style={{marginBottom: 12}}>
  116. <Col span={6}>
  117. <Button type="primary" onClick={() => setVisible(true)}><PlusOutlined/>添加文件</Button>
  118. </Col>
  119. <Col span={12}/>
  120. <Col span={6}>
  121. <Input placeholder="输入要查找的文件名" value={value} onChange={e => {
  122. setValue(e.target.value);
  123. }}/>
  124. </Col>
  125. </Row>
  126. <Table rowKey={record => record.key} dataSource={searchOssFileList} columns={columns}
  127. loading={loading.effects['gconfig/listOssFile']}/>
  128. </Card>
  129. </PageContainer>
  130. )
  131. }
  132. export default connect(({loading, gconfig}) => ({loading, gconfig}))(Oss);

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

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