2024秋软件工程结对作业(第二次之程序实现)
项目内容 | 详细信息 |
---|---|
这个作业属于哪个课程 | 软件工程2024 |
这个作业要求在哪里 | 作业要求链接 |
这个作业的目标 | 实现校园项目招募APP的设计 |
姓名及学号 | 施宇翔 102202135 |
结对成员及学号 | 蓝敏龙 102202146 |
Github仓库地址 | (https://github.com/acedia7/102202135-102202146) |
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) |
| | | |
+-----------------+ +-----------------+
| |
| |
| |
+-----------------+ +-----------------+
| 用户设备 | | 数据库 (MySQl)|
+-----------------+ +-----------------+
二、技术栈选择与理由
-
前端框架 - uniAPP
- 跨平台支持:uniAPP支持编译到多个平台,如微信小程序、H5、iOS和Android,极大地提高了开发效率和用户覆盖面。
- 丰富的组件库:提供丰富的UI组件,便于快速构建用户界面。
- 强大的生态系统:拥有广泛的社区支持和插件资源,便于集成第三方功能。
-
后端框架 - Djongo
- Django优势:Djongo基于Django,继承了Django的强大功能,如ORM、认证系统、管理后台等,简化了后端开发。
- MySQL集成:Djongo允许使用MySQL作为数据库,适合处理非结构化数据,提供更高的灵活性和可扩展性。
- RESTful API支持:通过Django REST Framework,可以轻松构建符合REST标准的API接口。
-
数据库 - MySQL
- 高性能和可扩展性:适合大规模数据存储和高并发访问。
- 灵活的数据模型:支持文档存储,便于存储复杂的项目和用户数据。
三、模块划分与功能实现
系统主要划分为以下五个模块,每个模块的功能和实现细节如下:
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。代码主要负责项目的管理、成员的管理、文件的上传与管理,以及推荐系统的实现。以下是对整体代码结构和功能的概述:
项目架构
-
模型层(Models):
- Project:代表一个校园项目,包含项目名称、描述、领域、创建者等信息。
- ProjectMember:关联项目和用户,表示用户在项目中的角色和权限。
- ProjectFile:用于存储与项目相关的文件,支持文件的上传和管理。
-
序列化器层(Serializers):
- ProjectSerializer:将Project模型实例序列化为JSON格式,或将JSON数据反序列化为Project实例。
- ProjectMemberSerializer:处理ProjectMember模型的数据序列化与反序列化。
- ProjectFileSerializer:负责ProjectFile模型的数据转换,支持文件上传。
-
视图集层(ViewSets):
- ProjectViewSet:提供项目的CRUD(创建、读取、更新、删除)操作,支持项目图片的上传与更新,并实现项目图片的获取功能。
- ProjectMemberViewSet:管理项目成员信息,仅允许已认证的用户访问,并限制用户只能查看自己参与的项目成员。
- ProjectFileViewSet:处理与项目文件相关的操作,包括文件的上传、查看和删除。确保文件的上传者或管理员有权限删除文件。
- ProjectSearchViewSet:提供项目的搜索功能,允许根据项目名称、描述和领域进行关键词搜索。
-
权限控制(Permissions):
- IsAuthenticated:确保只有已认证的用户才能访问相关API。
- IsOwnerOrAdmin:自定义权限类,确保只有项目的创建者或管理员可以修改或删除项目。
-
推荐系统(Recommendation APIs):
- recommend_students:根据项目的领域、描述和名称推荐符合条件的学生,基于学生的专业和兴趣进行关键词匹配。
- recommend_teachers:类似地,根据项目需求推荐合适的老师,确保推荐的老师在专业和兴趣上与项目匹配。
关键功能
-
项目管理:
- 创建与更新项目:用户可以创建新的项目,并上传项目描述图片。在更新项目时,也可以更新相关的图片。
- 权限控制:只有项目的创建者或管理员有权限修改或删除项目,确保项目的安全性和完整性。
- 获取项目图片:提供API接口以获取项目描述图片的URL,方便前端展示。
-
成员管理:
- 管理项目成员:允许用户查看自己参与的项目成员信息,支持添加或移除成员的功能。
- 权限验证:确保只有认证用户可以访问成员信息,增强数据的安全性。
-
文件管理:
- 文件上传与存储:支持用户上传与项目相关的文件,确保文件按项目ID进行路径分类存储,便于管理。
- 文件覆盖与删除:允许用户覆盖已有文件,并确保只有上传者或管理员可以删除文件,防止未经授权的操作。
-
搜索与推荐:
- 项目搜索:提供强大的搜索功能,用户可以根据项目名称、描述或领域进行关键词搜索,快速找到感兴趣的项目。
- 推荐系统:根据项目的具体需求,智能推荐符合条件的学生和老师,提升项目组建的效率和匹配度。
技术实现细节
- Django REST Framework(DRF):利用DRF的ViewSets和Serializers简化API的开发过程,提供了灵活且可扩展的接口设计。
- 权限管理:通过自定义权限类和DRF的内置权限,细化了对不同API的访问控制,确保数据的安全性。
- 文件处理:采用
MultiPartParser
和FormParser
处理文件上传,结合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的交互,实现了项目数据的提交和存储。
核心功能
-
数据管理
- 项目基本信息:组件定义了用于存储用户输入的项目名称、描述和领域的变量。这些数据通过双向绑定与表单输入元素关联,确保用户输入能够实时反映在组件的状态中。
- 开源许可选择:提供了一组预定义的开源许可选项,用户可以从中选择合适的许可类型。选择的许可类型通过索引记录,以便在提交时传递给后端。
- 项目可见性设置:允许用户设置项目的可见性为公开或私有,存储在
visibility
变量中,用于控制项目的访问权限。 - 图片上传管理:用户可以选择并上传项目描述图片,组件将图片的本地路径存储在
projectImage
变量中,以便在提交时上传到服务器。
-
用户交互方法
- 选择并上传图片:通过调用设备的图片选择功能,用户可以从相册或相机中选择一张图片。选中的图片路径被存储,并在界面上显示,确保用户确认所选图片。
- 处理开源许可选择:当用户选择不同的开源许可时,组件更新
licenseIndex
,以反映当前的选择。 - 设置项目可见性:用户可以通过界面上的选项设置项目的可见性,组件相应地更新
visibility
变量。 - 提交项目:在用户填写完所有必要信息并选择图片后,组件执行提交操作。提交过程中,组件会验证输入的完整性,处理文件名长度限制,并通过认证Token将数据和图片上传到后端服务器。
-
数据提交与处理
- 输入验证:确保所有必填字段(项目名称、描述、领域和图片)均已填写,若未完成则提示用户补全信息。
- 文件名处理:在上传图片前,组件检查文件名长度是否超过限制(100个字符),若超过则截取并警告用户,以符合服务器的要求。
- 文件上传:使用
uni.uploadFile
接口将图片和项目数据一并上传到指定的服务器URL。上传过程中,携带用户的认证Token以确保操作的安全性。 - 响应处理:根据服务器的响应状态码,组件分别处理成功和失败的情况。成功时,显示成功提示并自动跳转到首页;失败时,显示相应的错误信息,提示用户重新尝试。
-
用户反馈与导航
- 成功提示:在项目成功创建后,组件通过弹出提示信息告知用户,并在短暂延时后自动跳转到首页,提升用户体验的连贯性。
- 错误处理:在上传过程中若发生错误,组件会显示具体的错误信息,帮助用户了解问题所在并采取相应的措施。
整体工作流程
-
用户输入与选择
- 用户在表单中输入项目的名称、描述和领域。
- 用户从开源许可选项中选择适用的许可类型。
- 用户设置项目的可见性为公开或私有。
- 用户点击按钮选择并上传项目描述图片,图片路径被存储并显示在界面上。
-
项目提交
- 用户完成所有必要信息的填写后,点击“提交”按钮。
- 组件验证输入的完整性,确保所有必填字段均已填写。
- 处理上传图片的文件名,确保其长度符合服务器要求。
- 使用
uni.uploadFile
将项目数据和图片上传到后端服务器,附带用户的认证Token以确保操作权限。
-
响应处理与反馈
- 成功:若服务器返回成功响应,组件显示“项目发布成功”的提示,并在短暂延时后自动跳转到首页。
- 失败:若上传失败,组件显示具体的错误信息,提示用户重新尝试或检查输入内容。
技术实现要点
- 数据绑定与响应式设计:通过Vue.js的数据绑定机制,组件实现了用户输入与内部数据状态的实时同步,确保界面与数据的一致性。
- 文件上传处理:利用
uni.chooseImage
和uni.uploadFile
接口,组件实现了图片的选择与上传功能,同时处理文件名的长度限制,确保上传过程符合服务器要求。 - 权限与安全:通过在上传请求中附带用户的认证Token,组件确保只有经过认证的用户可以创建项目,增强了系统的安全性。
- 用户体验:通过即时的用户反馈(如提示信息和自动跳转),组件提升了用户操作的流畅性和满意度。
- 错误处理:组件具备完善的错误处理机制,能够在上传失败时提供具体的错误信息,帮助用户了解问题并采取相应的措施。
4.附加特点设计与展示
项目文件上传功能详解
在校园项目招募APP的开发过程中,文件上传功能是一个关键的模块。它不仅提升了用户体验,还确保了项目文件的有序管理和安全存储。本文将详细介绍我们在实现该功能时的设计思路、实现过程、关键代码片段及最终成果展示。
设计与附加特点
创意设计与意义
我们设计的文件上传功能不仅支持基本的文件上传,还具备以下创意特点:
-
文件覆盖机制:当用户上传的文件名与现有文件重复时,系统会自动删除旧文件,允许新文件覆盖。这一设计避免了文件名冲突,简化了用户操作流程。
-
项目关联存储:每个文件上传时都会与特定的项目关联,并根据项目ID动态生成文件存储路径。这种设计确保了文件的有序管理,便于后续查找和维护。
-
权限控制:只有文件的上传者或管理员才能删除文件,增强了系统的安全性和数据的保护力度。
设计的意义
这些设计不仅提升了系统的可靠性和用户体验,还确保了文件管理的高效性和安全性。通过自动处理文件覆盖和动态路径生成,用户无需担心文件名重复或存储混乱的问题;权限控制则保障了文件的私密性和安全性,防止未经授权的操作。
实现思路
在实现文件上传功能时,我们主要考虑了以下几个方面:
-
模型设计:定义了
ProjectFile
模型,包含文件名、上传者、关联项目等字段,确保文件与项目的紧密关联。 -
视图集配置:利用Django REST Framework的
ModelViewSet
,创建了ProjectFileViewSet
,实现文件的CRUD操作。 -
权限管理:自定义权限类,确保只有上传者或管理员可以删除文件,防止未授权的访问和操作。
-
文件上传与存储:使用
MultiPartParser
和FormParser
解析上传的文件,通过uni.uploadFile
接口实现前端与后端的文件传输。 -
错误处理与反馈:在文件上传和删除过程中,提供详细的错误信息和用户反馈,提升系统的稳定性和用户体验。
关键代码片段与解释
以下是我们实现文件上传功能的关键代码片段及其解释:
自定义权限类
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)
解释:
-
权限与解析器配置:
permission_classes
:结合IsAuthenticated
和IsOwnerOrAdmin
,确保只有经过认证的用户且符合权限条件的用户才能操作文件。parser_classes
:使用MultiPartParser
和FormParser
,支持文件的多部分表单数据上传。
-
查询集过滤:
get_queryset
方法根据传入的project_id
过滤文件,确保用户只能访问与特定项目相关的文件。
-
文件上传逻辑:
perform_create
方法在文件上传前检查是否存在同名文件,若存在则删除旧文件,允许新文件覆盖。- 确保上传的文件与当前用户及关联项目正确关联,增强数据的一致性和安全性。
-
文件删除权限控制:
destroy
方法通过权限检查,确保只有文件上传者或管理员才能删除文件,防止未授权的操作。
实现成果展示
通过上述设计与实现,我们成功开发了一个功能完善、安全可靠的文件上传模块。以下是该功能的实际应用效果展示:
-
文件上传流程:
- 用户在项目详情页点击“上传文件”按钮,选择本地文件进行上传。
- 上传过程中,系统自动处理文件名冲突,确保新文件覆盖旧文件。
- 上传成功后,文件列表中实时显示新上传的文件,用户可以方便地查看和管理项目文件。
-
权限控制效果:
- 文件列表中,只有文件的上传者或管理员可以看到删除按钮。
- 非上传者尝试删除文件时,系统会提示权限不足,确保文件安全。
-
动态路径生成:
- 所有上传的文件按照项目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 # 打包的依赖项
│ ├── cache # 打包后的项目文件
│ ├── res # 打包后的icon
│ └── release # apk文件所在的位置
│
├── 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 # 测试项目的另一个文本文件
│ └─project_images # 项目描述图片存储目录
│ ├─20 # 项目20的图片存储文件夹
│ ├─... # 项目...的图片存储文件夹
│
├─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中实施单元测试的过程,包括所选用的测试工具、学习方法、简易教程、测试代码展示及测试数据构造思路。
一、单元测试工具及学习方式
选用的测试工具
我们选择了Postman和Django自带的tests.py
作为主要的单元测试工具。
-
Postman:主要用于测试API接口。通过发送不同类型的HTTP请求,验证后端逻辑的正确性和接口的稳定性。
-
Django
tests.py
:Django框架自带的测试工具,支持创建单元测试和集成测试。通过编写测试用例,确保应用程序的各个部分在代码更改后仍能正常工作。
学习单元测试的方式
-
官方文档:
- Django文档:详细阅读Django官方文档中关于测试的部分,了解如何使用
tests.py
编写和运行测试用例。 - Postman文档:学习Postman的使用方法,包括如何创建请求、编写测试脚本和自动化测试。
- Django文档:详细阅读Django官方文档中关于测试的部分,了解如何使用
-
在线教程:
- 参加了Udemy和Coursera上的单元测试课程,通过系统的学习掌握了测试的基本概念和实践技巧。
-
实践项目:
- 在个人项目中不断实践,编写和运行测试用例,逐步积累经验,提升测试能力。
- 在个人项目中不断实践,编写和运行测试用例,逐步积累经验,提升测试能力。
二、简易教程
使用Django的tests.py
进行单元测试
-
创建测试用例:
在应用的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')
-
运行测试:
使用以下命令在终端中运行测试:python manage.py test
使用Postman测试API
-
创建请求并设置请求类型(GET、POST等)。
-
输入请求URL和参数。
-
发送请求并查看响应,确认接口是否返回预期结果。
示例:
- 启动项目:
- 方法:POST
- URL:
https://734dw56037em.vicp.fun/projects/projects/
- Headers:
Content-Type
:application/json
Authorization
:Token 58a903499e8125697e96e1ac1b425e9b7d90b05e
- Body:form-data,包括
image
、project_name
、field
、license
、description
、is_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,并确保文件被成功保存到数据库。
四、构造测试数据的思路
在构造测试数据时,我们考虑了以下几方面,以确保测试的全面性和可靠性:
-
各种输入情况:
- 正常情况:使用有效的用户名、密码、项目名称、描述等数据进行测试,确保系统在正常条件下工作正常。
- 异常情况:测试无效输入,如空字段、错误格式的数据(例如,错误的电子邮件格式)、文件名冲突等,验证系统能否正确处理并返回相应的错误信息。
- 边界情况:测试特殊字符、极长字符串、多语言字符等,确保系统在处理边界条件时稳定。
-
考虑将来测试人员的***难:
- 负面测试:主动提供无效或恶意数据(如SQL注入、跨站脚本攻击等)进行测试,确保系统能够防御潜在的安全威胁。
- 多用户并发:模拟多个用户同时请求系统,测试系统在高并发情况下的表现和稳定性。
- 异常处理:确保系统能够优雅地处理各种异常情况,如服务器错误、网络中断等,并返回合适的错误消息,提升用户体验。
通过这些测试数据构造思路,我们能够覆盖更多的使用场景和潜在问题,确保系统在各种情况下都能保持稳定和可靠。
单元测试在我们的校园项目招募APP开发过程中发挥了至关重要的作用。通过选择Postman和Django自带的tests.py
作为主要测试工具,我们不仅验证了API接口的正确性,还确保了应用程序在代码更改后的稳定性。通过系统的学习方法,包括阅读官方文档、参加在线教程和实践项目,我们逐步掌握了单元测试的核心技能。
在实际测试过程中,我们编写了覆盖关键功能的测试用例,构造了多种测试数据,考虑了各种正常和异常情况,甚至预设了将来可能遇到的挑战。这些努力使得我们的项目能够在功能完善和稳定性方面达到预期目标。
8.Github代码签入记录截图
9.代码模块异常及解决方法
1. 用户模型自定义导致的迁移问题
问题描述
在自定义用户模型(User
)时,初始迁移过程中出现错误,提示无法找到默认的用户模型。这导致无法顺利进行数据库迁移,阻碍了项目的进展。
做过哪些尝试
-
检查
settings.py
配置:
确认在settings.py
中正确设置了AUTH_USER_MODEL = 'users.User'
。 -
清理迁移文件:
删除所有应用的migrations
目录中的迁移文件(保留__init__.py
),重新运行makemigrations
和migrate
命令。 -
同步数据库:
尝试先创建一个超级用户,再进行迁移操作。 -
参考官方文档:
查阅Django官方文档和Djongo的兼容性说明,确保自定义用户模型的实现符合要求。
是否解决
部分解决。通过上述尝试,我们成功地进行了一次迁移,但仍然遇到了一些字段缺失和类型不匹配的问题。最终,通过调整模型字段类型和重新配置Djongo连接参数,问题得以完全解决。
有何收获
- 深入理解了Django自定义用户模型的配置流程。
- 学会了如何排查迁移过程中的常见错误。
- 增强了对Djongo与Django兼容性问题的认识,并掌握了相应的解决方法。
2. 上传图片到服务器失败
问题描述
在实现图片上传功能时,用户上传图片后,图片未成功存储到服务器,导致用户无法看到上传的图片。这影响了项目的用户体验和功能完整性。
做过哪些尝试
-
检查模型配置:
确认ImageField
的upload_to
参数正确配置,确保路径生成逻辑无误。 -
验证媒体配置:
检查settings.py
中的MEDIA_ROOT
和MEDIA_URL
配置,确保媒体文件路径正确,并且Django有权限写入该目录。 -
调试上传视图:
在上传视图中添加日志输出,确认接收到的文件数据是否完整,以及保存操作是否成功。 -
检查文件权限:
确认服务器上的媒体文件目录具有适当的读写权限,允许Django进程写入文件。 -
测试不同文件类型和大小:
尝试上传不同类型和大小的图片,确定问题是否与文件类型或大小有关。 -
查看服务器日志:
检查服务器日志,寻找与图片上传相关的错误信息,进一步定位问题原因。
是否解决
问题得到解决。通过以下步骤,我们最终解决了图片上传失败的问题:
-
修正
upload_to
方法:
确保get_image_upload_path
方法在项目创建后能够正确获取项目ID,从而生成正确的上传路径。 -
调整媒体目录权限:
修改服务器上MEDIA_ROOT
目录的权限,确保Django进程有写入权限。 -
验证视图逻辑:
确认上传视图正确处理文件保存,并返回成功响应给前端。 -
更新依赖包:
将Django和Djongo更新到兼容的最新版本,修复潜在的bug。
有何收获
- 学会了如何系统性地排查文件上传失败的各个环节,包括模型配置、媒体设置、服务器权限等。
- 深入理解了Django的文件存储机制和
ImageField
的工作原理。 - 掌握了如何通过日志和服务器调试信息快速定位和解决问题。
- 增强了对服务器文件权限管理的认识,确保应用能够安全、稳定地处理文件上传。
3. 跳转页面失败
问题描述
在完成某些操作(如表单提交或登录)后,页面未能按照预期跳转到目标页面,导致用户无法继续后续流程。这影响了应用的流畅性和用户体验。
做过哪些尝试
-
检查视图逻辑:
确认在视图函数或类视图中,使用了正确的跳转方法(如redirect
或HttpResponseRedirect
),并且目标URL正确无误。 -
验证URL配置:
检查urls.py
文件,确保目标视图的URL路由配置正确,且名称匹配。 -
调试模板代码:
在模板中使用{% url 'target_view_name' %}
时,确认视图名称和参数正确传递。 -
查看浏览器控制台和网络请求:
使用浏览器的开发者工具,检查跳转请求是否成功发送,以及是否有错误响应。 -
检查中间件配置:
确认Django的中间件配置没有阻止或干扰跳转行为,特别是认证和权限相关的中间件。 -
测试不同跳转方式:
尝试使用不同的跳转方法或手动编写JavaScript跳转,确定问题是否与Django的跳转方法相关。 -
查看服务器日志:
检查服务器日志,寻找与跳转相关的错误信息,进一步定位问题原因。
是否解决
问题得到解决。通过以下步骤,我们最终解决了页面跳转失败的问题:
-
修正视图中的跳转逻辑:
确认在视图中使用了redirect('target_view_name')
,并且目标视图名称在urls.py
中正确配置。 -
更新URL配置:
修正urls.py
中目标视图的路由,确保URL名称与视图名称一致,避免命名冲突。 -
调整模板代码:
确认模板中的跳转链接使用了正确的URL名称,并且传递了必要的参数。 -
移除或调整中间件:
识别并调整了阻止跳转的中间件配置,确保跳转请求能够正常通过认证和权限检查。 -
测试和验证:
通过多次测试,确保不同场景下的跳转行为符合预期,并且没有引入新的问题。
有何收获
- 学会了如何系统性地排查页面跳转失败的各个环节,包括视图逻辑、URL配置、模板代码等。
- 深入理解了Django的URL路由和跳转机制,确保视图名称和URL配置的一致性。
- 掌握了使用浏览器开发者工具和服务器日志进行调试的方法,快速定位和解决问题。
- 提升了对Django中间件配置的认识,确保中间件不会干扰正常的应用流程。
10.评价
蓝敏龙对施宇翔的评价
值得学习的地方
施宇翔在后端开发中展现了扎实的技术功底,特别是在数据库设计和API接口开发方面,真的特别牛牛牛!能够高效地解决问题,并且在遇到技术难题时,表现出很强的学习能力和适应性。此外,施宇翔在代码结构和模块化设计上做得很好,使得项目的后端部分清晰易懂。
需要改进的地方
在项目协作过程中,有时在沟通上稍显保守,导致一些需求理解上存在偏差。建议在团队讨论中更加积极主动,及时分享进展和遇到的问题,以便团队能够更好地协调和支持。同时,在文档编写方面,可以进一步详细,帮助前端同学更方便地对接接口。交互真的难!
施宇翔对蓝敏龙的评价
值得学习的地方
蓝敏龙在前端开发方面表现出色,特别是在用户界面设计和用户体验优化上。运用uniAPP,实现了界面美观且操作流畅的效果。通过逐渐学习,对前端技术逐渐掌握,能够高效地完成复杂的交互功能,提升了整个项目的可用性和吸引力。
需要改进的地方
在处理项目中的一些细节问题时,有时会花费较多时间,影响整体进度。建议在时间管理和任务优先级上进行优化,以提高工作效率。此外,在我们沟通时,可以更加注重表达的清晰度,确保队员能够准确理解前端的需求和反馈。