测试平台系列(91) 编写oss管理页面.md

2022/6/13 测试平台接口测试FastApiPythonReact

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

# 回顾

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

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

# 效果图

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

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

# 编写oss下载接口

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

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

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

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

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

  • 封装下载文件的方法

这里用到了BackgroundTask

  • 编写下载文件接口
@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}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意: 由于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);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

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

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