整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

使用 NestJS 和 MySQL 处理文件上传

多开发人员不知怎么处理文件上传。在这篇博客中,我们将教你如何使用 NestJS 和 MySQL 构建文件上传功能。



许多开发人员鄙视处理文件上传。这可以归因于缺乏对最佳方法的了解,或者难以确定如何配置他们的 NestJS 应用程序来处理文件上传。许多人可能希望将他们的文件直接保存到 MySQL 数据库,或者保存图像名称并将图像保存在磁盘存储中:这完全取决于他们的偏好和他们想要实现的目标。本教程将教您如何使用 NestJS 和 MySQL 构建文件上传功能。

先决条件

在开始学习本教程之前,请确保您的系统满足以下要求:

设置 NestJS

满足上述要求后,继续安装 NestJS CLI 并通过运行以下命令创建一个新项目:

$ npm i -g @nestjs/cli $ 嵌套新文件上传


这些命令将安装 NestJS CLI 并使用以下文件夹结构创建一个新的 NestJS 项目。

创建 NestJS 项目后,继续下一步 - 通过运行以下命令为您的应用程序安装所需的依赖项:

npm install --save @nestjs/typeorm typeorm mysql2 


在上面的命令中,您已经安装了TypeORM和mysql2模块:它们将使您能够将应用程序连接到 MySQL 数据库并对其执行操作。

设置 MySQL 数据库

安装上述依赖项后,继续设置并连接到您的 MySQL 数据库。要开始使用,请在app.module.ts文件中添加代码以及下面的代码片段。

...
import { TypeOrmModule } from '@nestjs/typeorm';
import { Image } from './image.entity';

@Module({
  imports: [TypeOrmModule.forRoot({
    type: 'mysql',
    host: 'localhost',
    port: 3306,
    username: 'root',
    password: '1234',
    database: 'blog',
    entities: [Image],
    synchronize: true,
  }),
  TypeOrmModule.forFeature([Image])
  ],
  ...
})
...


在上面的代码片段中,我们TypeOrmModule从之前安装的 typeorm 模块导入。我们使用该forRoot方法将应用程序连接到 MySQL 数据库并传入数据库凭据。这里要指出的另一件事是entities属性,它允许我们指定模块中的实体,并允许我们访问Image您将很快创建的实体:我们还将synchronize属性设置true为自动迁移数据库。

创建图像实体

接下来,让我们创建我们之前提到的 Image 实体。首先,在 src 目录中创建一个 image.entity.ts 文件并添加下面的代码片段。


import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

@Entity()
export class Image {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @CreateDateColumn()
    dateCreated: Date;

    @UpdateDateColumn()
    dateUpdated: Date;
}


在上面的代码片段中,我们导入了创建实体所需的装饰器。使用这些装饰器,我们定义了实体的属性。我们有使用装饰器id为数据库中的每条记录生成随机 id 的字段,用于存储将使用装饰器上传的图像名称的字段,用于保存记录创建和更新日期的 dateCreated 和 dateUpdate 字段使用和。@PrimaryGeneratedColumn()name@Column@CreateDateColumn()@UpdateDateColumn()

创建上传服务

创建 Image 实体后,让我们创建一个服务来执行CRUD 操作来处理文件上传。在app.service.ts文件中,添加下面的代码片段。


import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}


在上面的代码片段中,我们已经导入了injectRepository装饰器来注入,imageRepositoryAppServiceRepository您提供了对数据库执行某些操作所需的方法。因此,对于createImage图像服务,我们保存了上传的图像的名称,该名称将通过控制器传递。

创建上传控制器

现在让我们创建控制器以使用服务。在app.controller.ts文件中并添加下面的代码片段。


import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Image } from './image.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(Image)
    private readonly imageRepository: Repository<Image>,
  ) {}

  async getImages(): Promise<Image[]> {
    return this.imageRepository.find();
  }

  async createImage(image: Image): Promise<Image> {
    return this.imageRepository.save(image);
  }

  async getImage(id: number): Promise<Image> {
    return this.imageRepository.findOneBy({ id });
  }

  async deleteImage(id: number): Promise<void> {
    await this.imageRepository.delete(id);
  }
}


在上面的代码片段中,我们导入了几个装饰器,如FileInterceptorUploadedFileUseInterceptors。路由处理程序的拦截器使用装饰器FileInterceptor()从请求中提取文件。装饰器是从包中导出的@UploadedFile()。装饰器是从. 装饰器有两个参数,一个是提供包含文件的 HTML 表单中的字段名称的字符串,另一个是 MulterOptions 类型的可选对象。这与 multer 构造函数使用的对象相同。FileInterceptor()@nestjs/platform-express@UploadedFile()@nestjs/commonFileInterceptor()fieldNameoptions

关于createImage函数,我们使用前面提到的装饰器FileInterceptor()通过传递图像的字段名称来处理文件上传,我们修改了函数以通过使用中可用的函数指定属性来FileInterceptor()将图像上传到磁盘。然后我们指定图像的位置并为图像生成随机名称。此外,我们添加了一个属性来限制某些图像格式的上传。现在我们使用装饰器提取文件并获取名称并将其保存到数据库中。这样我们就可以使用每个图像的名称从存储位置获取图像。storagediskStoragemulterfilter@UploadedFile()

要使上述代码正常工作,您需要通过在终端中运行以下命令来安装 multer:

npm i -D @types/multer 


然后,您需要在app.module.ts文件的导入数组中注册 multer 模块:


...
import { MulterModule } from '@nestjs/platform-express';


@Module({
  ...
  MulterModule.register({
    dest: './files',
  }),],
  ...


上面的配置告诉 multer 处理文件上传和上传文件的位置。最后但同样重要的是,我们应该files在目录中创建一个文件夹src来实际存储文件。

服务文件

要将应用程序上上传的图像实际提供给用户,您需要serve-static通过运行以下命令来安装模块。


npm install --save @nestjs/serve-staticbr


然后,使用下面的代码片段ServeStaticModule在文件中的导入数组中注册 。app.module.ts


...
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';

@Module({
  ...
  ServeStaticModule.forRoot({
    rootPath: join(__dirname, '..', 'files')
  }),],
  ...


在上面的代码片段中,您已经指定了文件所在的位置并且可以从中提供服务。

测试 API

现在打开 Postman 并通过向端点发送 POST 请求来测试应用程序,并将localhost:4000/images请求正文中的有效负载作为表单数据传递。

如果您现在查看文件文件夹,您应该会看到已上传的文件。随意继续:测试并尝试其他路线。

结论

通过本教程,您学习了如何使用 NestJS 和 MySQL 处理文件上传。您已经学习了如何使用 TypeORM 连接到 MySQL 数据库,并且您还创建了一个实体并将图像上传到 NestJS 应用程序

如需进一步阅读,您还可以阅读有关在NestJS中上传文件的更多信息。对于额外的挑战,尝试通过保护删除和更新路由来扩展应用程序。接下来你会建造什么?

根据官方wiki文档,Sentinel控制台的实时监控数据,默认仅存储 5 分钟以内的数据。如需持久化,需要定制实现相关接口。

https://github.com/alibaba/Sentinel/wiki/在生产环境中使用-Sentinel

给出了指导步骤:

  • 自行扩展实现 MetricsRepository 接口;
  • 注册成 Spring Bean 并在相应位置通过 @Qualifier 注解指定对应的 bean name 即可;


0x01:MetricsRepository接口定义

package com.alibaba.csp.sentinel.dashboard.repository.metric;

import java.util.List;


public interface MetricsRepository<T> {

    void save(T metric);

    void saveAll(Iterable<T> metrics);

    List<T> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime);

    List<String> listResourcesOfApp(String app);
}

该接口就只定义4个方法,分别用于保存和查询Sentinel的metric数据。注释其实很清楚了,解析如下:

  • save:保存单个metric
  • saveAll:保存多个metric
  • queryByAppAndResourceBetween:通过应用名称、资源名称、开始时间、结束时间查询metric列表
  • listResourcesOfApp:通过应用名称查询资源列表

目前该接口只有一个基于内存级别的实现类:com.alibaba.csp.sentinel.dashboard.repository.metric.InMemoryMetricsRepository。

另外还有一个实体类com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity,如下图


梳理了相关的类关系就可以实现了。


0x02:根据MetricEntity新建数据库和新建实体类

建表语句如下

-- 创建监控数据表
CREATE TABLE `t_sentinel_metric` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'id,主键',
  `gmt_create` DATETIME COMMENT '创建时间',
  `gmt_modified` DATETIME COMMENT '修改时间',
  `app` VARCHAR(100) COMMENT '应用名称',
  `timestamp` DATETIME COMMENT '统计时间',
  `resource` VARCHAR(500) COMMENT '资源名称',
  `pass_qps` INT COMMENT '通过qps',
  `success_qps` INT COMMENT '成功qps',
  `block_qps` INT COMMENT '限流qps',
  `exception_qps` INT COMMENT '发送异常的次数',
  `rt` DOUBLE COMMENT '所有successQps的rt的和',
  `_count` INT COMMENT '本次聚合的总条数',
  `resource_code` INT COMMENT '资源的hashCode',
  INDEX app_idx(`app`) USING BTREE,
  INDEX resource_idx(`resource`) USING BTREE,
  INDEX timestamp_idx(`timestamp`) USING BTREE,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

实体类如下

package com.alibaba.csp.sentinel.dashboard.datasource.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

/**
 * @author 2230
 *
 */
@Entity
@Table(name = "t_sentinel_metric")
public class MetricDto implements Serializable {

    private static final long serialVersionUID = 7200023615444172715L;

    /**id,主键*/
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    /**创建时间*/
    @Column(name = "gmt_create")
    private Date gmtCreate;

    /**修改时间*/
    @Column(name = "gmt_modified")
    private Date gmtModified;

    /**应用名称*/
    @Column(name = "app")
    private String app;

    /**统计时间*/
    @Column(name = "timestamp")
    private Date timestamp;

    /**资源名称*/
    @Column(name = "resource")
    private String resource;

    /**通过qps*/
    @Column(name = "pass_qps")
    private Long passQps;

    /**成功qps*/
    @Column(name = "success_qps")
    private Long successQps;

    /**限流qps*/
    @Column(name = "block_qps")
    private Long blockQps;

    /**发送异常的次数*/
    @Column(name = "exception_qps")
    private Long exceptionQps;

    /**所有successQps的rt的和*/
    @Column(name = "rt")
    private Double rt;

    /**本次聚合的总条数*/
    @Column(name = "_count")
    private Integer count;

    /**资源的hashCode*/
    @Column(name = "resource_code")
    private Integer resourceCode;

   // get  set 方法省略

}


0x03:pom.xml添加依赖

因为是基于JPA和MySQL数据库实现,所以需要添加JPA依赖和MySQL数据库驱动依赖

   <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
          <version>${spring.boot.version}</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>


0x04:实现MetricsRepository 接口,把数据持久化到MySQL数据库

注意实现添加@Repository("jpaMetricsRepository")配置


package com.alibaba.csp.sentinel.dashboard.repository.metric;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricDto;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity;
import com.alibaba.csp.sentinel.util.StringUtil;

/**
 * https://www.cnblogs.com/yinjihuan/p/10574998.html
 * https://www.cnblogs.com/cdfive2018/p/9838577.html
 * https://blog.csdn.net/wk52525/article/details/104587239/
 * https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
 * 
 * @author 2230
 *
 */
@Transactional
@Repository("jpaMetricsRepository")
public class JpaMetricsRepository implements MetricsRepository<MetricEntity> {

    @PersistenceContext
    private EntityManager em;

    @Override
    public void save(MetricEntity metric) {
        if (metric == null || StringUtil.isBlank(metric.getApp())) {
            return;
        }

        MetricDto metricDto = new MetricDto();
        BeanUtils.copyProperties(metric, metricDto);
        em.persist(metricDto);
    }

    @Override
    public void saveAll(Iterable<MetricEntity> metrics) {
        if (metrics == null) {
            return;
        }

        metrics.forEach(this::save);
    }

    @Override
    public List<MetricEntity> queryByAppAndResourceBetween(String app, String resource, long startTime, long endTime) {
        List<MetricEntity> results = new ArrayList<MetricEntity>();
        if (StringUtil.isBlank(app)) {
            return results;
        }

        if (StringUtil.isBlank(resource)) {
            return results;
        }

        StringBuilder hql = new StringBuilder();
        hql.append("FROM MetricDto");
        hql.append(" WHERE app=:app");
        hql.append(" AND resource=:resource");
        hql.append(" AND timestamp>=:startTime");
        hql.append(" AND timestamp<=:endTime");

        Query query = em.createQuery(hql.toString());
        query.setParameter("app", app);
        query.setParameter("resource", resource);
        query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));
        query.setParameter("endTime", Date.from(Instant.ofEpochMilli(endTime)));

        List<MetricDto> metricDtos = query.getResultList();
        if (CollectionUtils.isEmpty(metricDtos)) {
            return results;
        }

        for (MetricDto metricDto : metricDtos) {
            MetricEntity metricEntity = new MetricEntity();
            BeanUtils.copyProperties(metricDto, metricEntity);
            results.add(metricEntity);
        }
        return results;
    }

    @Override
    public List<String> listResourcesOfApp(String app) {
        List<String> results = new ArrayList<>();
        if (StringUtil.isBlank(app)) {
            return results;
        }

        StringBuilder hql = new StringBuilder();
        hql.append("FROM MetricDto");
        hql.append(" WHERE app=:app");
        hql.append(" AND timestamp>=:startTime");

        long startTime = System.currentTimeMillis() - 1000 * 60;
        Query query = em.createQuery(hql.toString());
        query.setParameter("app", app);
        query.setParameter("startTime", Date.from(Instant.ofEpochMilli(startTime)));

        List<MetricDto> metricDtos = query.getResultList();
        if (CollectionUtils.isEmpty(metricDtos)) {
            return results;
        }

        List<MetricEntity> metricEntities = new ArrayList<MetricEntity>();
        for (MetricDto metricDto : metricDtos) {
            MetricEntity metricEntity = new MetricEntity();
            BeanUtils.copyProperties(metricDto, metricEntity);
            metricEntities.add(metricEntity);
        }

        Map<String, MetricEntity> resourceCount = new HashMap<>(32);

        for (MetricEntity metricEntity : metricEntities) {
            String resource = metricEntity.getResource();
            if (resourceCount.containsKey(resource)) {
                MetricEntity oldEntity = resourceCount.get(resource);
                oldEntity.addPassQps(metricEntity.getPassQps());
                oldEntity.addRtAndSuccessQps(metricEntity.getRt(), metricEntity.getSuccessQps());
                oldEntity.addBlockQps(metricEntity.getBlockQps());
                oldEntity.addExceptionQps(metricEntity.getExceptionQps());
                oldEntity.addCount(1);
            } else {
                resourceCount.put(resource, MetricEntity.copyOf(metricEntity));
            }
        }

        // Order by last minute b_qps DESC.
        return resourceCount.entrySet()
                .stream()
                .sorted((o1, o2) -> {
                    MetricEntity e1 = o1.getValue();
                    MetricEntity e2 = o2.getValue();
                    int t = e2.getBlockQps().compareTo(e1.getBlockQps());
                    if (t != 0) {
                        return t;
                    }
                    return e2.getPassQps().compareTo(e1.getPassQps());
                })
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }
}


0x05:application.properties配置文件添加数据库配置

# datasource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/gateway_v2?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=root

# spring data jpa
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=false

主要配置数据库连接信息和JPA的配置项,JPA使用Hibernate实现。


0x06:数据库持久化换成JpaMetricsRepository实现

找到如下两个类

com.alibaba.csp.sentinel.dashboard.controller.MetricController
com.alibaba.csp.sentinel.dashboard.metric.MetricFetcher

在metricStore属性上添加多一个@Qualifier("jpaMetricsRepository")注解,如下图



0x07:验证

设置sentinel-dashboard工程的启动参数

-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard

具体可以参考【 Sentinel如何进行流量监控 】;可以发现数据已经保存到MySQL数据库。


备注:以上代码改造都是在sentinel-dashboard项目上。


参考:https://www.cnblogs.com/cdfive2018/p/9838577.html

可能想知道为什么要将文件“放入”数据库,而不是仅仅放在文件系统上。那么大多数时候,你不会。

在您的PHP应用程序需要存储整个文件的情况下,首选方法是将文件保存到服务器的文件系统中,并将文件的物理位置存储在数据库中。这通常被认为是存储文件的最简单和最快捷的方式。

但是,您可能会发现自己处于需要将文件与数据库中其他数据保持一致的情况。这给你 - 或者说:MySQL - 完全控制文件数据,而不仅仅是文件在服务器上的位置。

这种方法有一些缺点,如:降低了性能,增加了PHP代码和数据库结构的复杂性。这是您在实际应用中使用之前应仔细考虑的内容。

话虽如此,本文演示了如何将文件从浏览器上传到MySQL,以及如何将文件发送回浏览器。

开始之前

要顺利完成,您应该熟悉以下内容:

  • PHP基础知识

  • MySQL基础知识

  • 在PHP中使用MySQL(mysqli)

  • HTML表单以及如何在PHP中处理POST数据。

战斗计划

与所有计划一样,在我们开始撰写之前,我们需要稍微计划一点。所以我们知道我们在写作之前要写什么。

在开始该程序之前,我们需要设计数据库。这不是一个复杂的设计,因为我们不是在说创建一些复杂的文件系统。我们只需要一个单独的表格,其中包含一个用于我们文件的BLOB字段和各种其他字段来存储我们文件中的信息,如名称,大小,类型。

接着。该程序的第一阶段是将文件从我们的用户获取到我们的PHP可以与它进行交互的服务器上。这是该过程中最简单的部分,只需要一个基本的HTML表单。

第二阶段涉及阅读上传的文件,确保已成功上传并将其添加到数据库。这与上传文件到文件系统时使用的过程类似,但使用MySQL函数而不是文件系统函数。

第三阶段是列出已经上传并保存在数据库中的所有文件,并附上一个链接,以便下载。这里唯一的问题是文件不存在于服务器上,所以我们如何创建一个链接呢?这是第4阶段处理的一个问题,我们在阶段3中需要做的是创建一个链接,该链接将嵌入到URL中的文件的ID。

第四个也是最后一部分是对这个过程最令人困惑的部分。我们获取文件并将其发送到客户端浏览器的部分。

我们从使用MySQL函数和第3阶段发送的ID开始,从数据库中获取文件数据。然后我们设置几个标题,让浏览器知道预期的内容,最后发送文件的内容。

现在,以此摘要为指导,让我们开始编写我们的程序。

阶段0:构建数据库

数据库很简单。一个具有文件数据的BLOB字段的表和与文件相关的各种信息的几个字段:

  1. CREATE TABLE`file`(

  2. `id` Int无符号不空Auto_Increment,

  3. `name` VarChar(255)Not Null默认'Untitled.txt',

  4. `mime` VarChar(50)Not Null默认'text / plain',

  5. `size` BigInt Unsigned Not Null默认值0,

  6. `data` MediumBlob不空,

  7. `created` DateTime不空,

  8. PRIMARY KEY(`id`)

如您所见,我们存储文件名,包括扩展名。

我们有mime类型,我们使用它来让浏览器知道我们正在处理什么样的文件。

文件的大小(以字节为单位)。

最后数据本身在一个MediumBlob字段中。

阶段1:上传文件

现在,我们需要从用户那里获取文件。我们设计的表不需要用户的任何其他信息,因此我们将简单地创建一个HTML表单,只有一个“文件”输入字段和提交按钮:

  1. <!DOCTYPE html>

  2. <HEAD>

  3. <title> MySQL文件上传示例</ title>

  4. <meta http-equiv =“content-type”content =“text / html; charset = UTF-8”>

  5. </ HEAD>

  6. <BODY>

  7. <form action =“add_file.php”method =“post”enctype =“multipart / form-data”>

  8. <input type =“file”name =“uploaded_file”> <br>

  9. <input type =“submit”value =“Upload file”>

  10. </ FORM>

  11. <P>

  12. <a href="list_files.php">查看所有文件</a>

  13. </ p>

  14. </ BODY>

  15. </ HTML>

注意<form>元素的第三个属性“enctype”。这告诉浏览器如何将表单数据发送到服务器。就这样,当发送文件时,必须设置为“multipart / form-data”。

如果设置任何其他方式,或者根本不设置,您的文件可能不会被正确传输。

在底部,我们将链接到我们将在阶段3中创建的列表。

阶段2:将文件添加到数据库

以我们在阶段1中构建的形式,我们将action属性设置为“add_file.php”。这是我们要在这个阶段进行构建的文件。

此文件需要检查文件是否已经上传,确保已经上传了没有错误,并将其添加到数据库中:

  1. <?PHP

  2. //检查文件是否已上传

  3. if(isset($ _ FILES ['uploaded_file'])){

  4. //确保文件被发送没有错误

  5. if($ _ FILES ['uploaded_file'] ['error'] == 0){

  6. //连接到数据库

  7. $ dbLink = new mysqli('127.0.0.1','user','pwd','myTable');

  8. if(mysqli_connect_errno()){

  9. die(“MySQL连接失败:”mysqli_connect_error());

  10. }

  11. //收集所有必需的数据

  12. $ name = $ dbLink-> real_escape_string($ _ FILES ['uploaded_file'] ['name']);

  13. $ mime = $ dbLink-> real_escape_string($ _ FILES ['uploaded_file'] ['type']);

  14. $ data = $ dbLink-> real_escape_string(file_get_contents($ _ FILES ['uploaded_file'] ['tmp_name']));

  15. $ size = intval($ _ FILES ['uploaded_file'] ['size']);

  16. //创建SQL查询

  17. $ query =“

  18. INSERT INTO`file`(

  19. `name`,`mime`,`size`,`data`,````

  20. 值(

  21. '{$ name}','{$ mime}',{$ size},'{$ data}',NOW()

  22. )“;

  23. //执行查询

  24. $ result = $ dbLink-> query($ query);

  25. //检查是否成功

  26. if($ result){

  27. 回应“成功!您的文件已成功添加!

  28. }

  29. else {

  30. echo'错误!无法插入文件'

  31. 。“<PRE> {$ dbLink->误差} </ PRE>”;

  32. }

  33. }

  34. else {

  35. echo'文件上传时出错。“

  36. 。'错误代码: '。INTVAL($ _ FILES [ 'uploaded_file'] [ '错误']);

  37. }

  38. //关闭mysql连接

  39. $ dbLink->接近();

  40. }

  41. else {

  42. echo'错误!一个文件没有发送!

  43. }

  44. //回到主页面的链接

  45. echo'<p>点击<a href="index.html">此处</a>返回</ p>';

  46. ?>

  47. 阶段3:列出所有现有文件

    所以,现在我们在我们的数据库中有几个文件,我们需要创建一个文件列表并将它们链接起来,以便它们可以被下载:

    1. <?PHP

    2. //连接到数据库

    3. $ dbLink = new mysqli('127.0.0.1','user','pwd','myTable');

    4. if(mysqli_connect_errno()){

    5. die(“MySQL连接失败:”mysqli_connect_error());

    6. }

    7. //查询所有现有文件的列表

    8. $ sql ='SELECT`id`,`name`,`mime`,`size`,`created` FROM`file`';

    9. $ result = $ dbLink-> query($ sql);

    10. //检查是否成功

    11. if($ result){

    12. //确保在那里有一些文件

    13. if($ result-> num_rows == 0){

    14. echo'<p>数据库中没有文件</ p>';

    15. }

    16. else {

    17. //打印表的顶部

    18. echo'<table width =“100%”>

    19. <TR>

    20. <TD> <B>名称</ B> </ TD>

    21. <TD> <B>默</ B> </ TD>

    22. <td> <b>大小(字节)</ b> </ td>

    23. <TD> <B>的创</ B> </ TD>

    24. <TD> <B>&NBSP; </ B> </ TD>

    25. </ TR>';

    26. //打印每个文件

    27. while($ row = $ result-> fetch_assoc()){

    28. 回声“

    29. <TR>

    30. <TD> {$行[ '名称']} </ TD>

    31. <TD> {$行[ 'MIME']} </ TD>

    32. <TD> {$行[ '尺寸']} </ TD>

    33. <TD> {$行[ '创造']} </ TD>

    34. <td> <a href='get_file.php?id={$row['id']}'>下载</a> </ td>

    35. </ TR>“;

    36. }

    37. //关闭表

    38. echo'</ table>';

    39. }

    40. //释放结果

    41. $ result->免费();

    42. }

    43. 其他

    44. {

    45. echo'错误!SQL查询失败:';

    46. echo“<pre> {$ dbLink-> error} </ pre>”;

    47. }

    48. //关闭mysql连接

    49. $ dbLink->接近();

    50. ?>

    51. 阶段4:下载文件

      这部分是通常导致最混乱的部分。

      要真正了解如何工作,您必须了解浏览器如何下载文件。当浏览器从HTTP服务器请求文件时,服务器响应将包含关于它所包含的内容的信息。这些信息位称为标题。标题通常包括有关要发送的数据类型的信息,响应的大小以及文件的文件名称。

      当然有很多其他的标题,我不会在这里覆盖,但值得研究!

      现在,这段代码。我们只需读取第3阶段链接发送的ID即可。如果ID有效,我们将获取我们收到的ID的文件信息,发送头文件,最后发送文件数据:

      1. <?PHP

      2. //确保已经通过了ID

      3. if(isset($ _ GET ['id'])){

      4. //获取ID

      5. $ id = intval($ _ GET ['id']);

      6. //确保该ID实际上是一个有效的ID

      7. if($ id <= 0){

      8. 死('该ID无效!);

      9. }

      10. else {

      11. //连接到数据库

      12. $ dbLink = new mysqli('127.0.0.1','user','pwd','myTable');

      13. if(mysqli_connect_errno()){

      14. die(“MySQL连接失败:”mysqli_connect_error());

      15. }

      16. //获取文件信息

      17. $ query =“

      18. SELECT`mime`,`name`,`size`,`data`

      19. FROM`file`

      20. WHERE`id` = {$ id}“;

      21. $ result = $ dbLink-> query($ query);

      22. if($ result){

      23. //确保结果有效

      24. if($ result-> num_rows == 1){

      25. //获取行

      26. $ row = mysqli_fetch_assoc($ result);

      27. //打印头

      28. header(“Content-Type:”。$ row ['mime']);

      29. header(“Content-Length:”。$ row ['size']);

      30. header(“Content-Disposition:attachment; filename =”。$ row ['name']);

      31. //打印数据

      32. echo $ row ['data'];

      33. }

      34. else {

      35. echo'错误!没有图像存在该ID。

      36. }

      37. //释放mysqli资源

      38. @mysqli_free_result($结果);

      39. }

      40. else {

      41. echo“错误!查询失败:<pre> {$ dbLink-> error} </ pre>”;

      42. }

      43. @mysqli_close($ DBLINK);

      44. }

      45. }

      46. else {

      47. echo'错误!没有身份证通过。

      48. }

      49. ?>

      50. 任何体面的浏览器都应该能够读取标题,并了解这是什么类型的文件,并且它是要下载的,而不是打开。

        终点线

        所以,正如你所看到的,这并不像人们想象的那么复杂。

        这段代码当然只是为了演示的目的,我不会建议使用它,而不增加一点额外的安全性。未经编辑,此代码基本上允许任何人上传任何内容到您的服务器,这不是一个好主意!