第四次軟工作業

LEML發表於2024-10-10
專案內容 詳細資訊
這個作業屬於哪個課程 軟體工程2024
這個作業要求在哪裡 作業要求連結
這個作業的目標 實現校園專案招募APP的設計
姓名及學號 藍敏龍 102202146
結對成員及學號 施宇翔 102202135
Github倉庫地址 (https://github.com/acedia7/102202135-102202146)

目錄
  • 1.具體分工
      • 藍敏龍同學
      • 施宇翔同學
  • 2.PSP表格
  • 3.解題思路與設計實現說明
        • 一、總體架構設計
        • 二、技術棧選擇與理由
        • 三、模組劃分與功能實現
          • 1. 登入註冊模組
          • 2. 專案管理模組
          • 3. 查詢專案模組
          • 4. 訊息通訊模組
          • 5. 個人主頁模組
        • 四、資料庫設計
        • 1. 使用者集合(Users)
        • 2. 專案集合(Projects)
        • 3. 訊息集合(Messages)
        • 五、流程圖展示
        • 六、資料流動圖
      • 七、後端關鍵程式碼及解釋
        • 專案架構
        • 關鍵功能
        • 技術實現細節
      • 八、前端關鍵程式碼及時
      • 整體程式碼解釋
        • 核心功能
        • 整體工作流程
        • 技術實現要點
  • 4.附加特點設計與展示
      • 專案檔案上傳功能詳解
        • 設計與附加特點
        • 實現思路
        • 關鍵程式碼片段與解釋
          • 自定義許可權類
          • 專案檔案檢視集
        • 實現成果展示
  • 5.APP功能展示
      • 登入
      • 註冊
      • 釋出專案
      • 修改專案
      • 尋求夥伴
      • 上傳檔案
      • 搜尋
      • 瞭解更多
      • 私聊
      • 個人中心
      • 修改個人資訊
      • 個人捐贈
  • 6.目錄說明和使用說明
        • 前端程式碼目錄
        • 後端程式碼目錄
  • 7.單元測試
      • 單元測試
        • 一、單元測試工具及學習方式
        • 二、簡易教程
        • 三、展示專案部分單元測試程式碼及說明
        • 四、構造測試資料的思路
  • 8.Github程式碼簽入記錄截圖
  • 9.程式碼模組異常及解決方法
      • 1. 使用者模型自定義導致的遷移問題
          • 問題描述
          • 做過哪些嘗試
          • 是否解決
          • 有何收穫
      • 2. 上傳圖片到伺服器失敗
          • 問題描述
          • 做過哪些嘗試
          • 是否解決
          • 有何收穫
      • 3. 跳轉頁面失敗
          • 問題描述
          • 做過哪些嘗試
          • 是否解決
          • 有何收穫
  • 10.評價
      • 藍敏龍對施宇翔的評價
          • 值得學習的地方
          • 需要改進的地方
      • 施宇翔對藍敏龍的評價
          • 值得學習的地方
          • 需要改進的地方

1.具體分工

本專案的具體分工如下:

藍敏龍同學

主要負責前端開發以及部落格的撰寫工作,具體包括:

  • 前端開發

    • 使用者介面設計與實現
    • 前端功能模組的開發(如登入/註冊頁面、使用者主頁、專案詳情介面等)
    • 響應式設計與最佳化
    • 前端程式碼的除錯與維護
  • 部落格撰寫

    • 部落格內容的組織與編寫
    • 專案文件的整理與釋出
    • 程式碼片段的展示與解釋
    • 設計創意與實現成果的描述

施宇翔同學

主要負責後端開發以及測試工作,具體包括:

  • 後端開發

    • 伺服器端邏輯的設計與實現
    • 資料庫設計與管理
    • API介面的開發與維護
    • 後端程式碼的最佳化與安全性保障
  • 測試工作

    • 編寫和執行單元測試
    • 測試工具的選型與使用
    • 測試報告的撰寫
    • 系統的整體測試與問題修復

2.PSP表格

PSP2.1 Personal Software Process Stages 藍敏龍同學 預估耗時(分鐘) 藍敏龍同學 實際耗時(分鐘) 施宇翔同學 預估耗時(分鐘) 施宇翔同學 實際耗時(分鐘)
Planning 90 100 75 85
Estimate 45 55 35 40
Development 300 350 270 320
Analysis 150 170 130 145
Design Spec 120 140 100 115
Design Review 60 75 50 60
Coding Standard 30 40 25 30
Design 225 260 200 225
Coding 450 500 400 450
Code Review 75 90 60 70
Test 180 220 165 200
Reporting 90 110 75 90
Test Report 60 75 50 60
Size Measurement 45 55 35 45
Postmortem & Process Improvement Plan 75 90 60 75
合計 1965 2310 1860 2120

3.解題思路與設計實現說明

在本專案中,我們旨在開發一個跨專業專案協作平臺,幫助學生們發起和參與專案,從而提升綜合能力、拓寬知識面和積累人脈。為實現這一目標,我們選擇使用 uniAPP 作為前端開發框架,Djongo 作為後端開發框架,並將系統模組劃分為五個主要部分:登入註冊、專案管理、查詢專案、訊息通訊、個人主頁。以下是詳細的解題思路及設計實現說明。

一、總體架構設計

本系統採用前後端分離的架構,前端使用 uniAPP 開發,能夠跨平臺部署(如微信小程式、H5、iOS和Android應用),後端使用 Djongo 框架,基於Django與MongoDB的結合,提供高效的資料處理和儲存能力。前後端透過RESTful API進行通訊,確保系統的靈活性和可擴充套件性。

架構圖示意:

+-----------------+          REST API         +-----------------+
|                 | <-----------------------> |                 |
|     前端 (uniAPP)|                           |    後端 (Djongo) |
|                 |                           |                 |
+-----------------+                           +-----------------+
        |                                              |
        |                                              |
        |                                              |
+-----------------+                           +-----------------+
|   使用者裝置      |                           |   資料庫 (MongoDB)|
+-----------------+                           +-----------------+

二、技術棧選擇與理由

  1. 前端框架 - uniAPP

    • 跨平臺支援:uniAPP支援編譯到多個平臺,如微信小程式、H5、iOS和Android,極大地提高了開發效率和使用者覆蓋面。
    • 豐富的元件庫:提供豐富的UI元件,便於快速構建使用者介面。
    • 強大的生態系統:擁有廣泛的社群支援和外掛資源,便於整合第三方功能。
  2. 後端框架 - Djongo

    • Django優勢:Djongo基於Django,繼承了Django的強大功能,如ORM、認證系統、管理後臺等,簡化了後端開發。
    • MongoDB整合:Djongo允許使用MongoDB作為資料庫,適合處理非結構化資料,提供更高的靈活性和可擴充套件性。
    • RESTful API支援:透過Django REST Framework,可以輕鬆構建符合REST標準的API介面。
  3. 資料庫 - MongoDB

    • 高效能和可擴充套件性:適合大規模資料儲存和高併發訪問。
    • 靈活的資料模型:支援文件儲存,便於儲存複雜的專案和使用者資料。

三、模組劃分與功能實現

系統主要劃分為以下五個模組,每個模組的功能和實現細節如下:

1. 登入註冊模組

功能描述:

  • 使用者透過教育郵箱進行註冊或登入。
  • 教育郵箱認證,確保使用者身份的真實性。

實現步驟:

  • 前端(uniAPP)

    • 設計註冊和登入頁面,包含輸入框(使用者名稱、密碼)、註冊和登入按鈕、“忘記密碼”連結。
    • 實現教育郵箱認證,透過輸入郵箱後傳送驗證碼進行驗證。
    • 使用uniAPP的表單驗證功能,確保使用者輸入的合法性。
  • 後端(Djongo)

    • 建立使用者模型,包含使用者名稱、密碼、手機號、學號、教育郵箱等欄位。
    • 實現註冊API,接收使用者資訊,進行驗證後儲存到資料庫。
    • 實現登入API,驗證使用者身份,返回認證Token。
    • 實現密碼重置API,傳送驗證碼至教育郵箱,並允許使用者重置密碼。
2. 專案管理模組

功能描述:

  • 使用者可以檢視自己參與的專案。
  • 釋出新的專案,包括專案名稱、編號、描述、相關領域等資訊。
  • 管理專案成員,上傳和下載專案檔案。

實現步驟:

  • 前端(uniAPP)

    • 設計使用者主頁,卡片式展示使用者參與的專案。
    • 提供釋出專案的介面,包含專案詳情填寫表單。
    • 實現專案詳情頁面,展示專案資訊、協作者列表及檔案管理功能。
  • 後端(Djongo)

    • 建立專案模型,包含專案名稱、編號、描述、相關領域、建立日期等欄位。
    • 實現專案釋出API,接收專案資訊並儲存。
    • 實現專案查詢API,支援按專案編號或名稱查詢。
    • 實現檔案管理API,支援檔案的上傳、下載。
3. 查詢專案模組

功能描述:

  • 使用者可以根據關鍵詞查詢感興趣的專案。

實現步驟:

  • 前端(uniAPP)

    • 設計查詢專案頁面,搜尋欄和專案展示。
    • 實現搜尋功能,傳送關鍵詞到後端並展示搜尋結果。
    • 展示系統推薦的專案,支援卡片式瀏覽。
  • 後端(Djongo)

    • 實現專案搜尋API,支援關鍵詞匹配和模糊搜尋。
    • 開發推薦演算法,根據專案的熱度、使用者的興趣和參與情況推薦專案。
    • 提供推薦專案的API介面,供前端呼叫展示。
4. 訊息通訊模組

功能描述:

  • 使用者可以根據不同專案的分組,進行私聊溝通。
  • 支援邀請加入專案組和申請加入專案組。

實現步驟:

  • 前端(uniAPP)

    • 設計通訊頁面,包含分組欄和聯絡人列表。
    • 實現私聊介面,支援實時聊天功能。
    • 提供邀請和申請加入專案組的互動介面。
  • 後端(Djongo)

    • 建立訊息模型,儲存聊天記錄和訊息狀態。
    • 實現私聊API,支援訊息的傳送和接收(可使用Django Channels實現實時通訊)。
    • 實現專案組邀請和申請API,處理邀請和申請請求。
5. 個人主頁模組

功能描述:

  • 展示使用者的個人資訊、關注和粉絲數量。
  • 允許使用者修改個人資訊、反饋意見、管理賬號安全。

實現步驟:

  • 前端(uniAPP)

    • 設計個人主頁頁面,展示使用者資訊和參與專案。
    • 實現修改個人資訊的介面,包括輸入框、時間選擇浮層、興趣選擇浮層等。
    • 提供反饋意見的介面,允許使用者提交反饋。
    • 設計賬號安全介面,支援修改密碼和繫結手機號。
  • 後端(Djongo)

    • 建立使用者資料模型,儲存使用者的詳細資訊和安全設定。
    • 實現個人資訊修改API,處理使用者的修改請求。
    • 實現反饋意見API,接收並儲存使用者的反饋。
    • 實現賬號安全API,支援密碼修改和手機號繫結功能。

四、資料庫設計

我們設計三個主要的資料庫集合(Collections):使用者(User)專案(Project)訊息(Message)。以下是每個集合的詳細設計,包括欄位定義、資料型別及其關係。


1. 使用者集合(Users)

集合名稱users

描述:儲存平臺使用者的詳細資訊,包括學生和教師。每個使用者具有唯一的教育郵箱作為登入標識,幷包含個人資料資訊。

欄位設計

欄位名稱 資料型別 描述 約束條件
_id ObjectId MongoDB自動生成的唯一識別符號 主鍵,自增長
student_id String 學生學號 必填,唯一
name String 使用者姓名 必填
school String 所在學校 必填
email String 教育郵箱 必填,唯一,索引
email_verified Boolean 郵箱是否經過驗證 預設值:false
is_active Boolean 賬戶是否啟用 預設值:false
is_admin Boolean 是否為管理員 預設值:false
is_teacher Boolean 是否為教師 預設值:false
major String 專業 可選
phone String 電話號碼 可選
interests String 興趣領域 可選
available_time String 空閒時間 可選
avatar String 頭像圖片路徑 可選
projects Array 參與的專案列表 引用 projects 集合的 project_id
followers Array 粉絲列表 引用 users 集合的 _id
following Array 關注列表 引用 users 集合的 _id
created_at DateTime 賬戶建立時間 自動生成
updated_at DateTime 賬戶資訊最後更新時間 自動更新

示例文件

{
  "_id": ObjectId("64a7c0f4e1d3c4a7b8f0a123"),
  "student_id": "20231001",
  "name": "張偉",
  "school": "清華大學",
  "email": "zhangwei@tsinghua.edu.cn",
  "email_verified": true,
  "is_active": true,
  "is_admin": false,
  "is_teacher": false,
  "major": "電腦科學",
  "phone": "13800138000",
  "interests": "人工智慧, 資料科學",
  "available_time": "週一、三、五下午",
  "avatar": "avatars/64a7c0f4e1d3c4a7b8f0a123/avatar1.png",
  "projects": [ObjectId("64a7c1a5e1d3c4a7b8f0a124")],
  "followers": [ObjectId("64a7c2b6e1d3c4a7b8f0a125")],
  "following": [ObjectId("64a7c3c7e1d3c4a7b8f0a126")],
  "created_at": ISODate("2023-10-01T10:00:00Z"),
  "updated_at": ISODate("2024-04-10T15:30:00Z")
}

2. 專案集合(Projects)

集合名稱projects

描述:儲存平臺上所有專案的詳細資訊,包括專案的基本資訊、成員和相關檔案。

欄位設計

欄位名稱 資料型別 描述 約束條件
_id ObjectId MongoDB自動生成的唯一識別符號 主鍵,自增長
project_id Number 專案編號 自動生成,唯一
project_name String 專案名稱 必填,索引
description String 專案描述 必填
field String 專案相關領域 必填
license String 專案許可證型別 必填,列舉(GPL, MIT, MPL, BSD, Apache)
status String 專案狀態 預設值:preparing,列舉(preparing, ongoing, completed)
is_private Boolean 專案隱私狀態 預設值:false,列舉(true, false)
created_by ObjectId 建立者使用者ID 引用 users 集合的 _id,必填
created_at DateTime 專案建立時間 自動生成
updated_at DateTime 專案最後更新時間 自動更新
image String 專案描述圖片路徑 可選
members Array 專案成員列表 引用 users 集合的 _id,含角色資訊
files Array 專案檔案列表 引用 project_files 集合的 _id

示例文件

{
  "_id": ObjectId("64a7c1a5e1d3c4a7b8f0a124"),
  "project_id": 1001,
  "project_name": "智慧推薦系統",
  "description": "開發一個基於機器學習的智慧推薦系統,提升使用者體驗。",
  "field": "人工智慧",
  "license": "MIT",
  "status": "ongoing",
  "is_private": false,
  "created_by": ObjectId("64a7c0f4e1d3c4a7b8f0a123"),
  "created_at": ISODate("2023-10-01T10:00:00Z"),
  "updated_at": ISODate("2024-04-10T15:30:00Z"),
  "image": "project_images/1001/專案圖.jpg",
  "members": [
    {
      "user_id": ObjectId("64a7c0f4e1d3c4a7b8f0a123"),
      "role": "admin"
    },
    {
      "user_id": ObjectId("64a7c2b6e1d3c4a7b8f0a125"),
      "role": "member"
    }
  ],
  "files": [ObjectId("64a7c4d8e1d3c4a7b8f0a127")]
}

3. 訊息集合(Messages)

集合名稱messages

描述:儲存使用者之間的私信和專案相關的聊天記錄,包括訊息內容、傳送者、接收者及關聯專案。

欄位設計

欄位名稱 資料型別 描述 約束條件
_id ObjectId MongoDB自動生成的唯一識別符號 主鍵,自增長
message_id Number 訊息編號 自動生成,唯一
sender_id ObjectId 傳送者使用者ID 引用 users 集合的 _id,必填
receiver_id ObjectId 接收者使用者ID 引用 users 集合的 _id,必填
project_id ObjectId 關聯專案ID 引用 projects 集合的 _id,可選
message String 訊息內容 必填
timestamp DateTime 訊息傳送時間 自動生成
is_read Boolean 訊息是否已讀 預設值:false

示例文件

{
  "_id": ObjectId("64a7c4d8e1d3c4a7b8f0a127"),
  "message_id": 5001,
  "sender_id": ObjectId("64a7c0f4e1d3c4a7b8f0a123"),
  "receiver_id": ObjectId("64a7c2b6e1d3c4a7b8f0a125"),
  "project_id": ObjectId("64a7c1a5e1d3c4a7b8f0a124"),
  "message": "你好,我已經完成了推薦演算法的初步設計。",
  "timestamp": ISODate("2024-04-10T16:00:00Z"),
  "is_read": true
}

五、流程圖展示

六、資料流動圖


資料流動圖說明:

  • 使用者前端 建立一個新專案,填寫專案相關資訊。
  • 前端 將專案資料傳送到 後端API
  • 後端API 處理專案資料,並將其儲存到 專案集合(Projects) 中,同時更新 使用者集合(Users) 以關聯專案。
  • 後端API 將建立結果返回給 前端,前端更新使用者主頁以顯示新專案。

資料流動圖說明:

  • 傳送者使用者前端 輸入訊息併傳送。

  • 前端 將訊息資料傳送到 後端API

  • 後端API 處理訊息資料,並將其儲存到 訊息集合(Messages) 中。

  • 後端API 將訊息推送給 接收者使用者,並更新訊息狀態。

  • 接收者使用者前端 接收並顯示訊息。

    資料流動圖說明:

  • 使用者前端 修改個人資訊(如頭像、興趣等)。

  • 前端 將修改後的資料傳送到 後端API

  • 後端API 驗證並更新 使用者集合(Users) 中的相關欄位。

  • 後端API 將更新結果返回給 前端,前端顯示更新後的資訊。

七、後端關鍵程式碼及解釋

from django.contrib.auth import get_user_model
from django.db import models
from rest_framework import viewsets, permissions,filters
from .models import Project, ProjectMember, ProjectFile
from .serializers import ProjectSerializer, ProjectMemberSerializer, ProjectFileSerializer
from rest_framework.decorators import action,api_view
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser
from django.shortcuts import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from .permissions import IsOwnerOrAdmin
from django.db.models import Q
from rest_framework import status

User = get_user_model()
# 自定義許可權:只允許專案建立者或管理員刪除或修改專案
class IsOwnerOrAdmin(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.created_by == request.user or request.user.is_admin

# 專案檢視集

class ProjectViewSet(viewsets.ModelViewSet):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]
    parser_classes = [MultiPartParser, FormParser]

    def perform_create(self, serializer):
        # 建立專案並儲存專案描述圖片
        image = self.request.data.get('image')
        serializer.save(created_by=self.request.user, image=image)

    def perform_update(self, serializer):
        # 更新專案時也可以更新專案描述圖片
        image = self.request.data.get('image')
        serializer.save(image=image)

    @action(detail=True, methods=['GET'])
    def get_project_image(self, request, pk=None):
        # 獲取專案描述圖片的 URL
        project = self.get_object()
        if project.image:
            return Response({'image_url': project.image.url}, status=status.HTTP_200_OK)
        else:
            return Response({'error': 'No image found for this project.'}, status=status.HTTP_404_NOT_FOUND)

    def get_queryset(self):
        # 返回使用者建立的專案或該使用者作為成員參與的專案
        user = self.request.user
        return Project.objects.filter(models.Q(created_by=user) | models.Q(members__user=user)).distinct()



# 專案成員檢視集
class ProjectMemberViewSet(viewsets.ModelViewSet):
    queryset = ProjectMember.objects.all()
    serializer_class = ProjectMemberSerializer
    permission_classes = [IsAuthenticated]

    def get_queryset(self):
        # 返回當前使用者參與的專案成員資訊
        user = self.request.user
        return ProjectMember.objects.filter(user=user)

# 專案檔案檢視集
class ProjectFileViewSet(viewsets.ModelViewSet):
    queryset = ProjectFile.objects.all()
    serializer_class = ProjectFileSerializer
    parser_classes = [MultiPartParser, FormParser]
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        # 獲取專案並確保路徑使用 project_id
        project = get_object_or_404(Project, pk=self.request.data.get('project'))  # 獲取專案
        file_name = self.request.data.get('file_name')
        existing_file = ProjectFile.objects.filter(project=project, file_name=file_name).first()

        if existing_file:
            # 刪除現有檔案,允許覆蓋
            existing_file.file.delete(save=False)
            existing_file.delete()

        # 儲存檔案時使用 project_id 生成檔案路徑
        serializer.save(uploaded_by=self.request.user, project=project)

    def get_queryset(self):
        # 返回當前使用者參與的專案相關的檔案
        user = self.request.user
        return ProjectFile.objects.filter(project__members__user=user)

    def destroy(self, request, *args, **kwargs):
        # 刪除檔案時,只有檔案上傳者或管理員有許可權
        file = self.get_object()
        if file.uploaded_by == request.user or request.user.is_admin:
            return super().destroy(request, *args, **kwargs)
        else:
            return Response({'error': 'You do not have permission to delete this file.'}, status=403)


class ProjectSearchViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    filter_backends = [filters.SearchFilter]
    search_fields = ['project_name', 'description', 'field']  # 新增對 field 的支援

    def list(self, request, *args, **kwargs):
        # 獲取查詢引數中的搜尋關鍵字
        search_query = request.query_params.get('search', None)
        if search_query:
            keywords = [keyword.strip() for keyword in search_query.split(',') if keyword.strip()]
            filter_query = None

            # 構建查詢條件
            for keyword in keywords:
                if filter_query is None:
                    filter_query = self.get_queryset().filter(
                        project_name__icontains=keyword
                    ) | self.get_queryset().filter(
                        description__icontains=keyword
                    ) | self.get_queryset().filter(
                        field__icontains=keyword  # 新增對 field 的搜尋
                    )
                else:
                    filter_query |= self.get_queryset().filter(
                        project_name__icontains=keyword
                    ) | self.get_queryset().filter(
                        description__icontains=keyword
                    ) | self.get_queryset().filter(
                        field__icontains=keyword  # 新增對 field 的搜尋
                    )

            self.queryset = filter_query

        return super().list(request, *args, **kwargs)


@api_view(['GET'])
def recommend_students(request, project_id):
    # 獲取專案
    project = get_object_or_404(Project, project_id=project_id)
    project_field = project.field
    project_description = project.description
    project_name = project.project_name

    # 獲取所有使用者
    users = User.objects.filter(is_teacher=False)

    # 儲存推薦的學生
    recommended_students = []

    for user in users:
        user_major = user.major
        user_interests = user.interests.replace(',', ',').split(',')  # 假設興趣以逗號分隔
        print(f"Project Field: {project_field}, User Major: {user_major}, User Interests: {user_interests}")

        # 進行關鍵詞匹配
        if (user_major.lower() in project_field.lower() or
                any(interest.lower() in project_field.lower() for interest in user_interests) or
                any(interest.lower() in project_description.lower() for interest in user_interests) or
                project_name.lower() in [interest.lower() for interest in user_interests]):
            recommended_students.append({
                'name': user.name,
                'email': user.email,
                'student_id': user.student_id,
                'school': user.school,
                'major': user.major,
                'phone': user.phone,
                'interests': user.interests,
            })

    return Response(recommended_students, status=status.HTTP_200_OK)

@api_view(['GET'])
def recommend_teachers(request, project_id):
    # 獲取專案
    project = get_object_or_404(Project, project_id=project_id)
    project_field = project.field
    project_description = project.description
    project_name = project.project_name

    # 獲取所有使用者
    users = User.objects.filter(is_teacher=True)  # 只篩選老師

    # 儲存推薦的老師
    recommended_teachers = []

    for user in users:
        user_major = user.major
        user_interests = user.interests.split(',')

        # 進行關鍵詞匹配
        if (user_major.lower() in project_field.lower() or
                any(interest.lower() in project_field.lower() for interest in user_interests) or
                any(interest.lower() in project_description.lower() for interest in user_interests) or
                project_name.lower() in [interest.lower() for interest in user_interests]):
            recommended_teachers.append({
                'name': user.name,
                'email': user.email,
                'student_id': user.student_id,
                'school': user.school,
                'major': user.major,
                'phone': user.phone,
                'interests': user.interests,
            })

    return Response(recommended_teachers, status=status.HTTP_200_OK)

本專案採用Django和Django REST Framework(DRF)構建了一個校園專案招募應用的後端API。程式碼主要負責專案的管理、成員的管理、檔案的上傳與管理,以及推薦系統的實現。以下是對整體程式碼結構和功能的概述:

專案架構

  1. 模型層(Models)

    • Project:代表一個校園專案,包含專案名稱、描述、領域、建立者等資訊。
    • ProjectMember:關聯專案和使用者,表示使用者在專案中的角色和許可權。
    • ProjectFile:用於儲存與專案相關的檔案,支援檔案的上傳和管理。
  2. 序列化器層(Serializers)

    • ProjectSerializer:將Project模型例項序列化為JSON格式,或將JSON資料反序列化為Project例項。
    • ProjectMemberSerializer:處理ProjectMember模型的資料序列化與反序列化。
    • ProjectFileSerializer:負責ProjectFile模型的資料轉換,支援檔案上傳。
  3. 檢視集層(ViewSets)

    • ProjectViewSet:提供專案的CRUD(建立、讀取、更新、刪除)操作,支援專案圖片的上傳與更新,並實現專案圖片的獲取功能。
    • ProjectMemberViewSet:管理專案成員資訊,僅允許已認證的使用者訪問,並限制使用者只能檢視自己參與的專案成員。
    • ProjectFileViewSet:處理與專案檔案相關的操作,包括檔案的上傳、檢視和刪除。確保檔案的上傳者或管理員有許可權刪除檔案。
    • ProjectSearchViewSet:提供專案的搜尋功能,允許根據專案名稱、描述和領域進行關鍵詞搜尋。
  4. 許可權控制(Permissions)

    • IsAuthenticated:確保只有已認證的使用者才能訪問相關API。
    • IsOwnerOrAdmin:自定義許可權類,確保只有專案的建立者或管理員可以修改或刪除專案。
  5. 推薦系統(Recommendation APIs)

    • recommend_students:根據專案的領域、描述和名稱推薦符合條件的學生,基於學生的專業和興趣進行關鍵詞匹配。
    • recommend_teachers:類似地,根據專案需求推薦合適的老師,確保推薦的老師在專業和興趣上與專案匹配。

關鍵功能

  1. 專案管理

    • 建立與更新專案:使用者可以建立新的專案,並上傳專案描述圖片。在更新專案時,也可以更新相關的圖片。
    • 許可權控制:只有專案的建立者或管理員有許可權修改或刪除專案,確保專案的安全性和完整性。
    • 獲取專案圖片:提供API介面以獲取專案描述圖片的URL,方便前端展示。
  2. 成員管理

    • 管理專案成員:允許使用者檢視自己參與的專案成員資訊,支援新增或移除成員的功能。
    • 許可權驗證:確保只有認證使用者可以訪問成員資訊,增強資料的安全性。
  3. 檔案管理

    • 檔案上傳與儲存:支援使用者上傳與專案相關的檔案,確保檔案按專案ID進行路徑分類儲存,便於管理。
    • 檔案覆蓋與刪除:允許使用者覆蓋已有檔案,並確保只有上傳者或管理員可以刪除檔案,防止未經授權的操作。
  4. 搜尋與推薦

    • 專案搜尋:提供強大的搜尋功能,使用者可以根據專案名稱、描述或領域進行關鍵詞搜尋,快速找到感興趣的專案。
    • 推薦系統:根據專案的具體需求,智慧推薦符合條件的學生和老師,提升專案組建的效率和匹配度。

技術實現細節

  • Django REST Framework(DRF):利用DRF的ViewSets和Serializers簡化API的開發過程,提供了靈活且可擴充套件的介面設計。
  • 許可權管理:透過自定義許可權類和DRF的內建許可權,細化了對不同API的訪問控制,確保資料的安全性。
  • 檔案處理:採用MultiPartParserFormParser處理檔案上傳,結合Django的檔案儲存系統,確保檔案的有序管理和高效訪問。
  • 資料庫查詢最佳化:利用Django的Q物件進行復雜查詢,提升了資料庫查詢的效率和靈活性。
  • 推薦演算法:基於關鍵詞匹配的簡單推薦邏輯,透過遍歷使用者興趣和專業,篩選出與專案需求匹配的使用者,提升推薦的準確性。

八、前端關鍵程式碼及時

<script>
export default {
  data() {
    return {
      projectImage: '', // 儲存上傳的專案圖片
      projectName: '',
      projectDescription: '',
      projectField: '',
      licenses: ['GPL', 'MPL', 'BSD', 'MIT', 'Apache'],
      licenseIndex: 0,
      visibility: 'public',
    };
  },
  methods: {
    // 處理開源許可選擇
    onLicenseChange(e) {
      this.licenseIndex = e.detail.value;
    },
    // 設定專案可見性
    setVisibility(type) {
      this.visibility = type;
    },
    // 選擇並上傳圖片
    chooseImage() {
      uni.chooseImage({
        count: 1,
        sizeType: ['compressed'],
        sourceType: ['album', 'camera'],
        success: (res) => {
          const tempFilePath = res.tempFilePaths[0];
          if (tempFilePath) {
            this.projectImage = tempFilePath;
            console.log("選中的圖片路徑:", tempFilePath); // 列印圖片路徑,確保是本地路徑
          } else {
            console.error("未獲取到有效的圖片路徑");
          }
        },
        fail: () => {
          uni.showToast({
            title: '未選擇圖片',
            icon: 'none',
          });
        },
      });
    },
    // 提交專案
    submitProject() {
      // 驗證輸入
      if (!(this.projectName && this.projectDescription && this.projectField && this.projectImage)) {
        uni.showToast({
          title: '請完整填寫資訊並上傳圖片',
          icon: 'none',
        });
        return;
      }

      const is_private = this.visibility === 'private' ? 'True' : 'False';
      const token = uni.getStorageSync('authToken');

      // 獲取檔名並處理長度
      const fileName = this.projectImage.split('/').pop(); // 獲取檔名
      const maxFileNameLength = 100; // 最大檔名長度
      let newFileName = fileName;

      // 如果檔名超過最大長度,擷取檔名
      if (newFileName.length > maxFileNameLength) {
        newFileName = newFileName.substring(0, maxFileNameLength);
        console.warn(`檔名長度超出限制,已擷取為: ${newFileName}`);
      }

      // 使用 uni.uploadFile 上傳檔案
      uni.uploadFile({
        url: 'https://734dw56037em.vicp.fun/projects/projects/',
        filePath: this.projectImage, // 選擇的圖片路徑
        name: 'image', // 檔案的欄位名為 image
        formData: {
          project_name: this.projectName,
          description: this.projectDescription,
          field: this.projectField,
          license: this.licenses[this.licenseIndex],
          is_private: is_private,
          status: 'preparing',
          file_name: newFileName, // 將新檔名新增到表單資料
        },
        header: {
          'Authorization': `Token ${token}`,
        },
        success: (res) => {
          console.log("伺服器響應:", res.data); // 列印伺服器響應資料
          if (res.statusCode === 201) {
            this.handleSuccess();
          } else {
            this.handleError(res.data.error || '專案釋出失敗');
          }
        },
        fail: (err) => {
          console.error("請求失敗的詳細資訊:", err); // 列印失敗詳細資訊
          this.handleError('請求失敗,請稍後再試');
        },
      });
    },
    handleSuccess() {
      uni.showToast({
        title: '專案釋出成功',
        icon: 'success',
        duration: 2000,
        success: () => {
          setTimeout(() => {
            uni.switchTab({
              url: '/pages/home/home',
            });
          }, 2000);
        },
      });
    },
    handleError(errorMsg) {
      uni.showToast({
        title: errorMsg || '專案釋出失敗',
        icon: 'none',
      });
    },
  },
};
</script>

整體程式碼解釋

該Vue.js元件主要負責校園專案招募APP中專案建立的功能。它允許使用者填寫專案的基本資訊,包括專案名稱、描述和領域,選擇開源許可型別,設定專案的可見性(公開或私有),並上傳專案描述圖片。元件透過與後端API的互動,實現了專案資料的提交和儲存。

核心功能

  1. 資料管理

    • 專案基本資訊:元件定義了用於儲存使用者輸入的專案名稱、描述和領域的變數。這些資料透過雙向繫結與表單輸入元素關聯,確保使用者輸入能夠實時反映在元件的狀態中。
    • 開源許可選擇:提供了一組預定義的開源許可選項,使用者可以從中選擇合適的許可型別。選擇的許可型別透過索引記錄,以便在提交時傳遞給後端。
    • 專案可見性設定:允許使用者設定專案的可見性為公開或私有,儲存在visibility變數中,用於控制專案的訪問許可權。
    • 圖片上傳管理:使用者可以選擇並上傳專案描述圖片,元件將圖片的本地路徑儲存在projectImage變數中,以便在提交時上傳到伺服器。
  2. 使用者互動方法

    • 選擇並上傳圖片:透過呼叫裝置的圖片選擇功能,使用者可以從相簿或相機中選擇一張圖片。選中的圖片路徑被儲存,並在介面上顯示,確保使用者確認所選圖片。
    • 處理開源許可選擇:當使用者選擇不同的開源許可時,元件更新licenseIndex,以反映當前的選擇。
    • 設定專案可見性:使用者可以透過介面上的選項設定專案的可見性,元件相應地更新visibility變數。
    • 提交專案:在使用者填寫完所有必要資訊並選擇圖片後,元件執行提交操作。提交過程中,元件會驗證輸入的完整性,處理檔名長度限制,並透過認證Token將資料和圖片上傳到後端伺服器。
  3. 資料提交與處理

    • 輸入驗證:確保所有必填欄位(專案名稱、描述、領域和圖片)均已填寫,若未完成則提示使用者補全資訊。
    • 檔名處理:在上傳圖片前,元件檢查檔名長度是否超過限制(100個字元),若超過則擷取並警告使用者,以符合伺服器的要求。
    • 檔案上傳:使用uni.uploadFile介面將圖片和專案資料一併上傳到指定的伺服器URL。上傳過程中,攜帶使用者的認證Token以確保操作的安全性。
    • 響應處理:根據伺服器的響應狀態碼,元件分別處理成功和失敗的情況。成功時,顯示成功提示並自動跳轉到首頁;失敗時,顯示相應的錯誤資訊,提示使用者重新嘗試。
  4. 使用者反饋與導航

    • 成功提示:在專案成功建立後,元件透過彈出提示資訊告知使用者,並在短暫延時後自動跳轉到首頁,提升使用者體驗的連貫性。
    • 錯誤處理:在上傳過程中若發生錯誤,元件會顯示具體的錯誤資訊,幫助使用者瞭解問題所在並採取相應的措施。

整體工作流程

  1. 使用者輸入與選擇

    • 使用者在表單中輸入專案的名稱、描述和領域。
    • 使用者從開源許可選項中選擇適用的許可型別。
    • 使用者設定專案的可見性為公開或私有。
    • 使用者點選按鈕選擇並上傳專案描述圖片,圖片路徑被儲存並顯示在介面上。
  2. 專案提交

    • 使用者完成所有必要資訊的填寫後,點選“提交”按鈕。
    • 元件驗證輸入的完整性,確保所有必填欄位均已填寫。
    • 處理上傳圖片的檔名,確保其長度符合伺服器要求。
    • 使用uni.uploadFile將專案資料和圖片上傳到後端伺服器,附帶使用者的認證Token以確保操作許可權。
  3. 響應處理與反饋

    • 成功:若伺服器返回成功響應,元件顯示“專案釋出成功”的提示,並在短暫延時後自動跳轉到首頁。
    • 失敗:若上傳失敗,元件顯示具體的錯誤資訊,提示使用者重新嘗試或檢查輸入內容。

技術實現要點

  • 資料繫結與響應式設計:透過Vue.js的資料繫結機制,元件實現了使用者輸入與內部資料狀態的實時同步,確保介面與資料的一致性。
  • 檔案上傳處理:利用uni.chooseImageuni.uploadFile介面,元件實現了圖片的選擇與上傳功能,同時處理檔名的長度限制,確保上傳過程符合伺服器要求。
  • 許可權與安全:透過在上傳請求中附帶使用者的認證Token,元件確保只有經過認證的使用者可以建立專案,增強了系統的安全性。
  • 使用者體驗:透過即時的使用者反饋(如提示資訊和自動跳轉),元件提升了使用者操作的流暢性和滿意度。
  • 錯誤處理:元件具備完善的錯誤處理機制,能夠在上傳失敗時提供具體的錯誤資訊,幫助使用者瞭解問題並採取相應的措施。

4.附加特點設計與展示

專案檔案上傳功能詳解

在校園專案招募APP的開發過程中,檔案上傳功能是一個關鍵的模組。它不僅提升了使用者體驗,還確保了專案檔案的有序管理和安全儲存。本文將詳細介紹我們在實現該功能時的設計思路、實現過程、關鍵程式碼片段及最終成果展示。


設計與附加特點

創意設計與意義

我們設計的檔案上傳功能不僅支援基本的檔案上傳,還具備以下創意特點:

  1. 檔案覆蓋機制:當使用者上傳的檔名與現有檔案重複時,系統會自動刪除舊檔案,允許新檔案覆蓋。這一設計避免了檔名衝突,簡化了使用者操作流程。

  2. 專案關聯儲存:每個檔案上傳時都會與特定的專案關聯,並根據專案ID動態生成檔案儲存路徑。這種設計確保了檔案的有序管理,便於後續查詢和維護。

  3. 許可權控制:只有檔案的上傳者或管理員才能刪除檔案,增強了系統的安全性和資料的保護力度。

設計的意義

這些設計不僅提升了系統的可靠性和使用者體驗,還確保了檔案管理的高效性和安全性。透過自動處理檔案覆蓋和動態路徑生成,使用者無需擔心檔名重複或儲存混亂的問題;許可權控制則保障了檔案的私密性和安全性,防止未經授權的操作。


實現思路

在實現檔案上傳功能時,我們主要考慮了以下幾個方面:

  1. 模型設計:定義了ProjectFile模型,包含檔名、上傳者、關聯專案等欄位,確保檔案與專案的緊密關聯。

  2. 檢視集配置:利用Django REST Framework的ModelViewSet,建立了ProjectFileViewSet,實現檔案的CRUD操作。

  3. 許可權管理:自定義許可權類,確保只有上傳者或管理員可以刪除檔案,防止未授權的訪問和操作。

  4. 檔案上傳與儲存:使用MultiPartParserFormParser解析上傳的檔案,透過uni.uploadFile介面實現前端與後端的檔案傳輸。

  5. 錯誤處理與反饋:在檔案上傳和刪除過程中,提供詳細的錯誤資訊和使用者反饋,提升系統的穩定性和使用者體驗。


關鍵程式碼片段與解釋

以下是我們實現檔案上傳功能的關鍵程式碼片段及其解釋:

自定義許可權類
class IsOwnerOrAdmin(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        return obj.uploaded_by == request.user or request.user.is_admin

解釋:
IsOwnerOrAdmin許可權類確保只有檔案的上傳者或具有管理員許可權的使用者才能刪除檔案。這一許可權控制機制有效防止了未授權的檔案操作,保障了資料的安全性。

專案檔案檢視集
class ProjectFileViewSet(viewsets.ModelViewSet):
    queryset = ProjectFile.objects.all()
    serializer_class = ProjectFileSerializer
    parser_classes = [MultiPartParser, FormParser]
    permission_classes = [IsAuthenticated, IsOwnerOrAdmin]

    def get_queryset(self):
        project_id = self.request.query_params.get('project_id', None)
        if project_id:
            return ProjectFile.objects.filter(project_id=project_id)
        return ProjectFile.objects.none()

    def perform_create(self, serializer):
        project = get_object_or_404(Project, pk=self.request.data.get('project'))
        file_name = self.request.data.get('file_name')
        existing_file = ProjectFile.objects.filter(project=project, file_name=file_name).first()

        if existing_file:
            existing_file.file.delete(save=False)
            existing_file.delete()

        serializer.save(uploaded_by=self.request.user, project=project)

    def destroy(self, request, *args, **kwargs):
        file = self.get_object()
        if file.uploaded_by == request.user or request.user.is_admin:
            self.perform_destroy(file)
            return Response(status=204)
        else:
            return Response({'error': 'You do not have permission to delete this file.'}, status=403)

解釋:

  1. 許可權與解析器配置

    • permission_classes:結合IsAuthenticatedIsOwnerOrAdmin,確保只有經過認證的使用者且符合許可權條件的使用者才能操作檔案。
    • parser_classes:使用MultiPartParserFormParser,支援檔案的多部分表單資料上傳。
  2. 查詢集過濾

    • get_queryset方法根據傳入的project_id過濾檔案,確保使用者只能訪問與特定專案相關的檔案。
  3. 檔案上傳邏輯

    • perform_create方法在檔案上傳前檢查是否存在同名檔案,若存在則刪除舊檔案,允許新檔案覆蓋。
    • 確保上傳的檔案與當前使用者及關聯專案正確關聯,增強資料的一致性和安全性。
  4. 檔案刪除許可權控制

    • destroy方法透過許可權檢查,確保只有檔案上傳者或管理員才能刪除檔案,防止未授權的操作。

實現成果展示

透過上述設計與實現,我們成功開發了一個功能完善、安全可靠的檔案上傳模組。以下是該功能的實際應用效果展示:

  1. 檔案上傳流程

    • 使用者在專案詳情頁點選“上傳檔案”按鈕,選擇本地檔案進行上傳。
    • 上傳過程中,系統自動處理檔名衝突,確保新檔案覆蓋舊檔案。
    • 上傳成功後,檔案列表中實時顯示新上傳的檔案,使用者可以方便地檢視和管理專案檔案。
  2. 許可權控制效果

    • 檔案列表中,只有檔案的上傳者或管理員可以看到刪除按鈕。
    • 非上傳者嘗試刪除檔案時,系統會提示許可權不足,確保檔案安全。
  3. 動態路徑生成

    • 所有上傳的檔案按照專案ID分類儲存,檔案路徑清晰有序,便於管理和查詢。
    • 專案圖片和相關檔案儲存在相應的目錄中,避免檔案混亂。

5.APP功能展示

登入

註冊

釋出專案

修改專案

尋求夥伴

上傳檔案

搜尋

瞭解更多

私聊

個人中心

修改個人資訊

個人捐贈

6.目錄說明和使用說明

前端程式碼目錄

Software_Engineering_JOB1 專案目錄:

├── register
│   ├── id_test.vue            # 身份驗證頁面
│   ├── register.vue           # 使用者註冊頁面
│   ├── register_fail.vue      # 註冊失敗的提示頁面
│   └── register_success.vue   # 註冊成功的提示頁面
│
├── static                     # 靜態資源目錄
│   ├── avatars                # 使用者頭像檔案
│   ├── icons                  # 圖示資源
│   ├── login                  # 登入相關靜態資源
│   └── photo                  # 照片檔案
│
├── unpackage                  # 編譯後生成的打包檔案
│   └── dist                   # 打包後的專案檔案
│
├── App.vue                    # Vue.js 應用的根元件
├── index.html                 # 應用的入口 HTML 檔案
├── main.js                    # Vue.js 應用的入口 JavaScript 檔案
├── manifest.json              # 應用的配置檔案
├── pages.json                 # 頁面路由資訊
├── uni.promisify.adaptor.js   # Promise 介面卡檔案
└── uni.scss                   # 全域性樣式檔案

├── pages                      # 頁面目錄
│   ├── find_project           # 與專案查詢相關的頁面
│   │   ├── find_project.vue   # 查詢專案的頁面
│   │   └── know_more.vue      # 瞭解更多專案細節的頁面
│   │
│   ├── home                   # 主頁和相關功能的頁面
│   │   ├── find_friend.vue    # 查詢好友的頁面
│   │   ├── home.vue           # 主頁頁面
│   │   ├── look.vue           # 瀏覽頁面
│   │   ├── project_setting.vue # 專案設定頁面
│   │   ├── publish_project.vue # 釋出專案頁面
│   │   └── teammate.vue       # 團隊成員頁面
│   │
│   ├── login                  # 登入相關頁面
│   │   ├── login.vue          # 登入頁面
│   │   ├── password_different.vue # 密碼不一致的提示頁面
│   │   └── password_wrong.vue # 密碼錯誤的提示頁面
│   │
│   ├── message                # 訊息相關頁面
│   │   ├── contactDetail.vue  # 聯絡人詳情頁面
│   │   └── message.vue        # 訊息頁面
│   │
│   └── person                 # 使用者個人相關頁面
│       ├── about_us.vue       # 關於我們的頁面
│       ├── account_security.vue # 賬戶安全設定頁面
│       ├── donate.vue         # 捐贈頁面
│       ├── feedback.vue       # 反饋頁面
│       ├── person.vue         # 個人資訊頁面
│       ├── personal_info.vue  # 個人詳細資訊頁面
│       └── system_settings.vue # 系統設定頁面

後端程式碼目錄

102202135-102202146-main
│  manage.py                                      # Django專案的命令列工具,用於管理專案(如執行伺服器、遷移資料庫等)
│  README.md                                      # 專案說明檔案,包含專案簡介、安裝指南、使用說明等
│
├─chat                                           # 聊天模組,處理使用者之間的訊息交流功能
│  │  admin.py                                    # 配置Django admin介面,註冊聊天模型以便在後臺管理
│  │  apps.py                                     # 配置聊天應用的配置類
│  │  models.py                                   # 定義聊天模組的資料模型(如PrivateMessage, ChatInvitation)
│  │  tests.py                                    # 聊天模組的單元測試
│  │  urls.py                                     # 定義聊天模組的URL路由
│  │  views.py                                    # 定義處理聊天功能的檢視函式或類檢視
│  │  __init__.py                                 # 初始化聊天模組,使其成為一個Python包
│
├─media                                          # 媒體檔案儲存目錄,用於存放使用者上傳的頭像和專案相關檔案
│  ├─avatars                                     # 使用者頭像圖片儲存目錄
│  │  └─8                                         # 使用者ID為8的頭像儲存資料夾
│  │          222874a9-fcc7-436b-b230-d752d438a283.jpg  # 使用者8的頭像圖片
│  │
│  └─project_files                               # 專案相關檔案儲存目錄
│      ├─project1                                 # 專案1的檔案儲存資料夾
│      │      image7.jpg                           # 專案1的相關圖片檔案
│      │
│      └─test-project                             # 測試專案的檔案儲存資料夾
│              test_file.txt                       # 測試專案的一個文字檔案
│              test_file_fAdk5Xw.txt               # 測試專案的另一個文字檔案
│              test_file_P4O0b0m.txt               # 測試專案的另一個文字檔案
│              test_file_xgejsnB.txt               # 測試專案的另一個文字檔案
│              test_file_YDdMb5p.txt               # 測試專案的另一個文字檔案
│              test_file_YWEJG7F.txt               # 測試專案的另一個文字檔案
│
├─partners                                       # 合作伙伴模組,管理和處理專案合作相關功能
│  │  asgi.py                                     # ASGI配置檔案,用於部署Django應用(如非同步通訊)
│  │  settings.py                                 # 專案的全域性設定檔案,配置資料庫、已安裝應用、Middleware等
│  │  urls.py                                     # 定義專案級別的URL路由
│  │  wsgi.py                                     # WSGI配置檔案,用於部署Django應用(如傳統同步伺服器)
│  │  __init__.py                                 # 初始化合作夥伴模組,使其成為一個Python包
│
├─projects                                       # 專案管理模組,處理專案的建立、編輯、刪除以及專案成員的管理等功能
│  │  admin.py                                    # 配置Django admin介面,註冊專案模型以便在後臺管理
│  │  apps.py                                     # 配置專案應用的配置類
│  │  models.py                                   # 定義專案模組的資料模型(如Project, ProjectMember, ProjectFile)
│  │  permissions.py                              # 定義專案模組的許可權控制
│  │  serializers.py                              # 定義專案模組的資料序列化類,用於API介面
│  │  tests.py                                    # 專案模組的單元測試
│  │  urls.py                                     # 定義專案模組的URL路由
│  │  views.py                                    # 定義處理專案功能的檢視函式或類檢視
│  │  __init__.py                                 # 初始化專案模組,使其成為一個Python包
│
└─users                                          # 使用者管理模組,處理使用者註冊、登入、個人資訊管理和賬號安全等功能
    │  admin.py                                    # 配置Django admin介面,註冊使用者模型以便在後臺管理
    │  apps.py                                     # 配置使用者應用的配置類
    │  models.py                                   # 定義使用者模組的資料模型(如User)
    │  tests.py                                    # 使用者模組的單元測試
    │  urls.py                                     # 定義使用者模組的URL路由
    │  views.py                                    # 定義處理使用者功能的檢視函式或類檢視
    │  __init__.py                                 # 初始化使用者模組,使其成為一個Python包

7.單元測試

單元測試

在軟體開發過程中,單元測試是確保程式碼質量和功能正確性的重要環節。透過編寫和執行單元測試,我們能夠及時發現並修復程式碼中的缺陷,提升專案的穩定性和可靠性。本節將詳細介紹我們在校園專案招募APP中實施單元測試的過程,包括所選用的測試工具、學習方法、簡易教程、測試程式碼展示及測試資料構造思路。

一、單元測試工具及學習方式

選用的測試工具

我們選擇了PostmanDjango自帶的tests.py作為主要的單元測試工具。

  • Postman:主要用於測試API介面。透過傳送不同型別的HTTP請求,驗證後端邏輯的正確性和介面的穩定性。

  • Django tests.py:Django框架自帶的測試工具,支援建立單元測試和整合測試。透過編寫測試用例,確保應用程式的各個部分在程式碼更改後仍能正常工作。

學習單元測試的方式

  1. 官方文件

    • Django文件:詳細閱讀Django官方文件中關於測試的部分,瞭解如何使用tests.py編寫和執行測試用例。
    • Postman文件:學習Postman的使用方法,包括如何建立請求、編寫測試指令碼和自動化測試。
  2. 線上教程

    • 參加了Udemy和Coursera上的單元測試課程,透過系統的學習掌握了測試的基本概念和實踐技巧。
  3. 實踐專案

    • 在個人專案中不斷實踐,編寫和執行測試用例,逐步積累經驗,提升測試能力。


二、簡易教程

使用Django的tests.py進行單元測試

  1. 建立測試用例
    在應用的tests.py檔案中建立測試類,繼承自django.test.TestCase

    from django.test import TestCase
    from .models import MyModel
    
    class MyModelTests(TestCase):
        def test_model_creation(self):
            """測試模型的建立"""
            my_model = MyModel.objects.create(name='test')
            self.assertEqual(my_model.name, 'test')
    
  2. 執行測試
    使用以下命令在終端中執行測試:

    python manage.py test
    

使用Postman測試API

  1. 建立請求並設定請求型別(GET、POST等)。

  2. 輸入請求URL和引數

  3. 傳送請求並檢視響應,確認介面是否返回預期結果。

    示例

    • 啟動專案
      • 方法:POST
      • URLhttps://734dw56037em.vicp.fun/projects/projects/
      • Headers
        • Content-Type: application/json
        • Authorization: Token 58a903499e8125697e96e1ac1b425e9b7d90b05e
      • Body:form-data,包括imageproject_namefieldlicensedescriptionis_private等欄位。

三、展示專案部分單元測試程式碼及說明

以下是我們專案中的部分單元測試程式碼,以及對測試函式的說明。

Django單元測試

from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from .models import Project, ProjectMember, ProjectFile
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient

User = get_user_model()

class ProjectFileViewSetTests(TestCase):

    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(
            student_id='T123456789',
            name='Test User',
            school='Fuzhou University',
            email='testuser@fzu.edu.com',
            password='testpassword',
        )
        self.token, created = Token.objects.get_or_create(user=self.user)
        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)
        self.project = Project.objects.create(
            project_name='Test Project',
            description='A project for testing',
            field='Computer Science',
            license='GPL',
            status='preparing',
            is_private=False,
            created_by=self.user
        )
        self.file_upload_url = reverse('file-list')

    def test_file_upload(self):
        """測試檔案上傳功能"""
        with open('test_file.txt', 'w') as f:
            f.write('This is a test file.')

        with open('test_file.txt', 'rb') as f:
            response = self.client.post(
                self.file_upload_url,
                {
                    'project': self.project.id,
                    'file_name': 'test_file.txt',
                    'file': f,
                },
                format='multipart'
            )
        self.assertEqual(response.status_code, 201)
        self.assertTrue(ProjectFile.objects.filter(file_name='test_file.txt').exists())

說明

  • ProjectFileViewSetTests
    • setUp 方法:初始化測試環境,建立一個使用者、專案,並獲取認證Token。
    • test_file_upload 方法:測試檔案上傳功能。建立一個測試檔案,使用API客戶端上傳檔案,驗證響應狀態碼為201,並確保檔案被成功儲存到資料庫。

四、構造測試資料的思路

在構造測試資料時,我們考慮了以下幾方面,以確保測試的全面性和可靠性:

  1. 各種輸入情況

    • 正常情況:使用有效的使用者名稱、密碼、專案名稱、描述等資料進行測試,確保系統在正常條件下工作正常。
    • 異常情況:測試無效輸入,如空欄位、錯誤格式的資料(例如,錯誤的電子郵件格式)、檔名衝突等,驗證系統能否正確處理並返回相應的錯誤資訊。
    • 邊界情況:測試特殊字元、極長字串、多語言字元等,確保系統在處理邊界條件時穩定。
  2. 考慮將來測試人員的***難

    • 負面測試:主動提供無效或惡意資料(如SQL隱碼攻擊、跨站指令碼攻擊等)進行測試,確保系統能夠防禦潛在的安全威脅。
    • 多使用者併發:模擬多個使用者同時請求系統,測試系統在高併發情況下的表現和穩定性。
    • 異常處理:確保系統能夠優雅地處理各種異常情況,如伺服器錯誤、網路中斷等,並返回合適的錯誤訊息,提升使用者體驗。

透過這些測試資料構造思路,我們能夠覆蓋更多的使用場景和潛在問題,確保系統在各種情況下都能保持穩定和可靠。


單元測試在我們的校園專案招募APP開發過程中發揮了至關重要的作用。透過選擇Postman和Django自帶的tests.py作為主要測試工具,我們不僅驗證了API介面的正確性,還確保了應用程式在程式碼更改後的穩定性。透過系統的學習方法,包括閱讀官方文件、參加線上教程和實踐專案,我們逐步掌握了單元測試的核心技能。
在實際測試過程中,我們編寫了覆蓋關鍵功能的測試用例,構造了多種測試資料,考慮了各種正常和異常情況,甚至預設了將來可能遇到的挑戰。這些努力使得我們的專案能夠在功能完善和穩定性方面達到預期目標。

8.Github程式碼簽入記錄截圖

9.程式碼模組異常及解決方法

1. 使用者模型自定義導致的遷移問題

問題描述

在自定義使用者模型(User)時,初始遷移過程中出現錯誤,提示無法找到預設的使用者模型。這導致無法順利進行資料庫遷移,阻礙了專案的進展。

做過哪些嘗試
  • 檢查settings.py配置
    確認在settings.py中正確設定了AUTH_USER_MODEL = 'users.User'

  • 清理遷移檔案
    刪除所有應用的migrations目錄中的遷移檔案(保留__init__.py),重新執行makemigrationsmigrate命令。

  • 同步資料庫
    嘗試先建立一個超級使用者,再進行遷移操作。

  • 參考官方文件
    查閱Django官方文件和Djongo的相容性說明,確保自定義使用者模型的實現符合要求。

是否解決

部分解決。透過上述嘗試,我們成功地進行了一次遷移,但仍然遇到了一些欄位缺失和型別不匹配的問題。最終,透過調整模型欄位型別和重新配置Djongo連線引數,問題得以完全解決。

有何收穫
  • 深入理解了Django自定義使用者模型的配置流程。
  • 學會了如何排查遷移過程中的常見錯誤。
  • 增強了對Djongo與Django相容性問題的認識,並掌握了相應的解決方法。

2. 上傳圖片到伺服器失敗

問題描述

在實現圖片上傳功能時,使用者上傳圖片後,圖片未成功儲存到伺服器,導致使用者無法看到上傳的圖片。這影響了專案的使用者體驗和功能完整性。

做過哪些嘗試
  • 檢查模型配置
    確認ImageFieldupload_to引數正確配置,確保路徑生成邏輯無誤。

  • 驗證媒體配置
    檢查settings.py中的MEDIA_ROOTMEDIA_URL配置,確保媒體檔案路徑正確,並且Django有許可權寫入該目錄。

  • 除錯上傳檢視
    在上傳檢視中新增日誌輸出,確認接收到的檔案資料是否完整,以及儲存操作是否成功。

  • 檢查檔案許可權
    確認伺服器上的媒體檔案目錄具有適當的讀寫許可權,允許Django程序寫入檔案。

  • 測試不同檔案型別和大小
    嘗試上傳不同型別和大小的圖片,確定問題是否與檔案型別或大小有關。

  • 檢視伺服器日誌
    檢查伺服器日誌,尋找與圖片上傳相關的錯誤資訊,進一步定位問題原因。

是否解決

問題得到解決。透過以下步驟,我們最終解決了圖片上傳失敗的問題:

  1. 修正upload_to方法
    確保get_image_upload_path方法在專案建立後能夠正確獲取專案ID,從而生成正確的上傳路徑。

  2. 調整媒體目錄許可權
    修改伺服器上MEDIA_ROOT目錄的許可權,確保Django程序有寫入許可權。

  3. 驗證檢視邏輯
    確認上傳檢視正確處理檔案儲存,並返回成功響應給前端。

  4. 更新依賴包
    將Django和Djongo更新到相容的最新版本,修復潛在的bug。

有何收穫
  • 學會了如何系統性地排查檔案上傳失敗的各個環節,包括模型配置、媒體設定、伺服器許可權等。
  • 深入理解了Django的檔案儲存機制和ImageField的工作原理。
  • 掌握瞭如何透過日誌和伺服器除錯資訊快速定位和解決問題。
  • 增強了對伺服器檔案許可權管理的認識,確保應用能夠安全、穩定地處理檔案上傳。

3. 跳轉頁面失敗

問題描述

在完成某些操作(如表單提交或登入)後,頁面未能按照預期跳轉到目標頁面,導致使用者無法繼續後續流程。這影響了應用的流暢性和使用者體驗。

做過哪些嘗試
  • 檢查檢視邏輯
    確認在檢視函式或類檢視中,使用了正確的跳轉方法(如redirectHttpResponseRedirect),並且目標URL正確無誤。

  • 驗證URL配置
    檢查urls.py檔案,確保目標檢視的URL路由配置正確,且名稱匹配。

  • 除錯模板程式碼
    在模板中使用{% url 'target_view_name' %}時,確認檢視名稱和引數正確傳遞。

  • 檢視瀏覽器控制檯和網路請求
    使用瀏覽器的開發者工具,檢查跳轉請求是否成功傳送,以及是否有錯誤響應。

  • 檢查中介軟體配置
    確認Django的中介軟體配置沒有阻止或干擾跳轉行為,特別是認證和許可權相關的中介軟體。

  • 測試不同跳轉方式
    嘗試使用不同的跳轉方法或手動編寫JavaScript跳轉,確定問題是否與Django的跳轉方法相關。

  • 檢視伺服器日誌
    檢查伺服器日誌,尋找與跳轉相關的錯誤資訊,進一步定位問題原因。

是否解決

問題得到解決。透過以下步驟,我們最終解決了頁面跳轉失敗的問題:

  1. 修正檢視中的跳轉邏輯
    確認在檢視中使用了redirect('target_view_name'),並且目標檢視名稱在urls.py中正確配置。

  2. 更新URL配置
    修正urls.py中目標檢視的路由,確保URL名稱與檢視名稱一致,避免命名衝突。

  3. 調整模板程式碼
    確認模板中的跳轉連結使用了正確的URL名稱,並且傳遞了必要的引數。

  4. 移除或調整中介軟體
    識別並調整了阻止跳轉的中介軟體配置,確保跳轉請求能夠正常透過認證和許可權檢查。

  5. 測試和驗證
    透過多次測試,確保不同場景下的跳轉行為符合預期,並且沒有引入新的問題。

有何收穫
  • 學會了如何系統性地排查頁面跳轉失敗的各個環節,包括檢視邏輯、URL配置、模板程式碼等。
  • 深入理解了Django的URL路由和跳轉機制,確保檢視名稱和URL配置的一致性。
  • 掌握了使用瀏覽器開發者工具和伺服器日誌進行除錯的方法,快速定位和解決問題。
  • 提升了對Django中介軟體配置的認識,確保中介軟體不會干擾正常的應用流程。

10.評價

藍敏龍對施宇翔的評價

值得學習的地方

施宇翔在後端開發中展現了紮實的技術功底,特別是在資料庫設計和API介面開發方面,真的特別牛牛牛!能夠高效地解決問題,並且在遇到技術難題時,表現出很強的學習能力和適應性。此外,施宇翔在程式碼結構和模組化設計上做得很好,使得專案的後端部分清晰易懂。

需要改進的地方

在專案協作過程中,有時在溝通上稍顯保守,導致一些需求理解上存在偏差。建議在團隊討論中更加積極主動,及時分享進展和遇到的問題,以便團隊能夠更好地協調和支援。同時,在文件編寫方面,可以進一步詳細,幫助前端同學更方便地對接介面。互動真的難!

施宇翔對藍敏龍的評價

值得學習的地方

藍敏龍在前端開發方面表現出色,特別是在使用者介面設計和使用者體驗最佳化上。運用uniAPP,實現了介面美觀且操作流暢的效果。透過逐漸學習,對前端技術逐漸掌握,能夠高效地完成複雜的互動功能,提升了整個專案的可用性和吸引力。

需要改進的地方

在處理專案中的一些細節問題時,有時會花費較多時間,影響整體進度。建議在時間管理和任務優先順序上進行最佳化,以提高工作效率。此外,在我們溝通時,可以更加註重表達的清晰度,確保隊員能夠準確理解前端的需求和反饋。


相關文章