EE308 - First Assignment:前后端分离通讯录系统

Course for This Assignment: EE308FZ
Assignment Requirements: This assignment requires us to develop a contacts management system that has three basic functions: add a contact, remove a contact, and edit the contact’s information. The coding requires frontend and backend separation.
Objectives of This Assignment: Implementing a Web-based Contact System with Frontend-Backend Separation, Mastering API Design, Cross-Origin Handling, and Cloud Deployment Processes.
Name: Yuxiang Xie(谢宇翔)
FZU_ID: 832301327
MU_ID: 23124237
Other References:



1. Git Repository Link and Code Standards Link

项目 链接
前端仓库 https://github.com/Markspro32/Yuxiang-Xie_832301327_Contacts-Web-App_front_end
后端仓库 https://github.com/Markspro32/Yuxiang-Xie_832301327_Contacts-Web-App_back_end
前端代码规范 codestyle.md
后端代码规范 codestyle.md

2. PSP Table

任务 预估时间(分钟) 实际时间(分钟)
需求分析 30 45
前端页面开发 120 150
后端开发(Django) 120 180
前后端联调 90 120
阿里云部署 60 70
博客撰写 60 120
总计 480 685

时间单位为分钟(min)


3. Presentation of the Finished Product

所有功能均已实现:增、删、改,数据存储于阿里云服务器 SQLite 数据库。

图 1:前端界面
前端页面展示

访问地址:https://markspro32.github.io/Yuxiang-Xie_832301327_Contacts-Web-App_front_end/

图 2:添加联系人与列表显示同步
在这里插入图片描述

输入姓名、邮箱、电话 → 点击 “Add Contact”
数据从后端 API (http://8.138.121.2:8000/contacts/) 实时加载

图3: 联系人列表展示
在这里插入图片描述
图 4: 添加联系人时缺少字段,系统提示
在这里插入图片描述
图 5: 添加联系人时格式出错,系统提示
在这里插入图片描述

图 6:编辑联系人
在这里插入图片描述

修改后点击 “Save”,数据同步到数据库

图 7:删除联系人
在这里插入图片描述

点击 “Delete” → 确认后移除

图 8:后端 API 测试 (在Postman中进行测试)
在这里插入图片描述
图 9:后端API测试2
在这里插入图片描述

向后端发送GET /contacts/ 请求,成功返回 JSON 数据,如图所示
这说明:
–后端API正在运行
–Django REST框架返回JSON数据
–后端数据库已连接
–云端部署正常运作
– 前端和Postman均可访问后端
–没有将数据存在网页缓存

图 10:阿里云服务器运行 Django

Django Cloud Server

python manage.py runserver 0.0.0.0:8000


4. Design and Implementation Process

本次作业的前端我是用html进行网页框架搭建,用css进行网页美化,使用javascript进行前后端逻辑交互。后端我采用Django框架进行开发,在views.py里编写视图函数,在model.py里对联系人进行建模,设置名字,邮箱和电话三个对象,在url.py中配置了项目总路由和app路由,在设置中配置跨域访问,最终实现前端和后端的交互,通讯录网页app成功在本地跑了起来。接下来,我把前后端项目通过git推送到了GitHub仓库。之后,我到阿里云官网租借云服务器,我在云服务器上下载了必备的工具,如python 3.11.0以及git for windows等必要组件。通过GitHub这个中间仓库,云服务器得以在上面拉取我的项目,并部署在了云端,这样通讯录网页app便可以通过网址,在各个设备上直接访问并使用了。

5. Code Explanation

前端核心代码

a.加载联系人

// ====== Load Contacts ======
async function loadContacts() {
  try {
    console.log('Starting to load contacts...');
    const response = await fetch(API_BASE);

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const contacts = await response.json();
    console.log(' Loaded contacts:', contacts);
    renderContacts(contacts);
  } catch (error) {
    console.error('Failed to load contacts:', error);
    showError(`Failed to load contacts: ${error.message}`);
  }
}

• 向后台API发送GET请求以获取所有联系人。
• 将返回的JSON数据转换为JavaScript对象。
• 调用renderContacts()更新用户界面。
• 如果请求失败,则显示错误信息。

目的:从后端数据库加载最新的联系人列表。

b.渲染联系人列表

// ====== RENDER CONTACTS ======
function renderContacts(contacts) {
  contactsList.innerHTML = '';
  
  if (contacts.length === 0) {
    contactsList.innerHTML = '<li>No contacts yet. Add one below!</li>';
    return;
  }

  contacts.forEach(contact => {
    const li = document.createElement('li');
    li.innerHTML = `
      <div>
        <strong>${escapeHtml(contact.name)}</strong><br>
        Email: ${escapeHtml(contact.email)}<br>
        Phone: ${escapeHtml(contact.phone)}
      </div>
      <div>
        <button class="edit-btn" data-id="${contact.id}">Edit</button>
        <button class="delete-btn" data-id="${contact.id}">Delete</button>
      </div>
    `;
    contactsList.appendChild(li);
  });

• 清空当前联系人列表。

• 如果没有联系人,则显示占位符信息。

• 否则,遍历每个联系人并动态创建相应的元素。

• 为每个联系人添加编辑和删除按钮。

用途:显示联系人信息并提供交互式操作按钮。

c.添加联系人

// ====== ADD CONTACT ======
async function handleAddContact(e) {
  e.preventDefault();
  
  const name = document.getElementById('name').value;
  const email = document.getElementById('email').value;
  const phone = document.getElementById('phone').value;

  try {
    const response = await fetch(API_BASE, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, email, phone })
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      throw new Error(errorData.error || `HTTP ${response.status}`);
    }

    // Clear the form and refresh the list after you add a contact
    contactForm.reset();
    loadContacts();
    console.log('Contact added');
  } catch (error) {
    console.error(' Failed to add contact:', error);
    showError(`Failed to add contact: ${error.message}`);
  }
}

• 当用户提交“添加联系人”表单时触发。

• 发送包含新联系人数据(姓名、电子邮件、电话)的 JSON 格式 POST 请求。

• 如果成功:

• 清空输入表单,

• 调用 loadContacts() 刷新联系人列表。

• 如果出现错误,则显示错误消息。

用处:允许用户创建新的联系人

d.删除联系人

// ====== DELETE CONTACT ======
async function deleteContact(id) {
    console.log(` Delete button clicked for ID: ${id}`);
  if (!confirm('Are you sure you want to delete this contact?')) return;

  try {
    const response = await fetch(`${API_BASE}${id}/`, {
      method: 'DELETE'
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    loadContacts();
    console.log('Contact deleted');
  } catch (error) {
    console.error(' Failed to delete contact:', error);
    showError(`Failed to delete contact: ${error.message}`);
  }
}

• 删除前会请求确认。

• 向 /contacts/<id_xxx>/ 发送 DELETE 请求。

• 删除成功后,重新加载联系人列表。

用途:从用户界面和后端数据库中删除联系人。

e.编辑联系人

// ====== EDIT CONTACT ======
function showEditForm(id) {
  const contactElement = document.querySelector(`button[data-id="${id}"]`).closest('li');
  const contactName = contactElement.querySelector('strong').textContent;
  const contactEmail = contactElement.querySelectorAll('div')[0].textContent.split('Email: ')[1].split('Phone: ')[0].trim();
  const contactPhone = contactElement.querySelectorAll('div')[0].textContent.split('Phone: ')[1].trim();

  contactElement.innerHTML = `
    <div>
      <input type="text" class="edit-name" value="${escapeHtml(contactName)}" />
      <input type="email" class="edit-email" value="${escapeHtml(contactEmail)}" />
      <input type="text" class="edit-phone" value="${escapeHtml(contactPhone)}" />
    </div>
    <div>
      <button class="save-btn" data-id="${id}">Save</button>
      <button class="cancel-btn">Cancel</button>
    </div>
  `;

  contactElement.querySelector('.save-btn').addEventListener('click', () => saveContact(id));
  contactElement.querySelector('.cancel-btn').addEventListener('click', loadContacts);
}

async function saveContact(id) {
  const name = document.querySelector('.edit-name').value;
  const email = document.querySelector('.edit-email').value;
  const phone = document.querySelector('.edit-phone').value;

  try {
    const response = await fetch(`${API_BASE}${id}/`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, email, phone })
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    loadContacts();
    console.log('Contact updated');
  } catch (error) {
    console.error(' Failed to update contact:', error);
    showError(`Failed to update contact: ${error.message}`);
  }
}

showEditForm() 函数会将联系人显示界面替换为输入框,以便用户可以修改联系人信息。

saveContact() 函数会发送 PUT 请求来更新后端联系人信息。

• 更新成功后,调用 loadContacts() 函数刷新联系人列表。

目的:提供无需页面重新加载即可直接编辑联系人信息的体验。

跨域配置关键:

# 允许跨域
CORS_ALLOW_ALL_ORIGINS = True

# 设置允许的主机(必须包含你的公网 IP)
ALLOWED_HOSTS = ['8.138.121.2', 'localhost', '127.0.0.1']

# 安装 CORS 插件
INSTALLED_APPS = [
    ...
    'corsheaders',
    'contacts_app',
]

# 将 CORS 中间件放在最前面
MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',  # 必须放在第一行
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

配置后端 API 地址(阿里云服务器公网 IP):

const API_BASE = 'http://8.138.121.2:8000/contacts/';

后端核心代码

Contacts_app/model.py

from django.db import models

class Contact(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(max_length=100)
    phone = models.CharField(max_length=100)

    def __str__(self):
        return self.name

Contacts_app/views.py
这里包含了增,删,改的核心代码。

该项目的后端提供用于管理联系人的 RESTful API。
它支持通过 HTTP 请求进行创建、读取、更新和删除(CRUD)操作。

@method_decorator(csrf_exempt, name='dispatch')
class ContactListView(View):
    ...

@method_decorator(csrf_exempt, name='dispatch')
class ContactDetailView(View):
    ...

ContactListViewContactDetailView 是基于类的视图,它们通过请求方法(GET、POST、PUT、DELETE)来组织逻辑。

Helper Function

def parse_json_body(request):
...

• 此函数从请求体中提取JSON数据
• 当JSON格式错误时可防止报错
• 解析失败时返回None → 便于早期错误处理

联系人列表视图 — 处理列表和创建操作(GET 与 POST)

方法 URL 用途
GET /contacts/ 返回所有联系人列表
POST /contacts/ 添加新联系人

GET请求示例行为

contacts = list(Contact.objects.values('id', 'name', 'email', 'phone'))
return JsonResponse(contacts, safe=False)

• 从数据库获取所有联系人记录。
• 将其转换为JSON对象列表。
• 返回给前端进行显示。

POST 请求事例行为

contact = Contact.objects.create(name=..., email=..., phone=...)

• 从前端接收JSON数据
• 校验必填字段(姓名、邮箱、电话)
• 将新联系人存入SQLite数据库
• 返回已创建联系人的数据及ID

联系人详情视图 — 处理单个联系人的查看、更新和删除

方法 URL 用途
GET /contacts/<id_xxx> 获取单个联系人
PUT /contacts/<id_xxx> 修改现有联系人
DELETE /contacts/<id_xxx> 删除联系人

GET请求示例行为

contact = Contact.objects.get(id=contact_id)

• 根据ID查找联系人。
• 如果未找到 → 发送404错误。
• 否则返回该联系人的详细信息。

PUT请求事例

contact.name = data.get('name', contact.name)
contact.save()
  • 从前端接收修订后的数据。
  • 仅更新提供的字段。
  • 将更新后的联系人保存到数据库。

DELETE 请求事例

contact.delete()

永久地从数据库中删除选定的联系人。

使用@csrf_exempt

由于本项目采用前后端分离架构,POST/PUT/DELETE请求来自JavaScript的fetch调用而非Django表单。为避免CSRF令牌验证错误,视图函数通过以下装饰器豁免校验:

@method_decorator(csrf_exempt, name='dispatch')

6. Personal Journey and Learnings

通过这次的前后端分离式通讯录的搭建,我了解了软件开发的大致流程,对前后端的框架有了初步的了解,这为我以后从事软件开发相关工作奠定了基础。
对于前端,我自学了html,css,javascript等前端语言,但是并没有学完,所以以后有时间还要继续学习,继续丰富前端开发经验。
对于后端,我了解到Django框架更加适合大规模的开发工作,在生产实践中相比于Flask更加实用,于是我对Django框架进行了初步的了解和学习。在一开始的时候,只是跟着b站教程一步一步敲代码,其中许多函数和逻辑还是云里雾里,但是一套教程看下来,也明白了大致的操作逻辑。我对于Django的理解还不够深入,以后还需要多花时间学习。
部署服务器,在这次项目中对我来说是一个不小的挑战。服务器的各种端口,各种协议在我这里都是全新的概念,而且使用git推送项目也是我第一次接触,这增加了我的学习成本,加大了开发难度。一开始我只是把后端部署到了服务器上,点开网址里面是json文件,存的是后端数据内容,后来我通过学习了解到需要将前端代码部署到后端中的tenmplates和static文件夹,前端静态文件通过访问后端API,才能在网页中显示…
克服了种种困难,我终于做出了前后端分离的通讯录项目。这次的作业对于我而言绝对是一次历练,是从不会到会的探索,是解决一个又一个困难并从中收获颇丰的一次学习经历,这对我受益匪浅。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐