AI编程(二十四):个人档案管理器 — 阶段2毕业作品

作者:

在

证件、账号、联系方式——把这一路学的所有东西,装进一个真正属于你的程序

一、你有多少”重要信息”散落在各处?

先别写代码,盘点一下:

  • 身份证号、护照号、驾照号——平时用不到,用到时找不到
  • 各种账号:邮箱、视频网站、购物网站、健身房卡号……
  • 家人的身份证号(订机票要用)
  • 银行卡号、社保号、保单号
  • 重要日期:签证过期、证件到期、保修截止

这些信息有个共同点:不用时想不起,要用时找不到。翻聊天记录、翻抽屉、问家人——每次都要折腾半天。

手机备忘录?可以,但查找麻烦、没法分类、明文摆着还不安全。

今天,我们自己做一个个人档案管理器。它是阶段2的收官之作——这一篇会用上你学过的所有知识点:字典列表、增删改查、文件读写、错误处理、函数、搜索、统计。没有一个新知识,但组合起来,就是一个真正能用的软件。

这也是编程最有魅力的时刻:你不再是在”练习知识点”,而是在”造工具”。

二、功能设计

一个称职的档案管理器需要:

  • ➕ 添加一条档案(比如”身份证”)
  • 📋 查看所有档案
  • 🔍 搜索档案(输”身份”能找到”身份证”)
  • ✏️ 修改档案(信息变了)
  • 🗑️ 删除档案(不要了)
  • 📁 分类管理(证件/账号/金融/其他)
  • 💾 保存到文件,下次打开还在
  • 🔐 加密存储(改进一)
  • 📊 导出报表(改进三)

一眼看过去全是熟面孔——第十二篇价格工具、第十七篇成绩管理器、第十八篇待办事项工具,都是这个”增删改查+文件”的框架。今天在这个框架上,加两样新花样:加密和报表。

三、数据结构:一条档案长什么样

{
    "名称": "身份证",
    "类别": "证件",
    "号码": "110101199001011234",
    "备注": "2030年到期"
}

一个字典一条档案,所有档案装一个列表。类别用”证件/账号/金融/其他”四种。

档案列表 = []    # 列表装字典——你的看家本领了

四、先跟AI聊

你的提示词:

用Python写一个个人档案管理器(命令行,中文界面)。管理个人信息如证件号、账号等。每条档案包含:名称、类别(证件/账号/金融/其他)、号码、备注。功能:添加、查看全部、按名称搜索、修改、删除、按分类查看,数据保存到JSON文件、启动自动加载。用列表存档案、每条是字典,各功能封装成函数,主循环显示菜单。代码加详细中文注释。

五、主体代码

import json

文件名 = "我的档案.json"
档案列表 = []           # 全部档案:列表装字典
类别表 = ["证件", "账号", "金融", "其他"]   # 固定的四个类别

# ============ 文件操作 ============

def 加载档案():
    """启动时从文件读取档案"""
    global 档案列表
    try:
        with open(文件名, "r", encoding="utf-8") as f:
            档案列表 = json.load(f)
        print(f"📂 已加载 {len(档案列表)} 条档案")
    except FileNotFoundError:
        print("📭 没有找到档案文件,从空档案开始")
    except json.JSONDecodeError:
        print("⚠️ 文件损坏,从空档案开始(原文件已备份为.bak)")
        import os
        os.rename(文件名, 文件名 + ".bak")    # 坏文件改名留证,不直接删

def 保存档案():
    """把全部档案写入文件"""
    with open(文件名, "w", encoding="utf-8") as f:
        json.dump(档案列表, f, ensure_ascii=False, indent=2)
    print(f"💾 已保存 {len(档案列表)} 条档案")

# ============ 添加 ============

def 选类别():
    """显示类别菜单,返回用户选的类别名"""
    for i, 名称 in enumerate(类别表, 1):
        print(f"  {i}. {名称}")
    选择 = input("选择类别(输入数字):").strip()
    try:
        return 类别表[int(选择) - 1]
    except (ValueError, IndexError):
        print("无效选择,归入'其他'")
        return "其他"

def 添加档案():
    """添加一条新档案"""
    名称 = input("档案名称(如:身份证):").strip()
    if not 名称:
        print("❌ 名称不能为空")
        return
    # 查重:名称不能重复
    for 条 in 档案列表:
        if 条["名称"] == 名称:
            print(f"❌ 已经有叫"{名称}"的档案了,换个名字或去修改")
            return

    类别 = 选类别()
    号码 = input("号码/内容:").strip()
    if not 号码:
        print("❌ 号码不能为空")
        return
    备注 = input("备注(可留空):").strip()

    新档案 = {"名称": 名称, "类别": 类别, "号码": 号码, "备注": 备注}
    档案列表.append(新档案)
    print(f"✅ 已添加:{名称}")

# ============ 查看 ============

def 显示档案表(若干条):
    """把一批档案打印成表格(供多个功能复用)"""
    if not 若干条:
        print("📭 (空)")
        return
    print(f"n{'名称':<12}{'类别':<8}{'号码':<22}{'备注'}")
    print("-" * 56)
    for 条 in 若干条:
        print(f"{条['名称']:<12}{条['类别']:<8}{条['号码']:<22}{条['备注']}")
    print(f"共 {len(若干条)} 条")

def 查看全部():
    """显示所有档案"""
    显示档案表(档案列表)

def 按分类查看():
    """只看某一类的档案"""
    类别 = 选类别()
    筛出的 = [条 for 条 in 档案列表 if 条["类别"] == 类别]
    print(f"n【{类别}】类档案:")
    显示档案表(筛出的)

# ============ 搜索 ============

def 搜索档案():
    """按名称或备注模糊搜索"""
    关键字 = input("输入关键字:").strip()
    if not 关键字:
        return
    结果 = [条 for 条 in 档案列表
            if 关键字 in 条["名称"] or 关键字 in 条["备注"]]
    print(f"n搜索"{关键字}"的结果:")
    显示档案表(结果)

# ============ 修改 ============

def 按名称找(名称):
    """辅助函数:按名称找档案,找到返回字典,找不到返回None"""
    for 条 in 档案列表:
        if 条["名称"] == 名称:
            return 条
    return None

def 修改档案():
    """修改一条档案的信息"""
    名称 = input("要修改哪条档案的名称:").strip()
    条 = 按名称找(名称)
    if 条 is None:
        print(f"❌ 找不到"{名称}"")
        return

    print(f"当前信息:{条}")
    print("直接回车表示该项不改n")

    新号码 = input(f"新号码(现:{条['号码']}):").strip()
    if 新号码:
        条["号码"] = 新号码

    新备注 = input(f"新备注(现:{条['备注']}):").strip()
    if 新备注:
        条["备注"] = 新备注

    print(f"✅ 已更新:{名称}")

# ============ 删除 ============

def 删除档案():
    """删除一条档案(需二次确认)"""
    名称 = input("要删除哪条档案:").strip()
    条 = 按名称找(名称)
    if 条 is None:
        print(f"❌ 找不到"{名称}"")
        return

    print(f"将要删除:{条}")
    确认 = input("⚠️ 确认删除?此操作不可恢复!(输入yes):").strip()
    if 确认 == "yes":
        档案列表.remove(条)          # 按内容删除列表元素
        print(f"🗑️ 已删除:{名称}")
    else:
        print("取消删除")

# ============ 主菜单 ============

def 主菜单():
    加载档案()
    菜单项 = {
        "1": ("添加档案", 添加档案),
        "2": ("查看全部", 查看全部),
        "3": ("按分类查看", 按分类查看),
        "4": ("搜索档案", 搜索档案),
        "5": ("修改档案", 修改档案),
        "6": ("删除档案", 删除档案),
        "7": ("保存", 保存档案),
        "0": ("退出", None)
    }

    while True:
        print("n" + "=" * 28)
        print("  🗂️ 个人档案管理器")
        print("=" * 28)
        for 编号, (名称, _) in 菜单项.items():
            print(f"  {编号}. {名称}")

        选择 = input("请选择:").strip()

        if 选择 == "0":
            保存档案()
            print("👋 再见!档案已保存。")
            break
        elif 选择 in 菜单项:
            菜单项[选择][1]()       # 取出函数并调用
        else:
            print("❌ 无效选择")

主菜单()

六、这段代码里的”毕业知识点”

大部分你都见过,这里挑三个设计点讲:

6.1 字典装函数:菜单的高级写法

菜单项 = {
    "1": ("添加档案", 添加档案),
    ...
}
elif 选择 in 菜单项:
    菜单项[选择][1]()       # 取出函数并调用

以前的菜单是一长串if 选择 == "1": 添加档案()。现在把函数本身存进字典,菜单项["1"][1]取出的就是添加档案这个函数,加个()就调用它。

函数可以像数字、文字一样传来传去——第七篇埋下的伏笔,今天用上了。这种写法的好处:菜单显示和功能执行用同一份数据,加功能只需往字典加一行。

6.2 辅助函数:按名称找

def 按名称找(名称):
    for 条 in 档案列表:
        if 条["名称"] == 名称:
            return 条
    return None

修改和删除都要”先找到再操作”,那就把”找”抽成一个函数,两处调用。一个功能只写一遍——第七篇函数的核心价值。注意两个return:找到返回那条字典,找不到返回None。调用方用is None判断找没找到。

6.3 删除要确认

确认 = input("⚠️ 确认删除?此操作不可恢复!(输入yes):").strip()
if 确认 == "yes":

要求用户明确输入”yes”而不是按y——危险操作要提高确认门槛。删错档案可能损失重要信息,宁可让用户多敲三个字母。真实软件里”请输入DELETE确认删除”就是这个思路。

6.4 文件损坏的处理

except json.JSONDecodeError:
    import os
    os.rename(文件名, 文件名 + ".bak")

文件坏了不是简单地”从空开始”——那样原数据就没了。先把它改名备份成.bak,再从空开始。万一只是小问题,还能手动修复备份文件。永远不要静默销毁用户数据。

七、运行试试

python 档案管理器.py
📂 已加载 0 条档案

添加几条:身份证、常用邮箱、健身卡、医保卡。然后试试搜索(输”卡”能搜到健身卡和医保卡)、按分类查看、修改、删除。

主体完成。现在开始加三个真正的亮点功能。

八、第一个改进:简单加密 — 文件里不存明文

打开我的档案.json一看:身份证号明晃晃躺在里面。这不行——文件被别人看到就全泄露了。

专业的加密很复杂,但我们可以做一个够用的简单版:把每个字符的编码”挪一挪”(凯撒密码的思路)。

def 加密文本(文本, 密钥=7):
    """把文字打乱:每个字符的编码加上密钥"""
    结果 = ""
    for 字 in 文本:
        结果 += chr(ord(字) + 密钥)    # 每个字"往后挪7位"
    return 结果

def 解密文本(文本, 密钥=7):
    """把打乱的文字还原:编码减去密钥"""
    结果 = ""
    for 字 in 文本:
        结果 += chr(ord(字) - 密钥)
    return 结果
  • ord(字):字符→数字。ord("A")是65
  • chr(数字):数字→字符。chr(65)是”A”
  • 加密就是每个字符+7,解密就是每个字符-7。”abc”加密成”hij”,解密回来还是”abc”

然后改造存取过程——存的时候加密,读的时候解密:

def 保存档案():
    """保存前把每条档案的号码和备注加密"""
    待存 = []
    for 条 in 档案列表:
        待存.append({
            "名称": 条["名称"],          # 名称和类别不加密(方便人眼看文件)
            "类别": 条["类别"],
            "号码": 加密文本(条["号码"]),  # 敏感字段加密
            "备注": 加密文本(条["备注"])
        })
    with open(文件名, "w", encoding="utf-8") as f:
        json.dump(待存, f, ensure_ascii=False, indent=2)
    print(f"💾 已加密保存 {len(待存)} 条档案")

def 加载档案():
    global 档案列表
    try:
        with open(文件名, "r", encoding="utf-8") as f:
            原始 = json.load(f)
        # 读进来后解密还原
        档案列表 = []
        for 条 in 原始:
            档案列表.append({
                "名称": 条["名称"],
                "类别": 条["类别"],
                "号码": 解密文本(条["号码"]),
                "备注": 解密文本(条["备注"])
            })
        print(f"📂 已加载 {len(档案列表)} 条档案")
    except FileNotFoundError:
        print("📭 没有找到档案文件,从空档案开始")

现在文件里长这样:

{"名称": "身份证", "类别": "证件", "号码": "8181818qyqy...", "备注": "..."}

号码变成了一串乱码,别人打开文件也看不懂。

必须说清楚:这不是真正的密码学加密!知道原理的人轻松能解。但对”防室友瞟一眼””防文件误传”这种日常场景,够用了。真实的密码管理器用的是AES这类专业加密——那是以后学了加密库才能碰的东西。今天重点是理解”存时变换、取时还原“这个模式。

九、第二个改进:分类统计 — 你的信息都藏在哪

给管理器加一个”体检”功能:看看档案的分布情况。

def 统计报告():
    """统计各类别档案数量"""
    if not 档案列表:
        print("📭 没有档案")
        return

    # 分类计数:字典累加
    计数 = {}
    for 条 in 档案列表:
        类别 = 条["类别"]
        计数[类别] = 计数.get(类别, 0) + 1

    print("n📊 档案分布")
    print("-" * 30)
    for 类别 in 类别表:                  # 按固定类别顺序显示
        数量 = 计数.get(类别, 0)
        条形 = "█" * 数量                # 字符串乘法画条形图
        print(f"  {类别:2}条 {条形}")
    print("-" * 30)
    print(f"  总计 {len(档案列表)} 条")

    # 附加信息:有没有快到期的(备注里带"到期"的)
    到期的 = [条["名称"] for 条 in 档案列表 if "到期" in 条["备注"]]
    if 到期的:
        print(f"n⏰ 备注里提到"到期"的档案:{'、'.join(到期的)}")
        print("   记得检查这些证件的有效期!")

效果:

📊 档案分布
------------------------------
  证件    3条 ███
  账号    2条 ██
  金融    1条 █
  其他    0条 
------------------------------
  总计 6 条

⏰ 备注里提到"到期"的档案:身份证、护照
   记得检查这些证件的有效期!

用的全是老技术:字典计数(第二十二篇)、字符串乘法画图(第二十一篇进度条)、列表推导式筛选(第十二篇)。但组合出来,它会主动提醒你该干的事——这就不是练习,是工具了。

十、第三个改进:导出报表 — 生成可打印的清单

最后加一个实用功能:把档案导出成一个人能看的文本文件,可以打印出来锁进抽屉,或者发给自己备份。

def 导出报表():
    """把档案导出成格式化的文本文件(号码部分隐藏)"""
    if not 档案列表:
        print("📭 没有档案可导出")
        return

    from datetime import datetime
    报表名 = f"档案清单_{datetime.now().strftime('%Y%m%d')}.txt"

    with open(报表名, "w", encoding="utf-8") as f:
        f.write("个人档案清单n")
        f.write(f"导出时间:{datetime.now().strftime('%Y-%m-%d %H:%M')}n")
        f.write("=" * 50 + "nn")

        for 类别 in 类别表:
            该类 = [条 for 条 in 档案列表 if 条["类别"] == 类别]
            if not 该类:
                continue                    # 空类别跳过
            f.write(f"【{类别}】n")
            for 条 in 该类:
                # 号码中间打码:只显示前3位和后2位
                号 = 条["号码"]
                if len(号) > 6:
                    隐藏 = 号[:3] + "*" * (len(号) - 5) + 号[-2:]
                else:
                    隐藏 = "****"
                f.write(f"  {条['名称']:<12} {隐藏}")
                if 条["备注"]:
                    f.write(f"  ({条['备注']})")
                f.write("n")
            f.write("n")

        f.write("=" * 50 + "n")
        f.write(f"共 {len(档案列表)} 条档案n")

    print(f"📄 报表已导出:{报表名}")

三个值得注意的细节:

1. 导出时打码。 号[:3] + "*" * (len(号) - 5) + 号[-2:]——只露前3位和后2位,中间全遮住。报表是给人看的、可能打印出来的,不能带完整敏感信息。切片操作(第八篇)在这里派上用场。

2. 空类别跳过。 if not 该类: continue——没有”金融”类档案就别印空标题。报表的每一行都该有信息量。

3. 文件名带日期。 档案清单_20250115.txt——导出多次不会互相覆盖,每次都是独立的快照。

导出的报表长这样:

个人档案清单
导出时间:2025-01-15 20:30
==================================================

【证件】
  身份证       110********234  (2030年到期)
  护照         E12****56  (2028年到期)

【账号】
  常用邮箱     ***

==================================================
共 5 条档案

打印出来锁抽屉,应急时翻出来——这就是”数字工具服务现实生活”。

十一、阶段2毕业总结

看看这个不到200行的程序,用了多少东西:

知识点 在哪里用的

|——–|———–|

变量和数据类型 档案字段、各种状态
条件判断 菜单分发、输入验证、打码判断
循环 主循环、遍历档案、分类统计
break/continue 退出、跳过空类别
函数 每个功能一个函数、辅助函数
函数作为值 字典装菜单函数
return / None 按名称找的两种返回
列表 档案列表、筛选结果
append / remove / in 增删查
字典 档案、计数、菜单
字典.get 累加计数、默认值
列表推导式 按类别筛选、按条件搜索
切片 号码打码
字符串方法 strip、startswith、join、字符串乘法
文件操作 JSON档案、TXT报表
错误处理 输入验证、文件不存在、文件损坏
import json、os、datetime
ord/chr 加密解密
global 加载时改全局列表
f-string格式化 对齐表格、日期文件名

19类知识点,没有一个是阶段2新学的。 全部来自阶段1的12篇文章。

这就是今天最重要的结论:你不需要新知识,就能做出真正有用的软件。 阶段2的12个项目(13-24),每个都在告诉你同一件事——

  • 猜数字:条件+循环就能做游戏
  • 密码生成器:随机+字符串就是安全工具
  • 记账本:字典+统计就能管钱
  • 番茄钟:time+循环就能管时间
  • 档案管理器:全拼起来就是完整软件

知识还是那些知识,变的是你组合它们的能力。

十二、跟AI协作的进阶心得

这12个项目做下来,你应该已经摸到了跟AI协作的节奏。总结几条:

1. 先设计数据,再要代码。 这一篇我们先想清楚”一条档案是什么字典”,再去问AI。数据结构定了,代码怎么写都顺。反过来(先要代码再理解数据)会一直磕磕绊绊。

2. 大项目拆成小步。 档案管理器如果一次性让AI全写出来(含加密、报表),代码几百行,你根本读不完。先做主体,跑通了再一个个加功能——每一步的代码量都是你能消化的。

3. 改动前先读懂现有代码。 加密功能动了保存和加载两个函数。如果没读懂原来的代码,改起来必然出错。读懂再改,永远比”让AI重写”靠谱——重写会把你已经调好的其他部分弄坏。

4. 报错信息是你的朋友。 这12个项目里你肯定遇到过报错。养成习惯:把完整报错信息发给AI,说明你做了什么。90%的错误AI能直接定位。

十三、今日小练习

毕业练习,三个都做完,阶段2圆满:

练习1:主密码锁

程序启动时要求输入主密码,密码不对不让进。把主密码加密存在一个单独的小文件里,第一次使用时设置。(提示:复用加密文本函数;用input+比对;错误三次自动退出——循环+计数器。)

练习2:修改档案时支持改类别

现在的修改功能只能改号码和备注。加上改类别(重新显示类别菜单)。再想想:能不能让用户输”直接回车不改”来跳过?现在的代码已经是这个逻辑了,类别这栏怎么套进去?

练习3:合并两个档案文件

写一个”导入”功能:从另一个JSON档案文件读入档案,合并到当前列表里。注意处理重名——同名档案是跳过、覆盖,还是都保留改个名?三种策略选一种实现。(提示:加载档案函数改造一下,别用global,改成传参数返回结果更干净。)


写在阶段2的结尾

回头看第一篇,你还在问”编程是什么”。现在,你已经有了12个自己亲手做出来的程序——游戏、工具、计时器、管理器。每一个都是从”跟AI聊需求”开始,一行行读懂,一步步改进出来的。

你已经不是”学编程的人”了,你是”会编程的人”。 这两者之间的区别不是知识量,而是——前者总觉得”我还没准备好”,后者知道”我可以开始做了”。

阶段3(第25篇起)要爬第一个真正的台阶了:文件和数据处理——CSV、JSON、数据清洗、批量处理。有了阶段2这12个项目的肌肉记忆,那个台阶你上得去。

去造下一个工具吧。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注