139
社区成员
这个作业属于哪个课程 | 2022年福大-软件工程、实践-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践结对作业二 |
这个作业的目标 | 1、fork仓库,和伙伴商讨协作细节等 2、编程实现 3、撰写博客 |
结队成员学号 | 061900408 加一、221900239 树一 |
其他参考文献 | 参考文献见于文末 |
引用 《阿里规约》 中的一段话:
对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。
代码千万行,安全第一行;代码不规范,同事泪两行👩💻! 规范的目的是为了编写高质量的代码,让你的团队成员每天的心情都是愉悦的,大家在一起是快乐的。所以我们制定了如下的代码规范:
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | 1920 | 2290 |
• Analysis | • 需求分析 (包括学习新技术) | 300 | 500 |
• Design Spec | • 生成设计文档 | 60 | 90 |
• Design Review | • 设计复审 | 20 | 30 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
• Design | • 具体设计 | 30 | 45 |
• Coding | • 具体编码 | 1330 | 1450 |
• Code Review | • 代码复审 | 30 | 25 |
• Test | • 测试(接口测试,修改代码,提交修改) | 120 | 90 |
Reporting | 报告 | 120 | 170 |
• Test and use Repor | • 接口、使用报告 | 90 | 130 |
• Size Measurement | • 计算工作量 | 10 | 15 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 20 | 25 |
合计 | 2070 | 2480 |
主要的UI设计
因为原型设计时对页面进行了精细的讨论和设计,所以本次项目我们决定继续沿用原型设计时的UI样式,降低UI设计时的工作量。这次设计的网站是体育赛事网站,考虑到网页的特点,我们决定使用顶端导航栏
+内容栏
+页脚
的方式来呈现
首页
首页实现了轮播图
来展示冬奥会的精彩瞬间。用户可以点击切换按钮
或轮播图下的小图标
来切换自己感兴趣的图片。
奖牌总榜
奖牌总榜我们采用图表的形式向用户展示了2022届北京冬奥会各国代表团的奖牌数以及最终排行,并高亮了中国代表团。
赛程页面
我们实现了一个赛程选项卡
供用户查看北京冬奥会的赛程信息,其中选项卡分别为:赛程
、中国队赛程
。用户可以通过切换选项卡来切换想要查询的赛程页面。
同时,用户还可以通过点击下拉框
选择赛程的搜索条件,通过切换比赛日期
、项目
、场馆
的形式来筛选用户希望查看的赛程
按照日期查询
按照项目查询
按照场馆查询
中国队赛程页面
冬奥历史
我们通过地图
的形式展示历年冬奥会的吉祥物和奖牌图片
,用户可以移动鼠标查看举办过冬奥会的国家的吉祥物和奖牌介绍。
世界地图
的形式直观地展示各个参赛国家的奖牌数,在地图上不同的颜色代表了不同的奖牌数区间,而颜色的深浅则代表了奖牌数的多少。当用户将鼠标移动到某一个国家上时,国家的色块将会高亮
并显示奖牌信息。由于最近疫情线上授课,所以我们这次结对作业沟通的主要场所是在宿舍,遇到问题时大部分时间采用面对面沟通,晚上休息时间以后在线上继续交流。
前期任务分工
因为树一同学之前有接触过一点Vue框架的知识再加上树一同学对后端代码实在是不感冒,所以决定由树一同学来完成前端部分的开发。
加一同学在之前的项目合作当中经常主动包揽下比较繁琐的后端开发功能,对Java的应用也比较熟练,所以本次结对项目就由加一同学负责后端代码的编写。 (加一同学是天使!有困难她真的上)
我们经过讨论后,决定采用vue+servlet+tomcat
进行本次结对项目的开发
相关教程收集
在项目实现的过程中我们会互相分享相关的教程,让项目更好地推进下去,提高开发的效率。具体教程参见附录中的参考文献。
相关细节和问题探讨
在项目实现过程中我们会对出现的问题和相关的细节进行及时地讨论和处理,比如对方出现一些问题的时候,队友也会积极的帮忙寻找解决方案,并且也会对对方的一些内容进行思考,提出改进建议~
前端的实现过程:
采用技术: Vue框架
、axios库
、ECharts数据可视化库
设计思路: 本次作业的前端部分的要点主要是页面的设计、后台数据的获取和渲染以及奖牌地图和冬奥历史地图的实现。
纯CSS
来完成。(事实证明,手打CSS真的太折磨了呜呜!)基于 promise 的 HTTP 库axios
向后端接口发送网络请求来获取。后台传送的数据是对象数组,所以我使用一个数组
来存放后台返回的结果数组。由于本次页面需要数据的部分主要是table
标签,所以我使用v-for
指令进行表格数据的渲染。ECharts库
在页面中显示世界地图,所以我们通过导入world.js和echarts.js文件并修改相关参数实现世界地图功能。后端的实现过程:
采用技术: tomcat
(Web轻量级应用服务器)、servlet
(Java的Web服务器端技术)、mysql
(关系型数据库管理系统)
设计思路:
存入数据库
中,方便后续数据获取操作。API生成思路: 首先在IDEA中创建继承自HttpServlet的类
,方便后续生成API接口,对于奖牌榜数据,后端没有参数需要获取,所以直接返回全部数据。对于赛程部分的查找功能,当前端请求对应日期、项目、场馆、中国队的相关赛程数据时,通过request.getParameter方法
,获取参数值,再通过参数值利用Java编写查找语句
,传入数据库后返回需要的数据,编写成Json格式后响应给浏览器(客户端),形成请求
,生成API接口
。
流程图展示(以奖牌榜API接口为例):
前端问题:
series
的data
数据的value
为后台获取的奖牌数据,再在tooltip里重新formatter
一下data,使用html拼接得到规定格式的输出字符串作为新的formatter。tooltip
中的数据一直是undefined
状态,无法将奖牌榜数据输出。我搜索了很多教程依然百思不得其解,最后尝试将国家名称修改为英文后数据就出现了。原来问题出现的原因是item对象的name属性
的中英文问题。我从后台获取的数据是中文的国家名称,而世界地图中的name属性是英文的国家名称,这导致ECharts始终无法通过键值找到name属性对应的数据。name
属性设置为国家的英文名
,与地图的每个模块相对应。再用一个新的字段
存储中文的国家名,这样就解决了地图的映射问题。后端问题:
统一采用UTF-8编码
,然而!没想到呀!居然有一个漏网之鱼!在把数据拿出来响应给浏览器的时候,我们也应该告诉浏览器它应该采用什么编码方式和采用什么格式来读取数据。 问题描述: 因为选择的是阿里云服务器,其部署的是Linux系统
,但是由于没有学过Linux指令相关的内容,所以一开始我按照网络视频教程做的时候,新建了一个用户,使其只可以操作一个文件夹,并且通过SSH连接的也是此用户,导致后面我要在云服务上部署tomcat的时候,一直无法获得权限
,然后我再重新看了一遍教程,才发现是用户登录错误了。
解决方案: 通过root用户
登录,并且要把对应的端口号设置在安全组
中。
前后端交互问题:
问题描述: 因为是第一次写后端接口然后提供给前端,所以第一次写完后,通过在线测试,返回200 OK
后就直接将API给前端了,没有考虑跨域问题
,导致前端报错,无法连接。
解决方案: 通过返回的HttpServletResponse response
调用setHeader方法,设置允许跨域的主机地址。
共同问题:
git上传文件的问题
问题描述: 因为对git的操作还不够熟练,所以在上传代码的过程中出现了上传的项目代码结构错误、拉取远程仓库后本地文件丢失的问题。
解决方案: 通过寻找相关教程,我学会了使用git撤销上次提交以及将本地文件上传到git中指定分支的方法。
对于上传项目代码结构错误的问题,先使用git reset --hard HEAD^
命令将前一次的提交撤销,再根据正确的流程重新提交正确的代码,解决了问题。
对于拉取远程仓库后本地文件丢失的问题,需要注意在每次上传文件时先在空文件夹中拉取远程仓库内容,再将更改后的项目添加进来提交,避免原项目的文件丢失。(凡事留个心眼,要记得保存副本!)
奖牌总榜数据的映射:
奖牌总榜页面使用v-for
指令进行表格数据的渲染,同时使用v-bind
指令动态绑定class
实现中国代表团的高亮效果。
奖牌总榜数据通过axios库
向后端接口发送GET请求
来获取
//HTML部分代码
<tbody>
<tr v-for="li in list" style="display: table-row;" v-bind:class="{'china':li.countryname=='中国'}">
<td>{{li.level}}</td>
<td class="country"><img v-bind:src="li.image">{{li.countryname}}</td>
<td>{{li.gold}}</td>
<td>{{li.silver}}</td>
<td>{{li.bronze}}</td>
<td>{{li.total}}</td>
</tr>
</tbody>
//JavaScript部分代码
data:{
list:[],
},
mounted(){
var that=this;
axios({
url:..., //接口地址
method:'get',
}).then(response=>{
console.log(response.data);
that.list=response.data.medallist;
});
}
奖牌地图的实现:
奖牌地图使用ECharts
第三方数据可视化库导入world.js
实现。通过axios向后台发送GET
请求获取奖牌榜数据,将获取到的数据作为地图参数series的data
属性值。通过修改tooltip
对象的formatter
参数值更改鼠标悬浮时的标签格式。添加visualMap
组件显示渐变的图例效果。
ECharts地图关键参数设置
series: [
data:[], //图表数据来源
itemStyle:{
emphasis: { //鼠标移动或点击时的样式
borderColor:'#B0C4E2',
...
},
},
roam : false, //关闭鼠标缩放和平移漫游
zoom : 1.2, //设置地图缩放大小
}
],
visualMap : {//视觉映射组件
min : 1, //数值的上下限
max : 37,
textStyle : { //文本样式
fontSize : 15,
...
},
text : ['高', '低'],
realtime : false, //拖拽时实时更新
calculable : true, //显示拖拽用的手柄
inRange : { //颜色变化
color : [ '#d4deeb', '#B0C4E2','#7c9ad9']
}
},
// 鼠标悬浮、单击产生的效果
tooltip : {
show : true,
trigger : "item", //触发器
triggerOn : "mousemove|click", //触发事件为鼠标单击或悬浮
//悬浮框文本
formatter: function(params) {
if (params['data']!=null){ //相应国家的奖牌数据不为空
var medal="";
//按照指定格式输出奖牌信息
medal+='<strong>'+params['data'].countryname+'</strong><br/>金牌榜第'+params['data'].level+'名<br/>金牌:'+params['data'].gold+'<img src="image/goldlogo.png" style="width:20px; vertical-align: middle;float:right;">';
medal+='<br/>银牌:'+params['data'].silver+'<img src="image/silverlogo.png" style="width:20px; vertical-align: middle;float:right"><br/>';
medal+='铜牌:'+params['data'].bronze+'<img src="image/bronzelogo.png" style="width:20px; vertical-align: middle;float:right;">';
return medal;
}
},
textStyle : { ... },
... //其余样式信息
},
奖牌地图数据的获取
mounted(){
this.chart= echarts.init(
document.getElementById('medalsMap'), 'white', {renderer: 'canvas'});
this.getData(); //从后端接口中获取数据
},
methods:{
getData(){
axios({
url:"http://192.168.0.105:8080/pairProject_war_exploded/medal_api", //接口地址
method:'get',
}).then(response=>{
this.medalData = response.data.medallist.map((item) => {
let medal =new Object();
//根据接口数据设置奖牌数据对象的值
medal.name = item.country;
...
return medal;
});
//设置地图的数据源为获得的奖牌数据
option.series[0].data=this.medalData;
this.chart.setOption(option);
});
},
赛程数据的查询:
因为赛程信息的查询需要传递参数,所以使用form表单
结合下拉框和提交按钮
来提交查询的数据。同时我封装了一个函数来获取用户传入的查询参数,通过输入查询项
来获得表单传递的参数
//HTML部分代码
<form method=GET action="schedules.html" style="display:inline">
<select name="date">
<option value="fiat" selected="selected">选择比赛日期</option>
...
</select>
<button type="submit">查询</button>
</form>
//Javascript部分代码
//获取用户传入的查询参数
method:{
getQueryValue(queryName){
var query = decodeURI(window.location.search.substring(1));
var vars = query.split('&'); //通过&分割键值对
for(var i=0;i<vars.length;i++){
var pair = vars[i].split('=');
if(pair[0]==queryName){ //查询项名称如date等
return pair[1]
}
}
return null;
}
},
mounted(){
axios({
url:..., //赛程接口地址
method:'get',
params:{
item:this.getQueryValue("item"), //根据查询项获取用户输入的参数
}
}).then(response=>{
this.list=response.data.detaillist; //通过参数获取到相应的赛程数据
});
}
Json数据解析:
//功能描述:指令为schedule并且日期正确时的输出内容解析
//参数:指定的日期
//返回值:指定的日期,其当天的赛程信息集合
public static List<Json_Schedule.Data.match> schedule_analyse(String date) throws IOException {
//获取日程版数据
String path = "./src/data/schedule/"+date+".json";
//从data下面的赛程日期.json中获取对应冬奥会日期的赛程
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(path),"UTF-8"));
Gson gson = new Gson();
//将json内容一一对应赛程类
java.lang.reflect.Type type = new TypeToken<Json_Schedule>() {}.getType();
Json_Schedule json_define = gson.fromJson(bufferedReader , Json_Schedule.class);
//把json中赛程榜的内容放入matchList中方便获
List<Json_Schedule.Data.match> matchList = json_define.getData().getMatchList();取
bufferedReader.close();
//返回赛程榜所有内容,方便后续文件输写
return matchList;
}
数据存入数据库(以奖牌榜数据为例)
//功能描述:将奖牌榜的内容写入数据库
public static void write_medal() throws Exception {
//连接数据库
Connection con = new SqlCon().getcon();
//创建用于执行静态SQL语句并返回它所生成结果的对象
Statement s1 = con.createStatement();
//存放奖牌数据
List<Json_Medal.Data.medal> medalsList = total_analyse();
for (Json_Medal.Data.medal m:medalsList) {
//sql插入语句书写
String str1 = "INSERT INTO all_medal(gold,silver,bronze,level,total,countryname) VALUES ('"
+m.getGold()+"','"+m.getSilver()+"','"+m.getBronze()+"','"
+m.getRank()+"','"+m.getCount()+"','"+m.getCountryname()+"')";
s1.executeUpdate(str1); //执行SQL语句
}
}
形成API接口---解决跨域问题 (以生成奖牌榜数据为例)
//允许跨域的主机地址
response.setHeader("Access-Control-Allow-Origin", "*");
//允许跨域的请求方法GET, POST, HEAD等
response.setHeader("Access-Control-Allow-Methods", "*");
形成API接口---获取数据库中的数据
//连接数据库
SqlCon sqlCon = new SqlCon();
Connection getcon = null;
try {
//创建数据库连接
getcon = sqlCon.getcon();
//sql语句
String sql="select * from all_medal";
PreparedStatement preparedStatement = getcon.prepareStatement(sql);
//执行sql并且返回结果
ResultSet resultSet = preparedStatement.executeQuery();
//while循环next方法,判断是否还有数据,若有返回true,没有false
ArrayList<servlet_medal> list = new ArrayList<>();
while (resultSet.next()){
servlet_medal medal_list = new servlet_medal();
//从resultSet中取值,并且把数据封装到bean对象中(实体类)
medal_list.setGold(resultSet.getString("gold"));
medal_list.setSilver(resultSet.getString("silver"));
medal_list.setBronze(resultSet.getString("bronze"));
medal_list.setLevel(resultSet.getString("level"));
medal_list.setTotal(resultSet.getString("total"));
medal_list.setCountryname(resultSet.getString("countryname"));
medal_list.setImage(resultSet.getString("image"));
medal_list.setCountry(resultSet.getString("country"));
//最后添加到集合中
list.add(medal_list);
}
······
} catch (Exception e) {
e.printStackTrace();
}finally {
//sql执行完要记得释放连接
sqlCon.closeCon(getcon);
形成API接口---将数据响应给浏览器
//告诉浏览器使用什么方式进行解析
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
//将ArrayList转为JSON形式,方便前端解析
JSONArray js = JSONArray.fromObject(list);
//将所有返回数据进行键值对命名,方便前端获取数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("medallist", js);
//将数据以utf-8形式转换为string类型
String all_medal = new String(jsonObject.toString().getBytes(StandardCharsets.UTF_8), "UTF-8");
//获取流,将数据相应给客户端
PrintWriter writer = response.getWriter();
//写入数据
writer.write(all_medal);
//关闭流
writer.close();
}
}
树一同学的心路历程和收获:
“纸上得来终觉浅,绝知此事要躬行”
,学习不能只流于纸面知识,想要深入理解其中的道理,必须要亲自实践才行。 希望自己在以后的学习和工作生涯中,能够改掉在学习上的坏毛病,积极地实践所学的知识,这样才能保证学有所得!”路漫漫其修远兮“
,我还需要好好努力!加一同学的心路历程和收获:
自律是强者的本能!
这次我感觉自己有一个缺点是没有提前做好准备,应该提前了解有关任务需要掌握的知识,先去主动学习,而不是等到任务发布下来了,才去被动学习! 树一同学对加一同学的评价: 这一次的作业我们总结了上一次结对作业中存在的问题,一起努力改正。加一同学这次的效率提高了很多,时间安排也比原型设计时更加合理了!加一同学有任何问题都会积极解决,主动参与,不会埋怨。同时,这次作业由于工作量有点大,再加上本周的事情实在是太多了,程序又遇到了很多bug (bug生产者就是我!) ,我个人出现了比较消极的情绪。加一同学也没有抱怨,及时地安慰我稳定我的情绪,努力帮助我解决遇到的难题。我认为加一同学称得上是非常优秀的合作伙伴。总而言之,这次合作非常愉快的过程非常愉快,我也从加一同学身上学到了很多,对我来说是一段非常宝贵的财富!
加一同学对树一同学的评价: 树一同学yyds,和树一同学合作每次都很安心,因为树一同学都会很完整有质量的完成自己那部分的任务。虽然这次作业前端的任务量相对后端会大一点,但是在每日交流中还是可以看出,树一同学在时间安排上还是很合理的,并且也在一开始我们制定的时间范围内完成了任务。最后提一提树一同学的一个小小的不完美但是又很好的点,就是太过注重细节了!这次树一同学做前端,导致只剩下她一个人在抠细节,所以有时候树一同学就会陷入纠结之中,不过有时候树一同学会意识到这个问题,所以会选择两三个版本发给我,我们两个通过交流,迅速解决选择。
在这里回答一下邹欣老师的问题:
假如有另几个体育盛会 (全运会, 亚运会)需要做类似的工作, 那么我们的代码中,对于已结束的赛会,奖牌总榜以及赛程只需要修改一些数据元素名称并更改后台数据库中的数据就能够解决新的需求;对于未结束的赛会,可能会考虑使用网络爬取的方式来获得后台的数据。冬奥历史和奖牌地图模块可以根据需求更改为全运会历史地图等,只需要插入新的js地图资源并修改地图中的数据就可以实现。
在邹欣老师提出问题前,我们有尝试过用手机打开本次项目的链接,就已经发现手机中网站的样式会与电脑端存在一定的差距,当时我们就讨论过解决这个问题的方案。如果需要同时在手机端实现类似的功能,那需要使用响应式布局,使我们的网站能适应各种各样的屏幕大小,为不同终端的用户提供更加舒适的界面和更好的用户体验。
在这里回答邹欣老师的问题:(针对后端代码)
假如有另几个体育盛会(全运会、亚运会)需要做类似的工作,那么由于我们的前后端是分离的,对于后端来说,主要是接口提供的内容会进行一个变化,所以我这边主要针对API接口复用来回答上述问题。
因为我是通过解析GSON数据,然后将数据存入数据库中,再根据前端的需要,读取数据返回API接口的。1、这边解析GSON数据是通过生成Bean对象进行,所以只要重新生成一个对应数据的Bean对象,解析GSON数据的方法是可以复用的。2、数据存入数据库这块,主要也是针对SQL语句进行一个改写即可。 3、API接口的返回,本质上也是SQL语句的改写。
综上所述,在后端生成API接口这块,代码的复用率还是比较高的。
由于审核问题,导致之前的评论消失了,所以在这里回复一下汪老师的评论: