随笔20250530 C# 整合 IC卡读写技术解析与实现

article/2025/8/7 15:47:10

以下是一个完整、最简化的 FeliCa 读取整合示例(无需 SDK,基于 PCSC NuGet 包),你可以直接运行这个控制台程序,验证能否识别 RC-S300 并读取卡片 UID:


🧪 示例说明

  • 📦 使用 NuGet 包 PCSC

  • 🎯 功能:初始化读卡器,读取插卡的 UID(部分 FeliCa 卡支持)

  • ✅ 仅依赖 Windows 驱动(无需 SDK)


🛠 使用方式

1. 创建控制台项目

dotnet new console -n FelicaReaderExample
cd FelicaReaderExample

2. 安装依赖包

dotnet add package PCSC

 

直接选第一个宝贝!!! 

3. 替换 Program.cs

将下载的 FelicaReaderExample.cs 文件内容,覆盖 Program.cs

或直接复制粘贴内容。

4. 运行程序

插入 FeliCa 卡到 RC-S300,然后运行:

dotnet run

✅ 成功输出示例

找到读卡器:Sony RC-S300
读取成功,卡片 UID: 01-23-45-67-89-AB-CD

FelicaReaderService.cs 

using System;
using System.Linq;
using System.Threading.Tasks;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{/// <summary>/// 提供对 FeliCa 卡(如 ICOCA)的读取功能,仅获取 UID(IDm)/// </summary>public class FelicaReaderService{/// <summary>/// 读取 FeliCa 卡的唯一识别码(IDm / UID)/// </summary>/// <returns>UID 字符串 或 错误信息</returns>public async Task<string?> ReadCardUidAsync(){try{// 1. 建立与 PC/SC 子系统的连接上下文using var context = ContextFactory.Instance.Establish(SCardScope.System);// 2. 获取所有已连接的智能卡读卡器列表var readerNames = context.GetReaders();if (readerNames == null || readerNames.Length == 0)return "未检测到任何读卡器。";var readerName = readerNames[0];// 3. 使用第一个读卡器进行连接using var reader = new SCardReader(context);var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result != SCardError.Success)return $"连接失败: {SCardHelper.StringifyError(result)}";// 4. 构造 APDU 指令读取 UID(IDm)// 标准 PC/SC 指令: FF CA 00 00 00var command = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 };var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[256];// 5. 发送指令并接收响应var transmitResult = reader.Transmit(sendPci,        // 协议控制结构command,        // 发送的指令receivePci,     // 接收的控制结构ref receiveBuffer // 输出缓冲区);if (transmitResult != SCardError.Success)return $"发送失败: {SCardHelper.StringifyError(transmitResult)}";// 6. 解析返回的 UID(通常为 8 字节的 IDm)var uid = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (uid.Length == 0)return "未读取到卡片 UID";return $"UID: {BitConverter.ToString(uid)}";}catch (Exception ex){return $"异常: {ex.Message}";}}}
}

 FelicaReaderTest.razor

@page "/felicareadertest"
@inject StarMauiPrinter.Services.FelicaReaderService FelicaReaderService<h3>ICOCA UID を取得する</h3><button class="btn btn-primary" @onclick="ReadUid">取得</button>
<p>@uid</p>@code {private string? uid;private async Task ReadUid(){try{uid = await FelicaReaderService.ReadCardUidAsync();}catch (Exception ex){uid = $"読み取りに失敗しました: {ex.Message}";}}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{/// <summary>/// Felica读卡器服务类,用于读取ICOCA等Felica格式的IC卡信息/// </summary>public class FelicaReaderService{#region 常量定义/// <summary>/// 获取UID的APDU命令/// </summary>private static readonly byte[] GET_UID_COMMAND = { 0xFF, 0xCA, 0x00, 0x00, 0x00 };/// <summary>/// 接收缓冲区大小/// </summary>private const int RECEIVE_BUFFER_SIZE = 256;/// <summary>/// 最大重试次数/// </summary>private const int MAX_RETRY_COUNT = 3;/// <summary>/// 重试延迟基数(毫秒)/// </summary>private const int RETRY_DELAY_BASE = 500;#endregion#region 公共方法/// <summary>/// 异步读取卡片UID(带重试机制)/// </summary>/// <returns>返回UID字符串,如果失败则返回错误信息</returns>public async Task<string> ReadCardUidAsync(){return await ExecuteWithRetryAsync(async () =>{// 方案1:直接创建SCardContext实例using var context = new SCardContext();context.Establish(SCardScope.System);// 获取可用读卡器var readerName = GetFirstAvailableReader(context);// 连接读卡器并读取UIDusing var reader = new SCardReader(context);ConnectToCard(reader, readerName);var uid = ReadCardUid(reader);return FormatUid(uid);});}/// <summary>/// 简化版:异步读取ICOCA卡的详细信息/// </summary>/// <returns>返回包含UID、余额和基本信息的字符串</returns>public async Task<string> ReadIcocaDetailsAsync(){return await ExecuteWithRetryAsync(async () =>{using var context = new SCardContext();context.Establish(SCardScope.System);var readerName = GetFirstAvailableReader(context);using var reader = new SCardReader(context);ConnectToCard(reader, readerName);// 读取基本UID信息var uid = ReadCardUid(reader);var uidString = FormatUid(uid);// 尝试读取余额var balance = TryReadBalance(reader);// 尝试读取最近一次交易var lastTransaction = TryReadLastTransaction(reader);return $"ICOCA卡信息:\n" +$"UID: {uidString}\n" +$"余额: {balance}\n" +$"最近交易: {lastTransaction}";});}/// <summary>/// 尝试读取余额(修复版)/// </summary>private string TryReadBalance(SCardReader reader){try{// ICOCA余额读取命令var command = new byte[] { 0xFF, 0xCA, 0x00, 0x01, 0x04, 0x8B, 0x00, 0x83, 0x00 };var response = new byte[64];// 修复:正确使用ref参数var result = reader.Transmit(command, ref response);// 检查命令是否成功执行if (result == SCardError.Success){// 检查响应长度var actualLength = response.Length;if (actualLength >= 4){// 正确解析余额数据var balance = (response[1] << 8) | response[0];  // Little-endianif (balance > 0 && balance < 50000){return $"¥{balance}";}// 尝试其他可能的位置for (int i = 0; i < actualLength - 1; i++){var testBalance = (response[i + 1] << 8) | response[i];if (testBalance > 0 && testBalance < 50000){return $"¥{testBalance}";}}}}else{return $"命令失败: {result}";}}catch (Exception ex){return $"读取失败: {ex.Message}";}return "无法读取";}/// <summary>/// 尝试读取最近一次交易(修复版)/// </summary>private string TryReadLastTransaction(SCardReader reader){try{// 交易记录读取命令var command = new byte[] { 0xFF, 0xCA, 0x00, 0x02, 0x04, 0x8C, 0x00, 0x80, 0x00 };var response = new byte[64];// 修复:正确使用ref参数var result = reader.Transmit(command, ref response);if (result == SCardError.Success && response.Length >= 8){var amount = (response[5] << 8) | response[4];var type = response[0] == 0x05 ? "乘车" : "其他";if (amount > 0 && amount < 10000) // 合理的交易金额范围{return $"{type} ¥{amount}";}}}catch (Exception){// 忽略错误}return "无记录";}/// <summary>/// 检查读卡器状态/// </summary>/// <returns>返回读卡器状态信息</returns>public async Task<string> CheckReaderStatusAsync(){return await Task.Run(() =>{try{// 修复:直接创建SCardContext实例using var context = new SCardContext();context.Establish(SCardScope.System);var readerNames = context.GetReaders();if (readerNames.Length == 0)return "状态: 未检测到任何读卡器";var statusInfo = new List<string>();foreach (var readerName in readerNames){try{// 检查读卡器中是否有卡var cardStatus = CheckCardPresence(context, readerName);statusInfo.Add($"读卡器: {readerName} - 状态: {cardStatus}");}catch (Exception ex){statusInfo.Add($"读卡器: {readerName} - 状态: 检查失败 ({ex.Message})");}}return string.Join("\n", statusInfo);}catch (Exception ex){return $"检查状态时发生异常: {ex.Message}";}});}/// <summary>/// 测试读卡器连接/// </summary>/// <returns>连接测试结果</returns>public async Task<string> TestReaderConnectionAsync(){return await Task.Run(() =>{try{using var context = ContextFactory.Instance.Establish(SCardScope.System);var readerNames = context.GetReaders();if (readerNames.Length == 0)return "测试失败: 未检测到任何读卡器";var readerName = readerNames[0];using var reader = new SCardReader(context);var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result == SCardError.Success){var protocol = reader.ActiveProtocol;return $"连接测试成功!\n读卡器: {readerName}\n协议: {protocol}";}else{return $"连接测试失败: {SCardHelper.StringifyError(result)}";}}catch (Exception ex){return $"连接测试异常: {ex.Message}";}});}#endregion#region 私有方法/// <summary>/// 获取第一个可用的读卡器/// </summary>/// <param name="context">PC/SC上下文</param>/// <returns>读卡器名称</returns>/// <exception cref="InvalidOperationException">当没有可用读卡器时抛出</exception>private string GetFirstAvailableReader(SCardContext context){var readerNames = context.GetReaders();if (readerNames.Length == 0)throw new InvalidOperationException("未检测到任何读卡器,请确认读卡器已正确连接");return readerNames[0];}/// <summary>/// 连接到卡片/// </summary>/// <param name="reader">读卡器实例</param>/// <param name="readerName">读卡器名称</param>/// <exception cref="InvalidOperationException">当连接失败时抛出</exception>private void ConnectToCard(SCardReader reader, string readerName){var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result != SCardError.Success){throw new InvalidOperationException($"连接到读卡器失败: {SCardHelper.StringifyError(result)}。" +"请确认卡片已正确放置在读卡器上");}}/// <summary>/// 读取卡片UID/// </summary>/// <param name="reader">读卡器实例</param>/// <returns>UID字节数组</returns>/// <exception cref="InvalidOperationException">当读取失败时抛出</exception>private byte[] ReadCardUid(SCardReader reader){var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[RECEIVE_BUFFER_SIZE];var transmitResult = reader.Transmit(sendPci,GET_UID_COMMAND,receivePci,ref receiveBuffer);if (transmitResult != SCardError.Success){throw new InvalidOperationException($"发送UID读取命令失败: {SCardHelper.StringifyError(transmitResult)}");}// 提取有效的UID数据(去除填充的0x00)var uid = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (uid.Length == 0 || uid.Length < 4){throw new InvalidOperationException("读取到的UID无效或长度不足");}return uid;}/// <summary>/// 尝试读取额外的卡片信息/// </summary>/// <param name="reader">读卡器实例</param>/// <returns>额外信息字符串</returns>private string TryReadAdditionalCardInfo(SCardReader reader){try{// 尝试读取更多信息的示例命令var infoCommand = new byte[] { 0xFF, 0xB0, 0x00, 0x00, 0x10 };var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[RECEIVE_BUFFER_SIZE];var result = reader.Transmit(sendPci,infoCommand,receivePci,ref receiveBuffer);if (result == SCardError.Success){var responseData = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (responseData.Length > 0){return $"附加信息: {BitConverter.ToString(responseData)}";}}return "附加信息: 无法读取或不支持";}catch (Exception ex){return $"附加信息: 读取异常 ({ex.Message})";}}/// <summary>/// 检查指定读卡器中是否有卡片/// </summary>/// <param name="context">PC/SC上下文</param>/// <param name="readerName">读卡器名称</param>/// <returns>卡片状态描述</returns>private string CheckCardPresence(SCardContext context, string readerName){try{using var reader = new SCardReader(context);var connectResult = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (connectResult == SCardError.Success){return "有卡 (已连接)";}else if (connectResult == SCardError.NoSmartcard){return "无卡";}else{return $"未知状态 ({SCardHelper.StringifyError(connectResult)})";}}catch (Exception){return "检查失败";}}/// <summary>/// 格式化UID为可读字符串/// </summary>/// <param name="uid">UID字节数组</param>/// <returns>格式化的UID字符串</returns>private string FormatUid(byte[] uid){if (uid == null || uid.Length == 0)return "UID: 无效";var uidString = BitConverter.ToString(uid).Replace("-", ":");return $"UID: {uidString} (长度: {uid.Length} 字节)";}/// <summary>/// 执行带重试机制的异步操作/// </summary>/// <param name="operation">要执行的操作</param>/// <returns>操作结果</returns>private async Task<string> ExecuteWithRetryAsync(Func<Task<string>> operation){Exception lastException = null;for (int attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++){try{return await operation();}catch (Exception ex){lastException = ex;if (attempt == MAX_RETRY_COUNT)break;// 在重试之间添加递增延迟await Task.Delay(RETRY_DELAY_BASE * attempt);}}// 如果所有重试都失败,返回错误信息return $"操作失败 (重试 {MAX_RETRY_COUNT} 次): {lastException?.Message ?? "未知错误"}";}#endregion#region 资源清理/// <summary>/// 清理资源/// </summary>public void Dispose(){// 强制垃圾回收,释放未管理资源GC.Collect();}#endregion}#region 扩展类和数据结构/// <summary>/// ICOCA卡信息结构/// </summary>public class IcocaCardInfo{public string Uid { get; set; } = string.Empty;public int Balance { get; set; }public DateTime LastUsed { get; set; }public List<TransactionRecord> History { get; set; } = new List<TransactionRecord>();}/// <summary>/// 交易记录结构/// </summary>public class TransactionRecord{public DateTime Date { get; set; }public string Type { get; set; } = string.Empty;public int Amount { get; set; }public string Station { get; set; } = string.Empty;}#endregion
}

 


using System;
using System.Linq;
using System.Threading.Tasks;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{public class FelicaReaderService{/// <summary>/// 获取卡的 IDm(UID)、System Code,并根据 IDm 前缀推断卡类型/// </summary>public async Task<string?> GetCardInfoAsync(){try{using var context = ContextFactory.Instance.Establish(SCardScope.System);var readers = context.GetReaders();if (readers == null || readers.Length == 0)return "未检测到读卡器";var readerName = readers[0];using var reader = new SCardReader(context);var conn = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (conn != SCardError.Success)return $"连接失败: {SCardHelper.StringifyError(conn)}";var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var recvPci = new SCardPCI();var buffer = new byte[256];// 发送 Polling 命令以获取卡信息var polling = new byte[] {0xFF, 0x00, 0x00, 0x00, 0x04,0xD4, 0x04, 0x00, 0x01};var result = reader.Transmit(sendPci, polling, recvPci, ref buffer);if (result != SCardError.Success)return $"轮询失败: {SCardHelper.StringifyError(result)}";var response = buffer.TakeWhile(b => b != 0x00).ToArray();if (response.Length < 18)return "响应数据不足,无法解析卡片信息";var idm = response.Skip(9).Take(8).ToArray();var idmStr = BitConverter.ToString(idm);var idPrefix = $"{idm[0]:X2}-{idm[1]:X2}";var systemCode = response.Length >= 27 ? response.Skip(25).Take(2).ToArray() : new byte[] { 0x00, 0x00 };var sysCodeStr = $"{systemCode[0]:X2}{systemCode[1]:X2}";// 根据 IDm 前缀 和 System Code 判断卡种string cardType = sysCodeStr switch{"0003" => idPrefix switch{"01-01" or "02-01" => "Suica","03-01" => "PASMO","07-01" or "07-02" => "ICOCA",_ => "未知交通卡"},"FE00" or "FE01" => "Edy","8B01" => "WAON",_ => $"未知卡片(SystemCode: {sysCodeStr}, IDm前缀: {idPrefix})"};return $"卡类型: {cardType}\nIDm: {idmStr}\nSystem Code: {sysCodeStr}";}catch (Exception ex){return $"异常: {ex.Message}";}}}
}


http://www.hkcw.cn/article/rCkHHpteEU.shtml

相关文章

day024-网络基础-TCP与UDP、DNS

文章目录 1. 李导推荐书籍2. OSI七层模型2.1 传输层2.2 网络层2.2.1 问&#xff1a;两端处于不同局域网的设备怎么网络通信&#xff1f; 2.3 数据链路层2.4 物理层2.5 图解OSI七层模型 3. 数据传输模式3.1 全双工3.2 半双工3.3 单工 4. TCP 3次握手4.1 抓包 5. TCP 4次挥手5.1 …

AI赋能开源:如何借助MCP快速解锁开源项目并提交你的首个PR

引子 很多同学都梦想为开源项目贡献力量&#xff0c;然而现实往往是——面对庞大复杂的项目&#xff0c;从入门到提交第一个有实质性代码的PR&#xff0c;时间跨度可能长达数年。传统路径通常是先从文档贡献开始&#xff0c;逐步深入理解项目架构&#xff0c;最终才能进行代码…

智能问数技术路径对比:NL2SQL vs NL2Semantic2SQL

在人工智能浪潮席卷数据分析领域的当下&#xff0c;“智能问数”凭借其自然语言交互的便捷性&#xff0c;迅速成为企业提升数据民主化与决策效率的焦点。大语言模型&#xff08;LLM&#xff09;展现出的强大语言理解和生成能力&#xff0c;无疑为这一愿景启动了引擎。 然而&am…

QT-Creator安装教程(windows)

目录 1&#xff0c;下载 1.1 镜像源下载 1.2 运行下载的exe文件 1.2.1 QT5 版本安装 1.2.2 QT6 版本安装 1.2.3 如何在安装完成之后&#xff0c;继续添加扩展包 1&#xff0c;下载 1.1 镜像源下载 地址&#xff1a;Index of /qtproject/ 根据电脑系统选择下载linux、macO…

Warm-Flow发布1.7.3 端午节(设计器流和流程图大升级)

Warm-Flow发布1.7.3 端午节&#xff08;设计器流和流程图大升级&#xff09; 更新内容项目介绍功能思维导图演示地址官网Warm-Flow视频 更新内容 [feat] 新版流程图通过前端渲染[perf] 美化流程设计器ui[feat] 办理人权限处理器&#xff0c;新增办理人转换接口&#xff0c;比如…

分布式锁Redisson使用

redission为我们提供了方便使用redis集群的方法&#xff0c;可以使用它完成锁的建立。 依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.36.0</version></dependency>spring引…

Unity UI系统中RectTransform详解

一、基础代码示例 public GameObject node; var rect node.GetComponent<RectTransform>();Debug.Log($"anchoredPosition----{rect.anchoredPosition}"); Debug.Log($"offsetMin.x--{rect.offsetMin}"); Debug.Log($"offsetMax.x--{rect.of…

神经网络(Neural Networks)

设计神经网络的最初动机是编写能够模仿人类大脑学习和思考方式的软件。现今&#xff0c;神经网络也被称为人工神经网络&#xff0c;其工作方式已经与我们所认为的大脑实际工作方式和学习方式大不相同。 研究神经网络的工作始于 20 世纪 50 年代&#xff0c;之后的一段时间它并不…

DeepSeek‑R1-0528 重磅升级:蚂蚁百宝箱免费、无限量调用

DeepSeek‑R1-0528 重磅升级&#xff1a;蚂蚁百宝箱免费、无限量调用 端午假期前一天&#xff0c;DeepSeek‑R1 更新到了 0528 版本&#xff01; 官方说明&#xff1a;0528 版本在深度思考与推理能力方面显著增强——在数学、编程与通用逻辑等多项基准测评中&#xff0c;表现已…

可定制化货代管理系统,适应不同业务模式需求!

在全球化贸易的浪潮下&#xff0c;货运代理行业扮演着至关重要的角色。然而&#xff0c;随着市场竞争的日益激烈&#xff0c;货代企业面临着越来越多的挑战&#xff1a;客户需求多样化、业务流程复杂化、运营成本上升、利润空间压缩……这些挑战迫使货代企业不断寻求创新和突破…

使用基于Xsens惯性传感器的动作捕捉技术测量人体工程学

由于单调和片面的体力消耗&#xff0c;牙科领域的从业者患肌肉骨骼疾病 (MSD) 的几率很高。惯性测量单元 (IMU) 越来越成为评估工作姿势风险的焦点。因此&#xff0c;本研究旨在使用基于惯性传感器的运动捕捉 (MoCap) 评估人体工程学讲座和培训干预对牙科助理学生的姿势风险和M…

设计模式之结构型:桥接模式

桥接模式(Bridge Pattern) 定义 桥接模式是一种​​结构型设计模式​​&#xff0c;通过​​将抽象部分与实现部分分离​​&#xff0c;使它们可以独立变化。它通过组合代替继承&#xff0c;解决多层继承导致的类爆炸问题&#xff0c;适用于​​多维度变化​​的场景(如形状与颜…

Spring Boot 3 整合 MQ 构建聊天消息存储系统

引子 在构建实时聊天服务时&#xff0c;我们既要保证消息的即时传递&#xff0c;又需要对消息进行持久化存储以便查询历史记录。然而&#xff0c;直接同步写入数据库在高并发场景下容易成为性能瓶颈&#xff0c;影响消息的实时性。秉承"没有什么问题是加一层解决不了的&q…

0-EATSA-GNN:基于图节点分类师生机制的边缘感知和两阶段注意力增强图神经网络(code)

code:https://github.com/afofanah/EATSA-GNN. 文章目录 Abstract1. Introduction1.1.动态图场景1.2.EATSA-GNN框架的背景化2. Background2.1.GNN边缘感知挑战2.2.GNN的可解释性问题2.3.EATSA-GNN可解释性3. Related worksAbstract 图神经网络(GNNs)从根本上改变了我们处理和…

解决开发者技能差距:AI 在提升效率与技能培养中的作用

企业在开发者人才方面正面临双重挑战。一方面&#xff0c;IDC 预测&#xff0c;到2025年&#xff0c;全球全职开发者将短缺400万人&#xff1b;另一方面&#xff0c;一些行业巨头已暂停开发者招聘&#xff0c;转而倚重人工智能&#xff08;AI&#xff09;来满足开发需求。这不禁…

内存池学习(一)

一、内存池 1、内存池所使用的内存是什么内存&#xff1f; 指的是虚拟内存&#xff08;堆空间&#xff09;&#xff0c;而不是物理内存 2、为什么会有内存池&#xff1f; 一个系统或者程序长期运行&#xff0c;突然会coredump掉&#xff0c;并且程序又频繁地分配和释放内存…

【TTS】基于GRPO的流匹配文本到语音改进:F5R-TTS

论文地址&#xff1a;https://arxiv.org/abs/2504.02407v3 摘要 我们提出了F5R-TTS&#xff0c;这是一种新颖的文本到语音(TTS)系统&#xff0c;它将群体相对策略优化(GRPO)集成到基于流匹配的架构中。 通过将流匹配TTS的确定性输出重新表述为概率高斯分布&#xff0c;我们的方…

现代密码学入门 | 现代密码学核心特点介绍

在当今互联互通的世界中&#xff0c;数字数据在全球范围内不断流动&#xff0c;安全通信和数据保护的需求从未如此迫切。现代密码学作为数字防御的先锋&#xff0c;提供了一系列复杂的技术和算法&#xff0c;以保护信息免受窥探和恶意行为的侵害。 现代密码学是从其古典前身—…

基于原生JavaScript前端和 Flask 后端的Todo 应用

Demo地址&#xff1a;https://gitcode.com/rmbnetlife/todo-app-js-flask.git Python Todo 应用 这是一个使用Python Flask框架开发的简单待办事项(Todo)应用&#xff0c;采用前后端分离架构。本项目实现了待办事项的添加、删除、状态切换等基本功能&#xff0c;并提供了直观…

【Linux 学习计划】-- 命令行参数 | 环境变量

目录 命令行参数 环境变量 环境变量的本质是什么&#xff1f; 相关配置文件 修改环境变量的相关操作 代码获取env —— environ 内建命令 结语 命令行参数 试想一下&#xff0c;我们的main函数&#xff0c;也是一个函数&#xff0c;那么我们的main函数有没有参数呢&am…