整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          使用 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. 任何體面的瀏覽器都應該能夠讀取標題,并了解這是什么類型的文件,并且它是要下載的,而不是打開。

                終點線

                所以,正如你所看到的,這并不像人們想象的那么復雜。

                這段代碼當然只是為了演示的目的,我不會建議使用它,而不增加一點額外的安全性。未經編輯,此代碼基本上允許任何人上傳任何內容到您的服務器,這不是一個好主意!


          主站蜘蛛池模板: 国产成人久久一区二区不卡三区| 一区二区三区四区在线播放 | 亚洲性色精品一区二区在线| 女女同性一区二区三区四区| 中文字幕在线视频一区| 精品无码人妻一区二区三区| 亚洲第一区精品观看| 波多野结衣AV一区二区三区中文| 91在线精品亚洲一区二区| 国模吧无码一区二区三区| 一区二区三区国模大胆| 精品一区二区三区无码视频| 婷婷亚洲综合一区二区| 玩弄放荡人妻一区二区三区| 精品欧洲av无码一区二区14| 一区二区三区四区免费视频| 国精无码欧精品亚洲一区| 国模丽丽啪啪一区二区| 久久亚洲色一区二区三区| 精品国产日韩亚洲一区| 久久精品人妻一区二区三区| 国产成人久久精品麻豆一区| 国产激情精品一区二区三区| 国产精品亚洲不卡一区二区三区 | 色窝窝无码一区二区三区成人网站 | 中文字幕精品亚洲无线码一区应用| 立川理惠在线播放一区| 日本一区二区三区在线观看视频| 国模精品一区二区三区视频| 成人精品一区二区不卡视频| 国产一区二区三区播放| 无码精品人妻一区二区三区免费| 一区二区视频在线免费观看| 红桃AV一区二区三区在线无码AV| 夜夜添无码一区二区三区| 一区二区三区在线|欧| 中文字幕人妻AV一区二区| 日本一区二区三区精品视频| 国产精品一区12p| 亚洲综合一区二区精品久久| 日韩一区二区三区电影在线观看 |