这个作业属于哪个课程 | 202302软件工程 |
---|---|
这个作业要求在哪里 | 结对第二次作业——编程实现 |
结对学号 | 222100421 & 222100426 |
这个作业的目标 | Git的合作、实现原型中的功能,项目部署到云服务器 |
其他参考文献 | 《构建之法》、Vue.js-渐近式框架、 SpringBoot 官方文档 |
目录
1. Git仓库地址和代码规范链接
2. PSP表格
PSP Stage | Chinese Translation | Estimated Time (minutes) | Actual Time (minutes) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
- Estimate | - 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 1645 | 1805 |
- Analysis | - 需求分析 (包括学习新技术) | 300 | 300 |
- Design Spec | - 生成设计文档 | 60 | 70 |
- Design Review | - 设计复审 | 50 | 45 |
- Coding Standard | - 代码规范 (为目前的开发制定合适的规范) | 30 | 35 |
- Design | - 具体设计 | 100 | 120 |
- Coding | - 具体编码 | 800 | 900 |
- Code Review | - 代码复审 | 60 | 75 |
- Test | - 测试(自我测试,修改代码,提交修改) | 50 | 45 |
Reporting | 报告 | 120 | 130 |
- Test Report | - 测试报告 | 30 | 40 |
- Size Measurement | - 计算工作量 | 5 | 6 |
- Postmortem & Process Improvement Plan | - 事后总结, 并提出过程改进计划 | 40 | 40 |
Total | 合计 | 1675 | 1835 |
3. 访问链接
3.1 项目地址
3.2 后端接口
4.成品展示
4.1 首页
- 设置了轮播图循环展示网站图片,下方有最新消息的卡片显示最近的新闻。
- 网站的页脚部分。
4.2 选手信息
展示所有的选手的信息,包括选手的国籍、姓名、性别、生日等,也爬取了对应国籍的头像和运动员的肖像,使界面更加丰富。[ 爬取行为仅用于课程教学]
4.3 每日赛程
以每一个大日期为一个分栏,展示对应日期下的赛事安排。每个赛事安排显示对应的时间和比赛的名字。对决赛做出颜色的强调,显示为橙色,初赛和半决赛为灰色。
4.4 比赛详细结果
点击对应日期下的赛事即可查看比赛的详细情况,包括选手的排名、国家、总分、年龄、落后多少分等详细信息。点击不同的按钮可以查看不同比赛的数据,如 Final 和 Preliminary:
4.5 奖牌榜
以每一行的形式展示国家的奖牌榜,包括金牌数、银牌数、铜牌数和国家的总数,显示对应国家的头像。
4.6 附加功能:了解更多
以图文的形式展示世锦赛的相关信息,引起对于世锦赛的兴趣。
5. 结对讨论过程
在开始项目之前,我们首先花了一些时间来评估不同的技术栈和框架。考虑到要展示选手信息、比赛赛况等内容。最终我们决定使用前端和后端分离的架构,选择Vue
作为前端框架,使用Spring Boot
作为后端框架。
5.1 讨论截图展示
结对伙伴宿舍比较近,更多的讨论是以面对面的形式。
6. 设计实现过程
6.1 后端
6.1.1 使用的技术及理由
后端这里我们使用了SpringBoot的框架进行开发,使用该框架可以带来许多开发的好处:
- 简化配置:SpringBoot提供了自动配置功能,可以根据项目的依赖和类路径自动配置应用>程序,减少了繁琐的配置过程。
- 内嵌服务器:SpringBoot内置了Tomcat、Jetty等常用的服务器,使得开发者无需手动配置服务器,只需运行应用程序即可。
- 快速启动:SpringBoot提供了快速的启动器(starter)和生产就绪的代码结构,可以快速搭建项目并开始开发,提高开发效率。
- 自动化配置:SpringBoot提供了大量的自动配置选项,简化了对组件的集成和配置过程。
6.1.2 主要任务
后端主要的任务就是爬取官网的数据(该爬取行为仅用于课程教学),然后对数据进行分析处理,最后将数据返回给前端。在开发前需要先从官网找到对应的获取数据的接口,如参数信息,资源请求路径等。
比如说对于运动员信息的接口,官网的地址是:
https://api.worldaquatics.com/fina/competitions/3337/athletes
,参数有gender
和countryId
分别代表运动员的性别和所属国家的Id,其他接口也是类似这样。
- 提供的接口有下面几个(已部署到云服务器):
6.1.3 实现
主要的架构就是MVC架构,由GameInfoController接收请求,GameInfoService处理业务逻辑,由于这里该项目中我们只是爬取数据进行处理,所以这里没有数据库操作,没有Dao层。由Service调用工具类JsonParseUtil将数据转换为Json格式,返回给前端,后端的难点就是对于Json数据的解析部分,对于接收请求和返回Json格式数据已由框架做好。
6.2 前端
6.2.1 使用的技术及理由
前端部分主要使用
Vue.js
框架进行开发,我们使用Vue框架的的理由是Vue简单易学、支持组件化开发、响应式数据绑定,且它十分灵活、性能优秀、社区支持和生态系统也很完善,有许多现成的组件可以使用,开发方便。
6.2.2 主要任务
开发的任务主要分为六大模块:首页、选手信息、每日赛程、比赛详细结果、奖牌榜、了解更多。每个模块进行组件化开发,如图每个文件夹代表对应的模块:
6.2.3 实现
主要通过axios发送http请求获取后端数据,获取数据后将数据渲染到模板上,实现的难点、重点就是要实现每个组件不同的样式,主要的任务在于CSS部分的编写。
6.3 功能结构图
7. 代码说明
7.1 前端
7.1.1 首页组件
使用Element-plus UI提供的轮播图组件
<div class="container">
<el-carousel height="570px" width="100%">
<el-carousel-item v-for="(image, index) in images" :key="index">
<img :src="image" class="carousel-image" alt="Slide" />
</el-carousel-item>
</el-carousel>
</div>
7.1.2 选手信息组件
有一个子组件 AthleteItem.vue
, 用来细致的展示一个运动员的信息
在index.vue中使用使用v-for
循环遍历运动员信息,以Props传递运动员信息,交给每个子组件去显示。
<template>
<div class="container">
<div class="header">
<ul>
... 省略展示的头信息
</ul>
</div>
<AthleteItem
v-for="athlete in athletes"
:key="athlete.fullName ? athlete.fullName : 'defaultKey'"
:athlete="athlete"
/>
</div>
</template>
其中获取数据的接口是:
export const getAthletesAPI = (() => {
return request({
url: '/api/athletes',
method: 'GET',
})
})
发送http请求的实例由工具类提供:
import axios from 'axios'
const httpInstance = axios.create({
baseURL: 'http://123.207.40.200',
timeout: 5000
})
export default httpInstance
7.1.3 每日赛程组件
子组件SchedulePanel.vue
负责展示每一天的比赛信息,接收index.vue中通过props传递的schedule数据并展示出来。
<template>
<div class="panel container">
<h2 class="date">
<strong>{{ schedule.date }}</strong>
</h2>
<div class="schedule-list">
<div class="list"
@click="handleClick(item)"
v-for="(item, index) in schedule.eventInfos"
:key="index"
>
<ul class="info">
<li class="color"></li>
<li class="time">
{{ item.time.slice(0, 5) }} {{ computeName(item.time) }}
</li>
<li><img src="@/assets/images/small.png" alt="" /></li>
<li class="discipline">{{ item.disciplineName }}</li>
<li :class="item.name === 'Final' ? 'final' : 'normal'">
{{ item.name }}
</li>
<li>{{ item.description }}</li>
</ul>
</div>
</div>
</div>
</template>
7.1.4 奖牌榜组件
主要展示奖牌的所有信息,包括排名、国家名称、各种奖牌数等。
<template>
<div class="wrapper container">
<ul>
<li class="color"></li>
<li class="rank font">{{ medal.rank }}</li>
<li class="image"><img :src="imgPath" alt="" /></li>
<li class="name font">{{ medal.countryName }}</li>
....
</ul>
</div>
</template>
数据的获取仍然调用提供的接口,在组件挂载完毕后发送请求获取数据:
<script setup>
import {getMedalsAPI} from '@/apis/medals'
import {ref, onMounted} from 'vue'
import MedalItem from './components/MedalItem.vue'
import MedalHeader from './components/MedalHeader.vue'
const medals = ref([])
const getMedal = async () => {
const res = await getMedalsAPI()
medals.value = res.data.data
};
onMounted(()=>{
getMedal()
})
</script>
7.1.4 比赛的详情组件
根据不同赛事显示不同的数据,这里使用到了Element-plus的表格组件el-table来显示,可以实现列的定制化,传入数据源给表格组件即可自动绑定。
<template>
<div>
<el-button
type="text"
@click="goBack"
style="font-size: 15px; margin-top: 15px"
>
返回每日赛程
</el-button>
</div>
<div>
<div class="button-container">
<el-button v-if="data['Preliminary']" @click="handleClick('Preliminary')" class="font normal">Preliminary</el-button>
<el-button v-if="data['Semifinal']" @click="handleClick('Semifinal')" class="font normal">Semifinal</el-button>
<el-button v-if="data['Final']" @click="handleClick('Final')" class="font orange">Final</el-button>
</div>
</div>
<div>
<el-table :data="tableData" style="width: 100%" :header-cell-style="{background:'#eef1f6',color:'#606266', fontWeight: 'bold'}" >
<el-table-column
v-for="column in columns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
>
<template v-if="column.prop === 'country'" #default="{ row }">
<div>
<img
:src="getCountryFlag(row.country)"
style="width: 20px; height: 20px; margin-right: 10px"
/>
<span>{{ row.country }}</span>
</div>
</template>
</el-table-column>
</el-table>
</div>
</template>
7.1.5 前端路由
一级路由是/
路由出口对应Layout组件,主要包括了Header和Footer,二级路由对应项目要求的几大模块,路由出口就是对应的模块的组件。
import { createRouter, createWebHistory } from 'vue-router'
import Layout from '@/views/Layout/index.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/', component: Layout,
children: [
{ path: '', component: () => import('@/views/Home/index.vue') },
{ path: 'athletes', component: () => import('@/views/Athletes/index.vue') },
{ path: 'medals', component: () => import('@/views/Medals/index.vue') },
{ path: 'more', component: () => import('@/views/More/index.vue') },
{ path: 'schedule', component: () => import('@/views/Schedule/index.vue') },
{ path: 'detail/:id' , component: () => import('@/views/Detail/index.vue') },
]
},
]
})
export default router
7.2 后端
7.2.1 选手信息接口
对应的实体类是Athlete.java
@CrossOrigin
@GetMapping("/athletes")
public Result<List<Athlete>> getRank(){
List<Athlete> athletes = gameInfoService.getAllAthletes();
return athletes == null ? Result.error("读取数据出错!") : Result.success(athletes);
}
7.2.2 每日赛程接口
对应的实体类是DailySchedule.java
@CrossOrigin
@GetMapping("/schedule")
public Result<List<DailySchedule>> getDailySchedule() {
List<DailySchedule> scheduleInfo = gameInfoService.getAllScheduleInfo();
return scheduleInfo == null ? Result.error("读取数据失败") : Result.success(scheduleInfo);
}
7.2.3 奖牌榜接口
对应的实体类是CountryMedal.java
, 接口同上风格类型,这里就贴上解析json的代码。
public static List<CountryMedal> parseAllCountryMedal(JSONObject jsonObject) {
if (jsonObject == null) return null;
JSONArray countries = jsonObject.getJSONObject("Medals")
.getJSONArray("SportMedals").getJSONObject(0).getJSONArray("Countries");
List<CountryMedal> countryMedals = new ArrayList<>();
for (Object countryObj : countries) {
JSONObject countryJson = (JSONObject) countryObj;
//提取数据
String countryName = countryJson.getStr("CountryCode");
int rank = countryJson.getInt("Rank");
int gold = countryJson.getJSONObject("Gold").getInt("Count");
int silver = countryJson.getJSONObject("Silver").getInt("Count");
int bronze = countryJson.getJSONObject("Bronze").getInt("Count");
int total = countryJson.getInt("TotalCount");
CountryMedal countryMedal = new CountryMedal(rank, countryName, gold, silver, bronze, total);
countryMedals.add(countryMedal);
}
return countryMedals;
}
7.2.4 比赛详情接口
对应的实体类是GameResult.java
, 使用@PathVariable 读取路径中的id参数,根据id爬取官网数据并解析。( 爬取行为仅限于课程使用 )
@CrossOrigin
@GetMapping("/event/{id}")
public Result<Map<String, List<GameResult>>> getEventDetail(@PathVariable("id") String id){
Map<String, List<GameResult>> eventDetails = gameInfoService.getEventDetail(id);
return eventDetails == null ? Result.error("读取数据出错!") : Result.success(eventDetails);
}
解析json的代码太长,就不贴了。
8. 心路历程与收获
8.1 心路历程
- 确定技术栈和框架
在开始项目之前,我们首先花了一些时间来评估不同的技术栈和框架。考虑到要展示选手信息、比赛赛况等内容。最终我们决定使用前端和后端分离的架构,选择Vue作为前端框架,使用Spring Boot作为后端框架。 - 规划项目结构
选择了合适的技术栈和框架之后,我们花了一些时间来规划项目的结构。我创建了dev分支,并且邀请队友加入仓库。创建了一个基本的文件夹结构,前端的代码位于学号根目录下。并且确定了各个模块之间的交互方式。 - 分配任务
考虑到这是一个团队作业,我和我的队友商讨了如何分配任务。我们决定采用敏捷开发的方式,将项目拆分成小的任务,并根据每个人的技能和兴趣来尽可能合理地分配任务。考虑到前端的内容还挺多,后端的同学完成后就加入前端一起开发。开发过程使用前后端联调来进行测试。
8.2 感想与收获
222100421
的感想与收获: 这个项目让我学会了如何使用Git进行团队合作开发,以及如何规划和管理一个完整的项目。我学会了如何使用前端和后端技术来构建一个完整的Web应用,熟悉使用了较前沿的开发框架,并且提高了自己的编程能力和解决问题的能力。还学习了使用Docker容器启动nginx部署前端应用。总的来说收获满满,是一次非常不错的开发体验。
222100426
的感想与收获:通过这个项目,我和我的队友之间建立了良好的沟通和合作机制。我们学会了如何分工合作、互相帮助,并且充分发挥了每个人的优势。这个项目不仅仅是一次作业,更是一次宝贵的实践经验。通过这个项目,我们更加深刻地理解了软件开发的流程和方法,对于将来的工作和学习都有很大的帮助。
8.3总结
完成这个项目是一次非常有意义的经历,我们从中收获良多。通过团队合作、技术实践以及项目规划,我们不仅提升了自己的技术能力,也培养了团队合作和沟通能力。这也是我们参与的一个完整的、从需求分析到原型设计再到编码实现、部署的一个完整的项目过程。我们相信这些经验和收获将对我们的未来发展产生重要影响。
9. 评价结对队友
222100421
评 222100426
: 在设计上注重细节,追求完美。学习知识的速度快,可以很快地理解后端的需求,高效的完成前端对应的功能。沟通能力强,可以很快地抓住问题的关键。
222100426
评 222100421
: 工作很高效,完成度高。做事细心,沟通能力也很强。对于难题有钻研精神,善于利用网上资源解决问题,学习速度快。