AngularJS 文件上传、Express 和 node-postgres (pg)

AngularJS file upload, Express and node-postgres (pg)

在 AngularJS 中获得了一个组件(即将移植到 Angular 7),用于更新调用 AngularJS 服务方法以执行 PUT 到 [=18 的用户配置文件=]/:id.

想要添加一张小照片 (<100K) 以与其他字段在同一个 PUT 中发送,然后在控制器中像这样处理请求...

// route: PUT /api/user/:id
import db from '../../utils/db';

export async function upsert(req, res) {
  const { id, name, phone, email, photo } = req.body;
  // users.photo in PostgreSQL has datatype of bytea
  const sql = `UPDATE users SET name = , phone = , email = , photo = ) WHERE id =  RETURNING id, name, phone, email, photo;`;
  const { rows } = db.query(sql, [id, name, phone, email, photo];
  return res.status(200).send(rows);
}

是否有一种干净的方法来对图像客户端进行编码,以便它可以包含在 JSON AngularJS 服务 PUT 中?对于这个用例,我发现的其他解决方案似乎有点矫枉过正 - 要求图像上传的处理方式与其他领域截然不同。

最后硬着头皮 - 使用 formidablenode- 为文件创建一个单独的 table 及其自己的 API postgres.如果这对其他人有帮助,结果如下。

PostgreSQL 数据定义...

-- DROP SEQUENCE public.files_seq;
CREATE SEQUENCE IF NOT EXISTS public.files_seq;

-- DROP TABLE public.files;
CREATE TABLE IF NOT EXISTS public.files (
  _id integer PRIMARY KEY DEFAULT nextval('files_seq'::regclass),
  name character varying(512) NOT NULL,
  type character varying(20) NOT NULL,
  data bytea
);

-- DROP INDEX public.users_first_name;
CREATE INDEX files_name ON public.files USING btree (name);

Express 控制器...

import stream from 'stream';
import fs from 'fs';
import { IncomingForm } from 'formidable';
import db from '../../utils/db';

// Returns list of images
export async function index(req, res) {
  const { rows } = await db.query('SELECT _id, name, type FROM files ORDER BY name;', []);
  return res.send(rows);
}

// Uploads a single file
export async function upload(req, res) {
  let _id;
  new IncomingForm().parse(req, (err, fields, files) => {
    if(err) throw err;
    if(Array.isArray(files)) throw new Error('Only one file can be uploaded at a time');
    const { name, type, path } = files.file;
    fs.readFile(path, 'hex', async(err, fileData) => {
      if(err) throw err;
      fileData = `\x${fileData}`;
      const sql = 'INSERT INTO files (name, type, data) VALUES(, , ) RETURNING _id;';
      const { rows } = await db.query(sql, [name, type, fileData]);
      _id = rows[0]._id;
      res.send({ id: _id });
      // console.log(`Uploaded ${name} to ${path} and inserted into database (ID = ${_id})`);
      // No need to delete the file uploaded as Heroku has an ephemeral file system
    });
  });
}

// Downloads a file by its _id
export async function download(req, res) {
  const _id = req.params.id;
  const sql = 'SELECT _id, name, type, data FROM files WHERE _id = ;';
  const { rows } = await db.query(sql, [_id]);
  const file = rows[0];
  const fileContents = Buffer.from(file.data, 'base64');
  const readStream = new stream.PassThrough();
  readStream.end(fileContents);
  res.set('Content-disposition', `attachment; filename=${file.name}`);
  res.set('Content-Type', file.type);
  readStream.pipe(res);
  return rows[0];
}

// Deletes a file from the database (admin-only)
export async function destroy(req, res) {
  const _id = req.params.id;
  const sql = 'DELETE FROM files WHERE _id = ;';
  await db.query(sql, [_id]);
  res.status(204).send({ message: `File ${_id} deleted.`});
}

在客户端,我使用 ng-file-upload 和 AngularJS。

这是视图的相关部分(为简洁起见,用哈巴狗表示)...

.form-group.col-md-6
  label(for='photo') Teacher photo
  input.form-control(ngf-select='$ctrl.uploadPhoto($file)', type='file', id='photo', name='photo', ng-model='$ctrl.user.photo', ngf-pattern="'image/*'", ngf-accept="'image/*'", ngf-max-size='100KB', ngf-min-height='276', ngf-max-height='276', ngf-min-width='236', ngf-max-width='236', ngf-resize='{width: 236, height: 276}', ngf-model-invalid='errorFile')
  ng-messages.help-block.has-error(for='form.photo.$error', ng-show='form.photo.$dirty || form.$submitted', role='alert')
    ng-message(when='maxSize') Please select a photo that is less than 100K.
    ng-message(when='minHeight,maxHeight,minWidth,maxWidth') The image must be 236 x 276 pixels.
  span(ng-if='$ctrl.user.imageId')
    img(ng-src='/api/file/{{ $ctrl.user.imageId }}' alt="Photo of Teacher")

及其控制器中的方法...

  uploadPhoto(file) {
    if(file) {
      this.uploadService.upload({
        url: '/api/file/upload',
        data: { file }
      })
        .then(response => {
          this.user.imageId = response.data.id;
        }, response => {
          if(response.status > 0) console.log(`${response.status}: ${response.data}`);
        }, evt => {
          // Math.min is to fix IE which reports 200% sometimes
          this.uploadProgress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total, 10));
        });
    }
  }