Flutter 图片选择及上传与后端处理

1、环境及依赖

Flutter 3.3.8 (空安全版本)

image_picker: ^0.8.6+3
dio: ^5.0.1

Nodejs搭建express框架服务器

"express": "^4.17.1",
"multer": "^1.4.5-lts.1",

2、前端图片选择与上传

首先要明确的是 图片将以文件的格式,被包含在http报文请求实体中的form-data数据项中发送
2-1 dio_http.dart 用于封装dio 此处只介绍封装form-data数据的方法

// dio_http.dart
class DioHttp {
  Dio? _client;
  late BuildContext context;

  static DioHttp of(BuildContext context) {
    return DioHttp._internal(context);
  }
  // 构建请求实例方法
  DioHttp._internal(BuildContext context) {
    if (_client == null || context != this.context) {
      this.context = context;
      var option = BaseOptions(
        baseUrl: 'http://10.0.2.2',
        connectTimeout: Duration(seconds: 1000 * 10),
        receiveTimeout: Duration(seconds: 1000 * 3),
        extra: {'context': context},
      );
      var client = Dio(option);
      this._client = client;
    }
  }
  // 封装form-data数据的方法
  Future<Response<Map<String, dynamic>>> postFormData(String path, params) async {
    var options = Options(
      contentType: 'multipart/form-data',
    );
    return await _client!.post(
      path,
      data: params,
      options: options,
    );
  }
}

2-2 使用 ImagePicker 完成 CommonImagePicker 组件
封装一个图片选择器
注意:图片的上传在父组件完成,因此要将子组件中的图片发送到父组件

// 位于父组件中
CommonImagePicker(
	onChange: (List<File> files) {
		setState(() {
			images = files;
		});
	},
),
// CommonImagePicker 组件(选择多张照片)
class CommonImagePicker extends StatefulWidget {
  // 回调函数,用于更新父级组件
  final ValueChanged<List<File>> onChange;
  const CommonImagePicker({super.key, required this.onChange});
  
  State<CommonImagePicker> createState() => _CommonImagePickerState();
}

// 由于ImagePicker都用的 XFile 格式,所以需要将 XFile 转为常用的 File格式
class _CommonImagePickerState extends State<CommonImagePicker> {
  // 用于保存组件内的照片
  List<XFile> files = [];
  // 用于更新父级组件中的照片
  List<File> fatherfiles = [];
  // 弹出图片选择器,也就是打开本地相册
  _pickImage() async {
    final XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery);
    if (image == null) return;
    setState(() {
      // 更新组件内的照片
      files = files..add(image);
      // 更新要发送给父组件的照片
      // 将 XFile 转化为 File: File(item.path)
      fatherfiles = files.map((i) => File(i.path)).toList();
    });
    // 通知父级更新
    if (widget.onChange != null) {
      widget.onChange(fatherfiles);
    }
  }

  
  Widget build(BuildContext context) {
    // ‘+’按钮
    Widget addButton = GestureDetector(
      onTap: () {
        _pickImage();
      },
      behavior: HitTestBehavior.translucent,
      child: Container(
        width: 30,
        height: 60,
        color: Colors.grey,
        child: Center(
          child: Text(
            '+',
            style: TextStyle(
              fontSize: 40,
              fontWeight: FontWeight.w100,
            ),
          ),
        ),
      ),
    );

    //单个图片组件
    Widget wrapper(XFile imgUrl) {
      var img = File(imgUrl.path);
      return Stack(
        children: [
          // 展示图片
          Image.file(
            img,
            height: 30,
            width: 60,
            fit: BoxFit.fill,
          ),
        ],
      );
    }
    List<Widget> list = files.map((item) => wrapper(item)).toList()..add(addButton);
    return Container(
      padding: EdgeInsets.all(10),
      child: Wrap(
        spacing: 10,
        runSpacing: 10,
        children: list,
      ),
    );
  }
}

选择单张照片的代码差不多,就是不用数组来保存图像文件就行了,这里就不展开说了

2-3 父组件向后端发送请求
封装请求方法,因为需要在发送请求后拿到结果并进行后续处理,所以用Future

Future<String> uploadImageSingle(File file, BuildContext context) async {
  if (file == null) return Future.value('');
  // 将图片封装成 formData 格式
  var formData = FormData();

  formData.files.add(MapEntry(
    "files", //后台接收的名字
    MultipartFile.fromFileSync(file.path, filename: 'b'),
  ));
  
  var res = await DioHttp.of(context).postFormData('/uploadImageSingle', formData);
  var resString = jsonDecode(res.toString());
  // nameList 是上传的图片的名字,数组类型
  var nameList = resString['data'];
  return Future.value(nameList);
}

在需要时调用,发起请求(在添加照片后或发送整体请求之前)

// 上传图片并获取图片名
var imagesString = await uploadImages(images, context);

3、后端处理

这里笔者是用的express框架搭建的服务器,过程比较简单就不说了
3-1 uploadImages.js
multer 插件用于处理 http报文 请求实体中的form-data数据

const express = require('express')
const router = express.Router()
const multer = require("multer");
const imagesRename = require('../utils/imagesRename')
// dest:指定存储位置
const upload = multer({ dest: __dirname + "/../uploadedImages" });

// 上传图片接口
// upload.any() 任意文件格式
router.post('/uploadImages', upload.any(), (req, res) => {
	// 拿到请求实体中的form-data数据
    const data = req.files
    // 可以打印出来看看,里面是数组对象的形式,包含很多文件信息
    // console.log(data)
    var imagesNameList = []
    for(let i = 0 ; i < data.length ; i++){
        // 我们只需要文件的文件名,用于保存到数据库
        imagesNameList.push(data[i].filename)
    }
    // 将上传的二进制文件转换成图片格式
    imagesRename()
    res.send({ status: 0, msg: '获取用户信息成功', data: imagesNameList })
})

module.exports = router

3-2 imagesRename.js
imagesRename 是一个将上传的二进制文件转换成图片格式的方法

const fs = require('fs')

function imagesRename() {
    fs.readdir('./uploadedImages', (error, list) => {
        if (error) {
            throw error;
        } else {
            for (let i of list) {
                if (i.indexOf('.jpg') == -1) {
                    fs.rename("./uploadedImages/" + i, "./uploadedImages/" + i + '.jpg', error => {
                        if (error) {
                            new Error(error);
                            return;
                        }
                    })
                }

            }
        }
    })
}

module.exports = imagesRename

这里如果对 multer 不太熟悉的小伙伴可以看看这位博主写的文章( 点击 )

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐