当前位置: 首页 > article >正文

【企业级Web应用中的文件下载处理:从S3预签名URL到压缩状态管理】

企业级Web应用中的文件下载处理:从S3预签名URL到压缩状态管理

1. 引言:一个看似简单的下载功能背后

在开发企业级Web应用时,文件下载功能看似简单,却常常隐藏着诸多技术挑战。近期,我们在一个xx申报系统项目中,遇到了一个典型问题:同一批数据中,部分文件下载正常(得到ZIP文件),而另一部分却返回XML格式的错误信息。深入排查后,我们发现这涉及到AWS S3存储服务、文件压缩状态管理、预签名URL机制等多方面因素的协同。本文将以此为例,系统分析企业应用中的文件下载解决方案。

2. 对象存储服务与预签名URL基础

2.1 为什么选择对象存储

现代企业应用大多采用对象存储服务(如AWS S3、阿里云OSS、腾讯云COS等)来存储和管理用户上传的文件,原因有:

  • 扩展性:几乎无限的存储容量,按需付费
  • 可靠性:多区域容灾,数据持久性高达99.999999999%
  • 安全性:精细的访问控制,传输加密
  • 成本效益:相比自建存储架构成本低

2.2 预签名URL机制

在我们的申报系统中,用户上传的申报材料(如PDF、Word文档等)被打包成ZIP文件存储在S3中。但我们不能直接将S3的URL暴露给前端,这会带来安全隐患。因此,采用了预签名URL机制。

预签名URL工作原理

  1. 后端程序通过S3 SDK生成一个临时URL,包含必要的认证信息
  2. URL中包含签名、过期时间等参数
  3. 前端使用这个URL直接从S3下载文件,无需额外认证
  4. URL在指定时间后自动失效

典型的预签名URL结构:

/sccnp-service-dev/zip/file-id.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20250331%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250331T115824Z&X-Amz-Expires=7200&X-Amz-SignedHeaders=host&X-Amz-Signature=7eed56c9e7d675d247112bef8336883bf7d2c4dc1c1bfb711294f9ffd1a3434a

其中关键参数:

  • X-Amz-Algorithm:签名算法
  • X-Amz-Credential:访问凭证
  • X-Amz-Date:签名生成时间
  • X-Amz-Expires:URL有效期(秒)
  • X-Amz-Signature:签名值

3. 文件准备状态与下载流程

3.1 实际业务流程

在申报系统中,文件下载流程比想象的复杂:

  1. 用户上传多个申报材料文件
  2. 后端接收并存储这些文件
  3. 异步任务将这些文件打包成ZIP
  4. 数据库记录生成的ZIP文件路径
  5. 前端请求下载时,后端生成预签名URL返回
  6. 前端使用预签名URL直接下载文件

问题是,步骤3可能需要时间完成,尤其对于大量文件或高并发场景。

3.2 压缩状态标识的关键作用

我们在实践中发现,跟踪文件压缩状态至关重要。在我们的系统中,使用compress字段标识:

  • compress=1:文件已压缩完成,可以下载
  • compress=null:文件尚未完成压缩处理

这个看似简单的状态字段,实际上是整个下载流程能否正常运行的关键。

4. 异常分析:当XML出现在ZIP下载中

在项目中,我们遇到典型问题:用户批量下载多个申报材料时,部分下载得到ZIP文件,部分却变成XML文件。

4.1 问题表现

通过分析网络请求和响应,我们发现:

  1. 正常情况:

    • 请求预签名URL
    • 响应Content-Type: application/zip
    • 浏览器触发文件下载
  2. 异常情况:

    • 请求预签名URL
    • 响应Content-Type: application/xml
    • 浏览器显示XML内容

4.2 错误响应解析

当请求S3中不存在的文件时,返回的XML格式标准错误信息如下:

<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>path/to/file.zip</Key><RequestId>EXAMPLE1234567890</RequestId><HostId>example-bucket.s3.region.amazonaws.com</HostId>
</Error>

通过数据对比,发现返回XML的记录有一个共同点:compress=null,而正常下载的记录都是compress=1

5. 根本原因:文件状态与预签名URL的配合

通过深入分析,我们发现了问题的本质:

  1. 后端在记录生成时就创建了预签名URL(包括compress=null的记录)
  2. 预签名URL有效,但指向的文件在S3中可能不存在(因为压缩任务尚未完成)
  3. 前端不加判断地使用这些URL尝试下载
  4. S3返回XML格式的错误信息而非ZIP文件

这是一个典型的状态不同步问题,预签名URL的生成时机早于文件实际可用时机。

6. 全面解决方案

6.1 前端防御性编程

改进下载处理函数:

async function handleClickDownload(row) {try {// 1. 检查压缩状态if (row.compress !== 1) {ElMessage.warning('文件正在准备中,请稍后再试');return;}// 2. 检查URL是否过期const urlParams = new URLSearchParams(row.zipUrl.split('?')[1]);const signDate = urlParams.get('X-Amz-Date');const expiresIn = parseInt(urlParams.get('X-Amz-Expires') || '0');if (isUrlExpired(signDate, expiresIn)) {// 请求新的URLconst newUrl = await refreshDownloadUrl(row.id);await downloadFile(newUrl, row.operatorName);} else {// 使用现有URLawait downloadFile(`/${downloadPre}${row.zipUrl}`, row.operatorName);}} catch (error) {// 3. 错误处理console.error('下载失败:', error);// 4. 检测是否为XML响应if (error.response?.headers?.['content-type']?.includes('xml')) {ElMessage.error('文件不存在或正在处理中,请稍后再试');} else {ElMessage.error('下载失败,请重试');}}
}// 检查URL是否过期
function isUrlExpired(signDate, expiresIn) {if (!signDate || !expiresIn) return true;// 解析AWS日期格式 (yyyyMMddTHHmmssZ)const year = signDate.substring(0, 4);const month = signDate.substring(4, 6);const day = signDate.substring(6, 8);const hour = signDate.substring(9, 11);const minute = signDate.substring(11, 13);const second = signDate.substring(13, 15);const signTimestamp = new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}Z`).getTime();const expiryTimestamp = signTimestamp + (expiresIn * 1000);return Date.now() > expiryTimestamp;
}

6.2 后端改进方案

  1. 延迟生成预签名URL

    public String getDownloadUrl(String fileId) {// 1. 检查文件压缩状态FileRecord record = fileRepository.findById(fileId);if (record.getCompress() != 1) {throw new BusinessException("文件正在处理中");}// 2. 生成预签名URLreturn s3Client.generatePresignedUrl(bucketName, record.getFilePath(), Date.from(Instant.now().plus(2, ChronoUnit.HOURS))).toString();
    }
    
  2. 添加文件状态查询接口

    public FileStatus checkFileStatus(String fileId) {FileRecord record = fileRepository.findById(fileId);return new FileStatus(record.getCompress() == 1,record.getCompress() == 1 ? estimateFileSize(fileId) : null);
    }
    
  3. 提供压缩任务触发接口

    public void triggerCompression(String fileId) {// 将压缩任务加入队列compressionTaskQueue.addTask(fileId);
    }
    

6.3 架构层面优化

  1. 引入文件状态管理

    • 添加更细粒度的状态:待处理、压缩中、压缩完成、压缩失败
    • 前端UI根据状态显示不同的下载按钮状态
  2. 使用WebSocket实时通知

    • 当大文件压缩完成时,通过WebSocket通知前端
    • 用户无需刷新页面即可获知文件可下载状态
  3. 分布式压缩任务

    • 使用消息队列(如RabbitMQ)管理压缩任务
    • 多个worker节点处理压缩,提高并发能力

7. 深入理解:S3错误处理与前端防御

预签名URL机制虽然便捷,但也带来了一些挑战:

7.1 常见S3错误及处理

错误代码描述处理方案
NoSuchKey请求的文件不存在检查文件是否已生成,可能需要触发生成流程
AccessDenied签名过期或无权限请求新的预签名URL
SlowDown请求速率过高实现退避算法,逐渐增加重试间隔
InternalErrorS3内部错误稍后重试,考虑请求备用区域

7.2 前端增强下载体验

针对大文件下载,可以增强用户体验:

async function enhancedDownload(row) {if (row.compress !== 1) {// 1. 显示进度状态const statusNotification = ElNotification({title: '文件准备中',message: '正在准备下载文件,请稍候...',duration: 0,type: 'info'});// 2. 轮询文件状态const fileReady = await pollFileStatus(row.id);statusNotification.close();if (!fileReady) {ElMessage.error('文件准备超时,请稍后重试');return;}}// 3. 大文件使用流式下载const downloadResponse = await fetch(`/${downloadPre}${row.zipUrl}`);if (!downloadResponse.ok) {if (downloadResponse.headers.get('content-type')?.includes('xml')) {ElMessage.error('文件不可用,请联系管理员');return;}throw new Error(`下载错误: ${downloadResponse.status}`);}// 4. 获取文件大小并显示进度const contentLength = downloadResponse.headers.get('content-length');const total = parseInt(contentLength, 10);let loaded = 0;const reader = downloadResponse.body.getReader();const chunks = [];const progressNotification = ElNotification({title: '下载进度',message: '0%',duration: 0,type: 'info'});while(true) {const {done, value} = await reader.read();if (done) break;chunks.push(value);loaded += value.length;// 更新下载进度const progress = Math.round((loaded / total) * 100);progressNotification.message = `${progress}%`;}progressNotification.close();// 5. 组装并触发下载const blob = new Blob(chunks);const url = URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = row.operatorName || 'download.zip';document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(url);
}// 轮询文件状态
async function pollFileStatus(fileId, maxAttempts = 10) {for (let attempt = 0; attempt < maxAttempts; attempt++) {const status = await Api.checkFileStatus(fileId);if (status.ready) return true;// 指数退避等待await new Promise(r => setTimeout(r, 1000 * Math.pow(1.5, attempt)));}return false;
}

8. 实际应用案例:批量下载功能改进

在申报系统中,批量下载功能尤为重要。改进后的完整实现:

async function batchDownload(selectedRows) {if (!selectedRows.length) {ElMessage.warning('请选择要下载的文件');return;}// 1. 过滤出可下载的文件const downloadableRows = selectedRows.filter(row => row.compress === 1);const pendingRows = selectedRows.filter(row => row.compress !== 1);// 2. 通知用户if (pendingRows.length) {ElMessage.warning(`${pendingRows.length}个文件正在准备中,将跳过这些文件`);}if (!downloadableRows.length) {ElMessage.warning('没有可下载的文件');return;}// 3. 创建下载进度跟踪const progress = reactive({total: downloadableRows.length,completed: 0,failed: 0});const progressDialog = createProgressDialog(progress);// 4. 并发下载,但限制并发数const concurrentLimit = 3; // 最多同时下载3个文件const downloadQueue = [...downloadableRows];const activeDownloads = new Set();async function processQueue() {if (downloadQueue.length === 0 && activeDownloads.size === 0) {// 所有下载完成progressDialog.close();ElMessage.success(`下载完成:${progress.completed}成功,${progress.failed}失败`);return;}// 填充活跃下载任务,直到达到并发限制while (downloadQueue.length > 0 && activeDownloads.size < concurrentLimit) {const row = downloadQueue.shift();const downloadTask = (async () => {try {await downloadFile(`/${downloadPre}${row.zipUrl}`, row[fileNameKey]);progress.completed++;} catch (error) {console.error('下载失败:', error, row);progress.failed++;} finally {activeDownloads.delete(downloadTask);// 继续处理队列processQueue();}})();activeDownloads.add(downloadTask);}}// 开始处理下载队列processQueue();
}// 创建进度对话框
function createProgressDialog(progress) {// 实现进度对话框显示// ...
}

9. 总结与最佳实践

通过这个实际案例,我们学到了几个重要经验:

  1. 文件状态管理至关重要

    • 在数据模型中明确文件处理状态
    • 前端需根据状态执行不同逻辑
  2. 预签名URL机制需谨慎使用

    • 生成时机应在文件确实可用后
    • 需考虑URL过期情况
    • 要处理S3错误响应
  3. 异步任务与状态同步

    • 大文件处理应异步进行
    • 状态变更需及时通知前端
    • 考虑引入事件驱动架构
  4. 防御性编程不可或缺

    • 前端需检查文件状态
    • 处理各种错误场景
    • 提供友好的用户反馈

上述经验不仅适用于S3预签名URL下载场景,也适用于各种涉及文件处理的企业应用。通过合理的架构设计和状态管理,可以显著提升文件处理功能的可靠性和用户体验。


这个看似简单的XML错误问题,实际上反映了企业应用中状态管理、异步处理、用户体验等多方面的技术挑战。通过深入分析和系统性解决,我们不仅修复了当前问题,也提升了整个应用的架构质量。这正是企业级应用开发中的常见模式:从具体问题出发,寻找全面、可扩展的解决方案。

相关文章:

【企业级Web应用中的文件下载处理:从S3预签名URL到压缩状态管理】

企业级Web应用中的文件下载处理&#xff1a;从S3预签名URL到压缩状态管理 1. 引言&#xff1a;一个看似简单的下载功能背后 在开发企业级Web应用时&#xff0c;文件下载功能看似简单&#xff0c;却常常隐藏着诸多技术挑战。近期&#xff0c;我们在一个xx申报系统项目中&#…...

【新模型速递】PAI一键云上零门槛部署DeepSeek-V3-0324、Qwen2.5-VL-32B

DeepSeek近期推出了“DeepSeek-V3-0324”版本&#xff0c;据测试在数学推理和前端开发方面的表现已优于 Claude 3.5 和 Claude 3.7 Sonnet。 阿里也推出了多模态大模型Qwen2.5-VL的新版本--“Qwen2.5-VL-32B-Instruct”&#xff0c;32B参数量实现72B级性能&#xff0c;通杀图文…...

[原创](Modern C++)现代C++的关键性概念: 如何利用多维数组的指针安全地遍历所有元素

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、C …...

flask开发中设置Flask SQLAlchemy 的 db.Column 只存储非负整数(即 0 或正整数)

如果你想控制一个 Flask SQLAlchemy 的 db.Column 只存储非负整数&#xff08;即 0 或正整数&#xff09;&#xff0c;你可以在模型中使用验证来确保这一点。一种常见的方法是使用模型的 validate 方法或者在执行插入或更新操作时进行检查。 以下是实现这一目标的几种方法&…...

【Elasticsearch基础】基本核心概念介绍

Elasticsearch作为当前最流行的分布式搜索和分析引擎&#xff0c;其强大的功能背后是一套精心设计的核心概念体系。本文将深入解析Elasticsearch的五大核心概念&#xff0c;帮助开发者构建坚实的技术基础&#xff0c;并为高效使用ES提供理论支撑。 1 索引&#xff08;Index&…...

Github 热点项目 awesome-mcp-servers MCP 服务器合集,3分钟实现AI模型自由操控万物!

【今日推荐】超强AI工具库"awesome-mcp-servers"星数破万&#xff01; ① 百宝箱式服务模块&#xff1a;AI能直接操作浏览器、读文件、连数据库&#xff0c;比如让AI助手自动整理Excel表格&#xff0c;三分钟搞定全天报表&#xff1b; ② 跨领域实战利器&#xff1a;…...

SpringMVC 拦截器(Interceptor)

一.拦截器 假设有这么一个场景&#xff0c;一个系统需要用户登录才能进入&#xff0c;在检验完用户的信息后对页面进行了跳转。但是如果我们直接输入跳转的url&#xff0c;可以绕过用户信息校验&#xff08;用户登录&#xff09;&#xff0c;直接进入系统。 因此我们引入了使…...

【NLP】16. NLP推理方法重点回顾 -- 52道多选题

Which of the following problems are commonly solved using sequence tagging? A) Named Entity Recognition (NER) B) Part-of-Speech (POS) Tagging C) Word Embedding Training D) Syntactic Dependency Parsing 序列标注是一种 NLP 任务&#xff0c;常用于 命名实体…...

Redisson分布式锁深度解析:原理与实现机制

Redisson作为Redis Java客户端中的分布式解决方案佼佼者&#xff0c;其分布式锁实现被广泛应用于生产环境。以下从底层设计到源码实现进行全面剖析。 一、核心架构设计 1. 整体架构图 graph LRA[客户端] --> B[RLock接口]B --> C[RedissonLock]C --> D[Redis命令执…...

Linux 系统调用实现机制详解

Linux 系统调用实现机制详解 —— fork()、execve()、waitpid() 内核层面的秘密 在 Linux 内核中&#xff0c;fork()、execve() 和 waitpid() 是构建多任务操作系统的三大基石&#xff0c;它们涉及进程控制、内存管理、文件系统等多个子系统。本文将带你一探它们在 内核层面的…...

责任链模式_行为型_GOF23

责任链模式 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;核心思想是将多个处理请求的对象连成一条链&#xff0c;请求沿链传递直到被处理。它像现实中的“多级审批流程”——请假或报销时&#xff0c;申请会逐级提交给…...

03-SpringBoot3入门-配置文件(自定义配置及读取)

1、自定义配置 # 自定义配置 zbj:user:username: rootpassword: 123456# 自定义集合gfs:- a- b- c2、读取 1&#xff09;User类 package com.sgu.pojo;import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.spring…...

学习记录-软件测试基础

一、软件测试分类 1.按阶段&#xff1a;单元测试&#xff08;一般开发自测&#xff09;、集成测试、系统测试、验收测试 2.按代码可见度测试&#xff1a;黑盒测试、灰盒测试、白盒测试 3.其他&#xff1a;冒烟测试(冒烟测试主要是在开发提测后进行&#xff0c;主要是测试主流…...

【蓝桥杯每日一题】3.28

&#x1f3dd;️专栏&#xff1a; 【蓝桥杯备篇】 &#x1f305;主页&#xff1a; f狐o狸x "今天熬的夜&#xff0c;会变成明天奖状的闪光点&#xff01;" 目录 一、唯一的雪花 题目链接 题目描述 解题思路 解题代码 二、逛画展 题目链接 题目描述 解题思路 解题代…...

优秀的 React 入门开源项目推荐

以下是一些优秀的 React 入门开源项目推荐&#xff0c;涵盖不同应用场景和功能模块&#xff0c;适合学习和实践&#xff1a; 1. Jira Clone 仓库地址&#xff1a;GitHub - oldboyxx/jira_clone 亮点&#xff1a; 基于 React Hooks 实现&#xff0c;模仿 Jira 的任务管理功能。…...

万字长文详解Text-to-SQL

什么是Text-to-SQL 在各个企业数据量暴涨的现在&#xff0c;Text-to-SQL越来越重要了&#xff0c;所以今天就来聊聊Text-to-SQL。 Text-to-SQL是一种将自然语言查询转换为数据库查询的技术。它可以让用户通过自然语言来查询数据库&#xff0c;而不需要编写复杂的SQL语句。 T…...

【Linux】动静态库的制作与使用

一.对软硬链接的补充 1、无法对目录进行硬链接 为什么呢&#xff1f; 首先&#xff0c;我们在访问文件时&#xff0c;每一个文件都会有自己的dentry结构&#xff0c;这些结构会在内存中维护一棵路径树&#xff0c;来快速进行路径查找。但是如果某个节点直接使用硬链接到了根节…...

ubuntu22.04 如何安装 ch341 驱动

前言 本篇是介绍ubuntu22.04如何安装 ch341 驱动&#xff0c;并对其中遇到的问题进行整理。 一、流程 1.1 查看CH340驱动 首先是查看ubuntu22.04系统自带的驱动&#xff0c;用以下命令即可 ls /lib/modules/$(uname -r)/kernel/drivers/usb/serial 然后会跳出以下界面&…...

个人博客网站从搭建到上线教程

步骤1:设计个人网站 设计个人博客网站的风格样式,可以在各个模板网站上多浏览浏览,以便有更多设计网站风格样式的经验。 设计个人博客网站的内容,你希望你的网站包含哪些内容如你的个人基本信息介绍、你想分享的项目、你想分享的技术文档等等。 步骤2:选择开发技术栈 因…...

android 一步完成 aab 安装到手机

家人们谁懂&#xff01;在 Android 系统安装 aab 应用超麻烦。满心期待快速体验&#xff0c;却发现 aab 无法直装&#xff0c;得先转为 apks 格式&#xff0c;这过程复杂易错。好不容易转好&#xff0c;还得安装 apks&#xff0c;一番折腾&#xff0c;时间与耐心全耗尽。别愁&a…...

c#使用forms实现屏幕截图

说明&#xff1a; c#使用forms实现屏幕截图 step1: 点击按钮&#xff0c;拖拽&#xff0c;截图&#xff0c;保存本地 C:\Users\wangrusheng\RiderProjects\WinFormsApp1\WinFormsApp1\Form1.cs using System; using System.Drawing; using System.Drawing.Imaging; using Syst…...

mac m4 Homebrew安装MySQL 8.0

1.使用Homebrew安装MySQL8 在终端中输入以下命令来安装MySQL8&#xff1a; brew install mysql8.0 安装完成后&#xff0c;您可以通过以下命令来验证MySQL是否已成功安装&#xff1a; 2.配置mysql环境变量 find / -name mysql 2>/dev/null #找到mysql的安装位置 cd /op…...

UE5学习笔记 FPS游戏制作26 UE中的UI

文章目录 几个概念创建一个UI蓝图添加UI获取UI的引用 切换设计器和UI蓝图将UI添加到游戏场景锚点轴点slotSizeToContent三种UI数据更新方式(Text、Image)函数绑定属性绑定事件绑定 九宫格分割图片按钮设置图片绑定按下事件 下拉框创建添加数据修改样式常用函数 滚动框创建添加数…...

Navicat导出mysql数据库表结构说明到excel、word,单表导出方式记录

目前只找到一张一张表导出的方式 使用information_schema传入表名查询 字段名根据需要自行删减&#xff0c;一般保留序号、字段名、类型、说明就行 SELECT COLUMNS.ORDINAL_POSITION AS 序号, COLUMNS.COLUMN_NAME AS 字段名, COLUMNS.COLUMN_TYPE AS 类型(长度), COLUMNS.N…...

目标检测 AP 计算 实例 python

以下是使用 Python 实现目标检测中 ‌Average Precision (AP)‌ 计算的完整实例&#xff0c;包含代码和注释。这里以 ‌Pascal VOC 标准‌ 为例&#xff08;IoU阈值0.5&#xff09;。 步骤1&#xff1a;准备数据 假设&#xff1a; gt_boxes: 真实标注框列表&#xff0c;格式为 …...

HarmonyOS NEXT图形渲染体系:重新定义移动端视觉体验

一、革命性架构设计 1.1 多线程并行渲染引擎 HarmonyOS NEXT通过四级流水线并行架构实现渲染效率质的飞跃&#xff0c;其核心包含&#xff1a; 优先级任务调度器&#xff1a;动态分配紧急渲染任务&#xff08;如手势反馈&#xff09;与常规任务智能线程池管理&#xff1a;根…...

使用 Docker 18 安装 Eureka:解决新版本 Docker 不支持的问题

使用 Docker 18 安装 Eureka&#xff1a;解决新版本 Docker 不支持的问题 在微服务架构中&#xff0c;Eureka 是一个常用的注册中心&#xff0c;用于服务发现和管理。然而&#xff0c;随着 Docker 版本的更新&#xff0c;一些新版本的 Docker 对 Eureka 的支持并不友好。如果你…...

Linux驱动开发 中断处理

目录 序言 1.中断的概念 2.如何使用中断 中断处理流程 中断上下文限制 屏蔽中断/使能 关键区别与选择 上半部中断 下半部中断 软中断&#xff08;SoftIRQ&#xff09; 小任务(Tasklet) 工作队列&#xff08;Workqueue&#xff09; 线程 IRQ&#xff08;Threaded IRQ…...

Centos主机检查脚本

使用方法&#xff1a; 将脚本保存为 CentOS_syscheck.sh 添加执行权限&#xff1a; chmod x CentOS_syscheck.sh 执行脚本&#xff1a; ./CentOS_syscheck.sh #!/bin/bash# 设置颜色变量 RED\033[0;31m GREEN\033[0;32m YELLOW\033[0;33m BLUE\033[0;34m NC\033[0m # 重置…...

python系统之综合案例:用python打造智能诗词生成助手

不为失败找理由&#xff0c;只为成功找方法。所有的不甘&#xff0c;因为还心存梦想&#xff0c;所以在你放弃之前&#xff0c;好好拼一把&#xff0c;只怕心老&#xff0c;不怕路长。 python系列之综合案例 前言一、项目描述二、项目需求三、 项目实现1、开发准备2、代码实现 …...