2024年6月


Excel Grid Data Converter 知识点总结

本文档总结了
ExcelGridConverter.py
脚本所涉及的关键 Python 知识点。该脚本用于从多个 Excel 文件中提取特定格式的数据并转换为一个新的 Excel 文件。

目录

  1. 导入库
  2. Pandas 数据处理
  3. Tkinter GUI 界面
  4. 文件操作
  5. 主要函数解释
  6. 总结

导入库

脚本使用了以下主要库:

  • tkinter
    :用于创建图形用户界面。
  • pandas
    :用于处理 Excel 数据。
  • os
    :用于处理文件和目录路径。
import tkinter as tk
from tkinter import filedialog, messagebox
import pandas as pd
import os

Pandas 数据处理

读取 Excel 文件

使用
pd.read_excel
方法读取 Excel 文件,并使用
sheet_name=None
参数读取所有工作表。添加
index_col=None
参数以确保第一列不会被自动设置为索引列。

source_df = pd.read_excel(file_path, sheet_name=None, index_col=None)
source_data = source_df['一格一案']

数据提取

通过 Pandas 的
iloc
方法,根据行列索引提取特定数据。

result_data = {
    '网格编号': source_data.iloc[1, 1],
    '责任段': source_data.iloc[1, 3],
    ...
}

处理合并单元格数据:

risk_check_path = "\n".join(source_data.iloc[9:19, 1].dropna().astype(str))
result_data['五、风险项点检查路径'] = risk_check_path

创建 DataFrame 并导出为 Excel 文件

将所有提取的数据放入一个 DataFrame 中,并使用
to_excel
方法导出为 Excel 文件。

result_df = pd.DataFrame(all_data)
result_df.to_excel(output_file_path, index=False)

Tkinter GUI 界面

创建主窗口

使用
tk.Tk
创建主窗口,并设置窗口标题、大小和位置。

root = tk.Tk()
root.title("Excel 转换工具")
root.geometry(f'{window_width}x{window_height}+{position_right}+{position_top}')

创建按钮和标签

使用
tk.Button

tk.Label
创建按钮和标签,并设置其属性和布局。

title_label = tk.Label(root, text="Excel 转换工具", font=("Arial", 18))
title_label.pack(pady=20)

select_button = tk.Button(root, text="选择 Excel 文件", command=select_files, font=("Arial", 12))
select_button.pack(pady=10)

文件操作

文件对话框

使用
filedialog.askopenfilenames
打开文件选择对话框,允许用户选择多个 Excel 文件。使用
filedialog.asksaveasfilename
打开文件保存对话框,允许用户选择保存路径。

file_paths = filedialog.askopenfilenames(filetypes=[("Excel 文件", "*.xlsx")])
output_file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel 文件", "*.xlsx")])

主要函数解释

transform_to_result_format_specific

该函数从源数据中提取特定字段,并返回一个字典格式的结果数据。

def transform_to_result_format_specific(source_data, source_file_path):
    risk_check_path = "\n".join(source_data.iloc[9:19, 1].dropna().astype(str))
    result_data = { ... }
    return result_data

select_files

该函数处理文件选择、数据转换和结果保存的主要逻辑。

def select_files():
    file_paths = filedialog.askopenfilenames(filetypes=[("Excel 文件", "*.xlsx")])
    all_data = []
    for file_path in file_paths:
        source_df = pd.read_excel(file_path, sheet_name=None, index_col=None)
        source_data = source_df['一格一案']
        transformed_data = transform_to_result_format_specific(source_data, file_path)
        all_data.append(transformed_data)
    result_df = pd.DataFrame(all_data)
    output_file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel 文件", "*.xlsx")])
    if output_file_path:
        result_df.to_excel(output_file_path, index=False)
        messagebox.showinfo("成功", "文件已成功转换并保存。")

总结

通过本脚本,我们学习了如何使用 Pandas 读取和处理 Excel 数据,如何使用 Tkinter 创建图形用户界面,以及如何处理文件对话框和文件操作。这些知识点在日常的 Python 开发中非常实用,特别是涉及数据处理和用户界面的项目中。


前言

日常Bug排查系列都是一些简单Bug的排查。笔者将在这里介绍一些排查Bug的简单技巧,同时顺便积累素材。

Bug现场

线上连续两天出现NP异常,而且都是凌晨低峰期才出现,在凌晨的流量远没有白天高峰期大。而出问题的接口又是通常的业务请求。于是,很自然的,我们就想凌晨有什么特殊的运维动作,翻了下时间线。发现,每天凌晨都会进行改表,而修改的这张表恰好就是出现NP异常的表。如下图所示:

在此解释下业务的相关场景。A表是主表,B表是子表,两者都是严格保证在一个事务内一块插入和更新的,在该表时刻确出现了在一个事务内查询,能查到A确查不到B的现象。

思路

数据库的一个核心特性就是原子性,看上去这个场景破坏了原子性。但是由于是和改表强相关,其它时间没有类似错误。那么很明显的,思路就会指向该表这个动作会短暂的破坏原子性。由于线上使用的ghost进行改表,于是笔者就看了下ghost改表的原理:

ghost会创建一个影子表,在影子表上完成alter改表,然后分批将全量数据应用到新表。
同时在处理增量数据的时候,通过解析binLog事件,将任务期间的新增数据应用到新表。
最后一步,通过Rename语句使新表替换老表

从这个原理中可以推断,最后一步Rename的时候才会对当前的SQL产生影响,是不是刚好这个这个Rename操作短暂的使读数据不一致了呢?看了下DBA那边的改表日志,发现Rename那个时刻和NP异常出现时刻完全吻合。看来它就是罪魁祸首了。

为什么Rename会导致读数据不一致?

笔者稍加思索就明白了原因。首先,线上库的隔离级别是RR的,也就是可重复读。而Alter表的时候势必会有一张旧表B和新表BNew。业务的事务保证是操作在A和B上的,而读数据不一致应该是A和BNew上,所以无法保证A和BNew的一致。只能通过binLog的重放保证最终一致。 那么最终导致问题的原因就很明显了,如下所示:

BNew新表通过ghost的binlog重放将原B表中相关的binLog重放到BNew表中。但是在事务T2开始的时候BNew这张表中新纪录B还没有被重放。在事务T2开始的时候首先查询了A表建立了MVCC视图,这时候的数据库实际快照就是A表有A,BNew表没有B。尽管在Rename表的时候MySQL会对B和BNew都进行锁表,这时候所有对于这两张表的访问都会等锁表的结束。但是由于RR的原因,这个事务内后续读BNew表的时候始终就是A表有A,BNew表没有B这样的现象。在后续的查询中select B查询的实际上是BNew表,进而产生了数据不一致,进而导致了NullPointerException。

测试复现实验的一个小问题

还有一个小问题,就是笔者在线下设计相关实验复现问题的时候。这个复现的实验看上去是比较容易的,模拟一下事务顺序,新建一张BNew表然后Rename下,看看现象是否一致就可以了,如下图所示:

但笔者发现,在Rename的时候,模拟的请求2在做select 新B表的时候始终会出现

Table definition has changed,please retry transaction

这个报错。于是笔者看了下MySQL的源代码,要想让Rename不报错,必须在模拟的请求2事务开始之前就创建这个BNew表,否则请求2在查询BNew表的时候就会由于找不到UndoHistory导致报错。MySQL源代码如下所示:

row_vers_old_has_index_entry(......){

  ......
  /* If purge can't see the record then we can't rely on
  the UNDO log record. */

  bool purge_sees = trx_undo_prev_version_build(
   rec, mtr, version, index, *offsets, heap,
   &prev_version, NULL, vrow, 0);
  // 在这边,如果找不到这张表在t1前的undo history的话,则会报"Table definition has changed, please retry transaction"
  err  = (purge_sees) ? DB_SUCCESS : DB_MISSING_HISTORY;

  if (prev_heap != NULL) {
   mem_heap_free(prev_heap);
  }
    ......
}

总结

线上环境是错综复杂的,改表等运维操作也会导致出现意料之外的结果,很多组件的特性在一些特殊的情况下会被打破,所以防御式编程就显得尤其重要了。

本文分享自华为云社区
《使用Python实现深度学习模型:BERT模型教程》
,作者: Echo_Wish。

BERT(Bidirectional Encoder Representations from Transformers)是Google提出的一种用于自然语言处理(NLP)的预训练模型。BERT通过双向训练Transformer,能够捕捉到文本中词语的上下文信息,是NLP领域的一个里程碑。

在本文中,我们将详细介绍BERT模型的基本原理,并使用Python和TensorFlow实现一个简单的BERT模型应用。

1. BERT模型简介

1.1 Transformer模型复习

BERT基于Transformer架构。Transformer由编码器(Encoder)和解码器(Decoder)组成,但BERT只使用编码器部分。编码器的主要组件包括:

多头自注意力机制(Multi-Head Self-Attention):计算序列中每个位置对其他位置的注意力分数。
前馈神经网络(Feed-Forward Neural Network):对每个位置的表示进行独立的非线性变换。

1.2 BERT的预训练与微调

BERT的训练分为两步:

预训练(Pre-training):在大规模语料库上进行无监督训练,使用两个任务:

  • 遮蔽语言模型(Masked Language Model, MLM):随机遮蔽输入文本中的一些词,并要求模型预测这些被遮蔽的词。
  • 下一句预测(Next Sentence Prediction, NSP):给定句子对,预测第二个句子是否是第一个句子的下文。

微调(Fine-tuning):在特定任务上进行有监督训练,如分类、问答等。

2. 使用Python和TensorFlow实现BERT模型

2.1 安装依赖

首先,安装必要的Python包,包括TensorFlow和Transformers(Hugging Face的库)。

pip install tensorflow transformers

2.2 加载预训练BERT模型

我们使用Hugging Face的Transformers库加载预训练的BERT模型和对应的分词器(Tokenizer)。

import tensorflow astffromtransformers import BertTokenizer, TFBertModel

# 加载预训练的BERT分词器和模型
tokenizer
= BertTokenizer.from_pretrained('bert-base-uncased')
model
= TFBertModel.from_pretrained('bert-base-uncased')

2.3 数据预处理

我们将使用一个简单的句子分类任务作为示例。假设我们有以下数据:

sentences = ["I love machine learning.", "BERT is a powerful model.", "I enjoy studying AI."]
labels
= [1, 1, 1] # 假设1表示积极,0表示消极

我们需要将句子转换为BERT输入格式,包括输入ID、注意力掩码等。

# 将句子转换为BERT输入格式
input_ids
=[]
attention_masks
=[]for sentence insentences:
encoded_dict
=tokenizer.encode_plus(
sentence, # 输入文本
add_special_tokens
=True, # 添加特殊[CLS]和[SEP]标记
max_length
= 64, # 填充和截断长度
pad_to_max_length
=True,
return_attention_mask
=True, # 返回注意力掩码
return_tensors
= 'tf'# 返回TensorFlow张量
)

input_ids.append(encoded_dict[
'input_ids'])
attention_masks.append(encoded_dict[
'attention_mask'])

input_ids
= tf.concat(input_ids, axis=0)
attention_masks
= tf.concat(attention_masks, axis=0)
labels
= tf.convert_to_tensor(labels)

2.4 构建BERT分类模型

我们在预训练的BERT模型基础上添加一个分类层。

fromtensorflow.keras.layers import Densefromtensorflow.keras.models import ModelclassBertClassifier(Model):
def __init__(self, bert):
super(BertClassifier, self).__init__()
self.bert
=bert
self.dropout
= tf.keras.layers.Dropout(0.3)
self.classifier
= Dense(1, activation='sigmoid')

def call(self, input_ids, attention_mask):
outputs
= self.bert(input_ids, attention_mask=attention_mask)
pooled_output
= outputs[1]
pooled_output
=self.dropout(pooled_output)returnself.classifier(pooled_output)

# 实例化BERT分类模型
bert_classifier
= BertClassifier(model)

2.5 编译和训练模型

编译模型并进行训练。

# 编译模型
optimizer
= tf.keras.optimizers.Adam(learning_rate=2e-5)
loss
=tf.keras.losses.BinaryCrossentropy()
metric
=tf.keras.metrics.BinaryAccuracy()

bert_classifier.compile(optimizer
=optimizer, loss=loss, metrics=[metric])

# 训练模型
bert_classifier.fit([input_ids, attention_masks], labels, epochs
=3, batch_size=2)

2.6 评估模型

训练完成后,我们可以对新数据进行预测。

# 预测新句子
new_sentences
= ["AI is fascinating.", "I dislike machine learning."]
new_input_ids
=[]
new_attention_masks
=[]for sentence innew_sentences:
encoded_dict
=tokenizer.encode_plus(
sentence,
add_special_tokens
=True,
max_length
= 64,
pad_to_max_length
=True,
return_attention_mask
=True,
return_tensors
= 'tf')

new_input_ids.append(encoded_dict[
'input_ids'])
new_attention_masks.append(encoded_dict[
'attention_mask'])

new_input_ids
= tf.concat(new_input_ids, axis=0)
new_attention_masks
= tf.concat(new_attention_masks, axis=0)

# 进行预测
predictions
=bert_classifier.predict([new_input_ids, new_attention_masks])
print(predictions)

3. 总结

在本文中,我们详细介绍了BERT模型的基本原理,并使用Python和TensorFlow实现了一个简单的BERT分类模型。通过本文的教程,希望你能够理解BERT模型的工作原理和实现方法,并能够应用于自己的任务中。随着对BERT模型的理解加深,你可以尝试实现更复杂的任务,如问答系统、命名实体识别等。

点击关注,第一时间了解华为云新鲜技术~

PrimiHub
一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。

RSA(Rivest–Shamir–Adleman)加密算法是一种基于大素数分解难题的非对称加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman于1977年提出。RSA算法广泛应用于数字签名、数据加密和密钥交换等领域,其安全性依赖于两个大素数的乘积难以分解的特性。RSA算法的核心是利用一对密钥:公钥和私钥。本文将详细介绍RSA算法中的密钥对生成与传输过程,并分析其在实际应用中的重要性和方法。

RSA算法简介

基本原理

RSA算法的安全性依赖于大整数分解的计算复杂性。具体来说,RSA算法基于以下几个基本数学原理:

  1. 素数选择
    :选择两个大素数
    \(p\)

    \(q\)
  2. 模数计算
    :计算模数
    \(n\)
    ,其中
    \(n = p \times q\)
  3. 欧拉函数
    :计算欧拉函数
    \(\phi(n)\)
    ,其中
    \(\phi(n) = (p-1) \times (q-1)\)
  4. 公钥指数选择
    :选择一个小于
    \(\phi(n)\)
    且与
    \(\phi(n)\)
    互质的整数
    \(e\)
  5. 私钥指数计算
    :计算私钥指数
    \(d\)
    ,使得
    \(d \times e \equiv 1 \ (\text{mod} \ \phi(n))\)

公钥由
\((n, e)\)
组成,私钥由
\((n, d)\)
组成。

数学公式

RSA算法的加密和解密过程可以通过以下公式表示:

  • 加密
    :给定明文
    \(m\)
    和公钥
    \((n, e)\)
    ,密文
    \(c\)
    通过公式
    \(c = m^e \ (\text{mod} \ n)\)
    生成。
  • 解密
    :给定密文
    \(c\)
    和私钥
    \((n, d)\)
    ,明文
    \(m\)
    通过公式
    \(m = c^d \ (\text{mod} \ n)\)
    还原。

流程图示

密钥生成主体

用户自身生成

用户在本地设备上生成密钥对的优点是私钥始终不离开用户设备,安全性高。然而,如果用户设备被攻破,私钥可能被窃取。

可信第三方生成

由可信的第三方(如认证机构)生成并分发密钥对可以简化用户操作。这种方式需要信任第三方不会滥用或泄露私钥。通常,可信第三方会采用严格的安全措施和审计机制来保护密钥。

硬件安全模块(HSM)生成

硬件安全模块(HSM)是一种专用设备,设计用于高安全性环境中生成、存储和管理密钥。HSM具有防篡改和防物理攻击的特点,可以显著提高密钥的安全性。

云端密钥管理服务

近年来,随着云计算的发展,许多云服务提供商(如AWS、Azure、Google Cloud)提供了云端密钥管理服务(KMS)。用户可以利用KMS在云端生成和管理RSA密钥对,这种方式不仅方便快捷,还具备一定的安全性和可扩展性。

密钥传输

公钥传输

公钥传输不需要保密,但必须确保其完整性和真实性。常见的公钥传输方式包括:

公开发布

用户可以将公钥发布在公开场所,如公司网站、社交媒体平台或公告栏。这种方式简单直观,但需防止恶意篡改。

数字证书

数字证书是一种电子文档,由权威认证机构(CA)签发,包含持有者身份信息和公钥。数字证书可以确保公钥的真实性,并防止中间人攻击。数字证书通常采用X.509标准格式,广泛应用于SSL/TLS协议中。

可信中介传输

通过可信中介(如邮件、消息传递服务)传输公钥时,需确保中介本身的安全性。例如,可以通过加密的电子邮件或受信任的消息应用(如Signal、WhatsApp)发送公钥。

私钥传输

私钥传输必须保证其保密性和完整性,常见的传输方式包括:

物理传输

将私钥存储在安全介质(如U盘、智能卡)中,通过安全渠道(如专人送达)传输。这种方式适用于需要极高安全性的场合,但成本较高。

加密传输

使用对称加密算法加密私钥,通过安全通信渠道(如SSL/TLS)传输。加密传输可以在一定程度上保证私钥的安全性,但前提是对称密钥的管理同样需要高安全措施。

密钥分割技术

将私钥分割成多个部分,分别通过不同渠道传输,接收方在接收到所有部分后再进行合并。这种方式可以提高私钥传输的安全性,防止单一渠道被攻破。

密钥管理与保护

无论使用何种方式传输密钥,密钥管理与保护都是至关重要的。以下是一些关键的密钥管理与保护措施:

密钥备份与恢复

定期备份密钥并确保备份的安全存储,可以防止因设备故障或数据丢失导致密钥丢失。同时,制定密钥恢复策略,确保在紧急情况下能够迅速恢复密钥。

密钥轮换

定期轮换密钥可以降低密钥被破解或泄露的风险。密钥轮换应遵循一定的策略,如每隔一段时间或在发现安全事件后立即进行轮换。

密钥销毁

当密钥不再需要时,应及时销毁。密钥销毁应采用安全的方法,确保密钥无法被恢复或重建。

实际应用中的密钥生成与传输

为了更好地理解RSA密钥对生成与传输的实际应用,我们以一个具体的案例来说明。

案例分析:Alice与Bob的通信

假设Alice希望与Bob进行安全的通信,他们可以采用以下步骤来生成和传输RSA密钥对:

  1. 密钥生成


    • Alice在本地生成RSA密钥对,得到公钥
      \((n_A, e_A)\)
      和私钥
      \((n_A, d_A)\)
    • Bob同样在本地生成RSA密钥对,得到公钥
      \((n_B, e_B)\)
      和私钥
      \((n_B, d_B)\)
  2. 公钥交换


    • Alice将她的公钥
      \((n_A, e_A)\)
      发送给Bob,确保公钥传输的安全性和完整性。
    • Bob收到Alice的公钥后,也将自己的公钥
      \((n_B, e_B)\)
      发送给Alice。
  3. 私钥保护


    • Alice和Bob都严格保护自己的私钥
      \(d_A\)

      \(d_B\)
      ,确保只有授权的人能够访问私钥。
    • 私钥可以存储在安全的硬件设备或使用加密算法加密后存储。
  4. 安全通信


    • Alice使用Bob的公钥
      \((n_B, e_B)\)
      加密要发送给Bob的消息。
    • Bob收到密文后,使用自己的私钥
      \(d_B\)
      解密消息。

通过这种方式,Alice和Bob可以安全地进行加密通信,保护其消息免受窥探和篡改。


RSA非对称加密算法的密钥对生成与传输是信息安全领域的核心问题之一。密钥生成过程需要保证随机性和安全性,而密钥的传输则需要选择适当的方式来确保其保密性和完整性。通过合理的密钥管理和保护措施,可以有效地利用RSA算法保护通信安全,防止信息泄露和篡改。在实际应用中,用户和系统管理员需要结合具体情况选择最佳的密钥生成和传输策略,以达到最佳的安全性和效率。

PrimiHub
一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、密码学、联邦学习、同态加密等隐私计算领域的技术和内容。

经过上传客户要求主副表迁出,又提出可以将某张表的数据导出excel,听着很简单,实际看数据表发现上万条数据,并且需要关联表查询相关字段,导出的表格才可以被客户看明白。

要是使用office包目前后台内存耗尽,被迫停止运行,所以想要突破百万条数据导出需要另辟它路。所以就是使用了导出CSV并非excel文件。

1、设置程序需要一直运行并设置运行内存(php提示过128M)
by user 悦悦 https://www.cnblogs.com/nuanai

2、数据查询出需要导出的数据字段(分页)

3、分页进行导出文件并一起合并成压缩包后下载

如果导出前需要某些条件筛选,那就需要在前端设置表单提交筛选条件(不过多介绍)

导出配置的编写,需要设置文件路径、名称、表头等基本信息
by user 悦悦 https://www.cnblogs.com/nuanai

1 //让程序一直运行
2 set_time_limit(0);3 //设置程序运行内存---php提示过128M
4 ini_set('memory_limit', '128M');5 //文件名使用的日期命名
6 $fileName = date('YmdHis', time());7 //文件保存位置
8 $destPath = ROOT_PATH . 'public' .DS. 'uploads'.DS.'download'.DS.$fileName. '.csv';9 //打开文件句柄,准备写入数据
10 $fileHandle = fopen($destPath, 'w');11 //检查文件句柄是否成功打开
12 if ($fileHandle === false) {13     die('无法打开文件: ' . $fileHandle);14 }15 //表头
16 fputcsv($fileHandle,['序号', '产品名称', '产品型号','工名称','条码1','条码2','状态','时间']);17 //条数
18 $nums = 10000;19 //数据表的总数
20 $count = $this->model->count();21 $step = ceil($count/$nums);

查询数据,这边需要关联表查询用的是左查询的方式,通过for循环进行每页的写入文件

时间戳格式化直接使用了sql语句转换,省去进入循环后再次循环格式化
by user 悦悦 https://www.cnblogs.com/nuanai

1
FROM_UNIXTIME(updatetime,
'
%Y-%m-%d %H:%i:%s
'
)

需要注意就是写入CSV之前需要对字段转一维数组

1 for($i=0;$i<$step;$i++){2     $start = $i*$nums;3     $result = $this->model->alias('pd')4         ->join('products ps','ps.id=pd.product_id','LEFT')5         ->join('step st','st.id=pd.step_id','LEFT')6         ->field("pd.id,ps.name,ps.spec,st.name as sname,pd.barcode,pd.barnum,pd.isin,FROM_UNIXTIME(pd.updatetime, '%Y-%m-%d %H:%i:%s') AS uptime")->order("pd.updatetime","desc")->limit($start,$nums)->select();7         
8     foreach ($result as $K=> &$item) {9         //转换每行数据为一维数组
10         $rowData =[11             $item['id'],
12             $item['name'],
13             $item['spec'],
14             $item['sname'],
15             $item['barcode'],
16             $item['barnum'],
17             $item['isin'],
18             $item['uptime']19 ];20         
21         //写入 CSV
22         fputcsv($fileHandle, $rowData);23 }24     
25 }

开启了文件句柄,记得关闭

1
//
关闭文件句柄
2
fclose
(
$fileHandle
);

到这里就是可以将上万条数据导出CSV文件了,为了减少客户下载时间,又进行了对文件压缩,客户下载压缩文件。
by user 悦悦 https://www.cnblogs.com/nuanai

这里使用的调用方法,需要传入参数:文件名

1 if (!class_exists('ZipArchive')) {2     throw new Exception(__('ZinArchive not install'));3 }4 $downloadPath = ROOT_PATH . 'public' .DS. 'uploads'.DS.'download'.DS; //服务器中文件zip保存的位置
5 $destDir =  $downloadPath.$zipName . '.zip'; //全路径
6 $zip = new \ZipArchive;  //压缩类
7 $zip->open($destDir, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);8 $csvDir = $downloadPath .$csvFileName.'.csv'; //需要压缩文件的全路径
9 $fileName = basename($csvDir); //获取文件名
10 $zip->addFile($csvDir, $fileName);11 $zip->close();12 $zipFilename = basename($destDir);13 //设置HTTP头信息
14 header('Content-Type: application/octet-stream');15 header('Content-Disposition: attachment; filename="'.$zipFilename.'"');16 header('Content-Length: ' . filesize($destDir));17 //读取并输出ZIP文件内容
18 readfile($destDir);19 //删除临时ZIP文件(可选)
20 unlink($destDir);

这边再生成压缩文件后,并删除了云服务器中的压缩文件,如果服务器空间允许看个人选择;如果想要删除服务器中的CSV文件,需要在生成压缩文件后进行删除操作

在第一个方法的最后添加如下代码:保存文件的路径
by user 悦悦 https://www.cnblogs.com/nuanai

1
unlink
(
$destPath
);

到这里就可以实现客户通过后台进行上万条数据的导出下载功能了,就是有一个弊端,需要等待浏览器跳出下载框,哎,给客户说一下耐心生成文件吧。