第一次作业:通讯录管理系统

Ethan.CH 2025-11-11 22:17:32

第一次作业:通讯录管理系统

姓名: 程一鸣 学号: FZU:832302227, MU:23124792 GitHub ID: tomcheng9421-sketch

课程名称软件工程
作业要求实现前后端分离的通讯录管理系统 (前后端分离,云服务器部署)
作业目标掌握前后端分离开发模式,实现联系人的增删改查,熟悉云端部署流程
技术栈Python Flask, HTML/CSS/JS, Render, Vercel, Git

目录

$$TOC$$

一、Git仓库链接与代码规范

本项目严格遵循前后端分离的开发规范,代码托管于 GitHub,前端与后端分别存放于两个独立的仓库。

  1. 前端仓库
  2. 后端仓库
  3. 网页链接 (云端部署)

二、PSP表格

本次项目复现的 PSP (Personal Software Process) 表格如下,其中“实际时间”包含了大量用于环境配置、排查网络故障和解决云端部署“踩坑”的时间。

阶段步骤内容说明预估时间(h)实际时间(h)
Planning计划明确技术栈 (Flask + JS),确定云部署方案 (Render + Vercel)1.01.0
Development需求分析分析通讯录功能 (增删改查、扩展字段、过滤)0.51.0
后端开发搭建 Flask API,定义 models.py,实现 CRUD 接口3.04.5
前端开发编写 index.html 布局、style.css 美化、app.js 逻辑3.04.0
Testing本地调试联调本地前后端,解决 venv 激活、contacts.db 不同步等问题1.53.5
Deployment部署准备解决 git push 网络故障,清理 .idea 文件0.52.0
后端部署部署 Flask 到 Render,配置 PostgreSQL,解决 DATABASE_URL 环境变量问题1.03.0
部署踩坑(预估未包含)(1.0)
(解决 Render 免费版 'Shell' 禁用,实现 'Plan B' 自动建表)
前端部署部署 HTML/JS 到 Vercel,修复 API_URL 指向,解决 Failed to fetch 问题0.51.5
Evaluation总结与报告撰写博客,总结开发与部署过程中的所有“坑”1.01.5
合计11.022.0

三、成品展示

1. 基础功能

1.1. 主界面展示

主界面采用现代化卡片式设计,集成了 Font Awesome 图标库,观感简洁。顶部为“按姓名选择”和“按标签选择”的动态筛选区域。下方为联系人列表,每个联系人以卡片形式展示其所有信息。

img

1.2. 添加联系人功能

左侧为“添加新联系人”表单,支持填写姓名、电话、地址、邮箱,并可通过复选框选择“家人”、“朋友”等标签。

img

1.3. 编辑联系人功能

点击联系人卡片上的“修改”按钮,该联系人的信息(包括姓名、电话、地址、邮箱和已选中的标签)就可被修改。修改完成后点击“保存修改”,列表将自动刷新。

img

1.4. 删除联系人功能

点击联系人卡片上的“删除”按钮,会弹出浏览器 confirm 对话框,防止用户误操作。确认后,前端向后端发送 DELETE 请求,成功后自动刷新列表。

img


img

2. 拓展功能

2.1. 按姓名/标签选择 (条件搜索)

顶部的两个筛选框(一个输入框,一个下拉框)实现了前端实时过滤。当用户在“按姓名选择”https://i-blog.csdnimg.cn/communtity/e4a6761f5e5e4123bf38832804997418.png "#left")
框输入文字,或在“按标签选择”下拉框中选择一项时,app.js 会立即重新渲染联系人列表,仅显示符合所有条件的联系人。

2.2. 详细信息与标签系统

本项目在基础的“姓名”和“电话”之上,成功扩展了 address (地址), email (邮箱), tags (标签) 三个字段。所有字段均可在前后端进行增、删、改、查,并在前端卡片上清晰展示。

四、设计实现过程

1. 系统架构设计

本系统是一个彻底的前后端分离架构,职责清晰:

前端 (Vercel):

  • 技术: 原生 HTML, CSS, JavaScript (ES6+)。
  • 职责: 负责所有界面展示、用户交互和样式美化。它是一个纯“静态站点”,不包含任何后端逻辑。
  • 部署: 部署于 Vercel 全球 CDN,实现高速访问。

后端 (Render):

  • 技术: Python + Flask-SQLAlchemy + Flask-Cors。
  • 职责: 负责所有业务逻辑、数据处理和 API 接口。
  • 部署: 部署于 Render 的 Web Service,使用 gunicorn 作为生产服务器。

数据库 (Render):

  • 技术: 本地开发使用 SQLite (即 contacts.db 文件),生产环境使用 Render 提供的 PostgreSQL 云数据库。
  • 职责: 持久化存储所有联系人数据。

数据流:

  1. 用户访问 Vercel 网址 (...vercel.app)。
  2. 前端 app.js 向 Render 网址 (...onrender.com/api/contacts) 发起 fetch 请求。
  3. Render 后端收到请求,通过 SQLAlchemy 查询 PostgreSQL 数据库。
  4. Render 后端将查询结果以 JSON 格式返回给前端。
  5. 前端 app.js 收到 JSON,动态生成 HTML 并渲染到页面。

2. 功能结构图

通讯录系统
├── 前端 (Vercel)
│   ├── 视图 (index.html)
│   ├── 样式 (style.css)
│   └── 逻辑 (app.js)
│       ├── API_URL (连接后端)
│       ├── fetchContacts() (获取/渲染)
│       ├── Add/Edit/Delete (增删改)
│       └── Filters (过滤)
└── 后端 (Render)
    ├── 应用 (run.py / src/__init__.py)
    │   ├── 自动创建DB (db.create_all())
    │   └── CORS (跨域)
    ├── API 路由 (controller/concacts.py)
    │   ├── /api/contacts (GET, POST)
    │   └── /api/contacts/<id> (PUT, DELETE)
    └── 数据库 (models.py / PostgreSQL)
        └── Contact 表 (name, phone, address, email, tags)

3. 数据库设计

基于扩展后的需求,我们最终的数据库模型 (在 src/models.py 中定义) 如下:

字段名类型约束说明
idIntegerPrimary Key主键ID
nameString(80)Not Null姓名
phoneString(20)Not Null电话
addressString(200)Nullable地址
emailString(120)Nullable邮箱
tagsString(100)Nullable标签 (如 "家人,朋友")

五、代码解释

1. 前端关键代码

1.1. API_URL 的切换 (部署的关键)

app.js 的第一行代码是整个前后端分离项目能否在云端工作的命脉。

部署前 (本地开发):

// app.js (修改前)
const API_URL = 'http://localhost:5000/api';

部署后 (云端生产):
在部署到 Vercel 之前,必须将其修改为指向 Render 后端的公网地址,否则前端将无法找到后端,导致 Failed to fetch 错误。

// app.js (修改后)
const API_URL = 'https://eight32302227-contacts-backend.onrender.com/api';

1.2. 动态获取与渲染 (fetchContacts)

app.js 中的 fetchContacts 函数是前端的核心,它负责从后端拉取数据、执行前端过滤、并动态生成 HTML 渲染到页面。

async function fetchContacts() {
    contactsList.innerHTML = '<li>加载中...</li>';
    
    try {
        const response = await fetch(`${API_URL}/contacts`);
        let contacts = await response.json();

        // --- 过滤逻辑 ---
        const nameFilter = filterNameInput.value.toLowerCase();
        const tagFilter = filterTagSelect.value;

        contacts = contacts.filter(contact => {
            const matchesName = nameFilter === '' || contact.name.toLowerCase().includes(nameFilter);
            const matchesTag = tagFilter === '' || (contact.tags && contact.tags.includes(tagFilter));
            return matchesName && matchesTag;
        });
        // --- 过滤逻辑结束 ---

        contactsList.innerHTML = ''; // 清空列表

        if (contacts.length === 0) {
            contactsList.innerHTML = '<li>暂无联系人...</li>';
            return;
        }

        contacts.forEach(contact => {
            // ... (此处省略动态创建 li 元素的 HTML) ...
            contactsList.appendChild(li);
        });

    } catch (error) {
        console.error('获取联系人失败:', error);
        contactsList.innerHTML = '<li>加载联系人失败</li>';
    }
}

2. 后端关键代码

2.1. 数据模型定义 (src/models.py)

后端的 Contact 模型定义了数据库的结构,与前端的需求完全对应。

# src/models.py
from . import db  # 从 src/__init__.py 导入 db 实例

class Contact(db.Model):
    __tablename__ = 'contacts'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    phone = db.Column(db.String(20), nullable=False)
    # --- 新增字段 ---
    address = db.Column(db.String(200), nullable=True) # 可以为空
    email = db.Column(db.String(120), nullable=True)   # 可以为空
    tags = db.Column(db.String(100), nullable=True)    # 可以为空,存储如 "家人,朋友"

    def to_dict(self):
        return {
            "id": self.id,
            "name": self.name,
            "phone": self.phone,
            "address": self.address,
            "email": self.email,
            "tags": self.tags
        }

2.2. “B 计划” - 自动创建数据库 (src/__init__.py)

这是本次部署中最关键的“踩坑”和“填坑”!

问题: Render 免费版禁用了 "Shell" (命令行) 功能,导致我们无法手动登录服务器执行 db.create_all() 来创建数据库表。

解决方案 (Plan B):
修改应用工厂函数,让 Flask 应用在每次启动时,自动检查并创建数据库表。

# src/__init__.py (关键代码片段)

def create_app():
    # ... (前面的 app, db, CORS 配置) ...

    # 5. 导入并注册蓝图
    from .controller import concacts
    app.register_blueprint(concacts.bp)

    # 6. 确保模型被导入
    from . import models

    # ---  B 计划:在这里添加这两行! ---
    # 在应用启动时,自动在云端创建数据库表
    # db.create_all() 会检查表是否存在,如果不存在,则创建。
    with app.app_context():
        db.create_all()
    # --- B 计划结束 ---

    return app

六、心得体会

这次通讯录系统的复现,远不止是“写代码”,而是一次从零开始、贯穿本地开发、网络调试、云端部署的全栈实战。最大的收获不是来自成功,而是来自排错。

第一关:本地环境。
在项目刚开始,就遇到了 PowerShell 的“下马威”。venv 虚拟环境的激活命令在 PowerShell 下是 .\venv\Scripts\activate 而不是 source venv/bin/activate,并且还被“执行策略”(Set-ExecutionPolicy) 拦截。这让我第一次深刻理解到,工具和环境的配置是项目的第一道门槛。

第二关:数据库不同步。
在本地开发时,当我为 models.py 添加了 address, email 等新字段后,前端立刻报错 no column named address。我花了很长时间才意识到:代码改了,但 instance/contacts.db 数据库文件还是旧的! 必须手动删除旧的 .db 文件,重启 run.py,让 SQLAlchemy 重新生成表结构。这个教训让我明白,代码和数据状态必须时刻保持同步。

第三关:网络故障。
当我准备 git push 上传代码时,连续遇到了 Connection was resetFailed to connect 错误。这与代码完全无关,纯粹是网络问题。通过关闭代理、切换手机热点等方式才最终推送成功。这让我学会了区分“代码错误”和“环境错误”。

第四关:云端部署“踩坑”。
这才是本次项目的“BOSS 战”。

  • Render (后端): 第一次部署失败,日志显示 SQLALCHEMY_DATABASE_URI must be set。原因是我在创建服务时,忘记在 Advanced (高级) 选项里添加 DATABASE_URL 环境变量。
  • Render (后端): 加上环境变量并部署成功 (Your service is live) 后,我准备按计划进入 "Shell" 手动创建数据库表,却发现**“Shell is not supported for free instance types”**。Render 平台更新了规则,免费版禁用了 Shell!原计划彻底作废。
  • Plan B (后端): 最终,我想到了“B 计划”:让代码自己创建数据库。通过在 src/__init__.py 中添加 with app.app_context(): db.create_all(),让应用在每次启动时自动建表。git push 这个修改后,Render 自动重新部署,问题解决!
  • Vercel (前端): 前端部署很顺利,但打开网址后,添加联系人时弹出了 Failed to fetch。这是因为我的 app.js 里的 API_URL 还是 localhost:5000。我必须再次修改 app.js,将其指向 Render 的公网 URL,然后再次 git push,Vercel 自动重新部署后,才最终成功。

总结:
一个“简单”的通讯录,却串联起了 Git、Python、Flask、SQLAlchemy、PostgreSQL、Gunicorn、JavaScript、CORS、RESTful API、CI/CD (Vercel/Render 的自动部署) 等无数个知识点。我深刻体会到,一个真正的“软件工程师”,不仅要会写代码,更要是一个会排查环境、调试网络、解决部署问题的“侦探”。

...全文
159 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

164

社区成员

发帖
与我相关
我的任务
社区描述
2501_MU_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_LQF
  • 助教_林日臻
  • 朱仕君
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧