结对作业(2/2)
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2020SpringW |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/2020SpringW/homework/10456 |
这个作业的目标 | 采用web技术,结合寒假第二次作业的成果实现原型设计中的部分功能 |
结对学号 | 221701233、221701234 |
作业正文 | https://www.cnblogs.com/sillyby/p/12492953.html |
其他参考文献 |
Github仓库地址: https://github.com/WallofWonder/InfectStatisticWeb
代码规范链接:https://github.com/WallofWonder/InfectStatisticWeb/blob/master/codestyle.md
演示地址:阿里云 (初次加载比较慢,请耐心等待)
成果展示
全国疫情概览:
- 展示现有确诊、现有疑似、现有重症、累计确诊、累计治愈、累计死亡等数据
- 提供现有确诊、累计确诊的全国地图可视化展示
切换地图数据
省份详情:
单击地图上的省份进入省详情页面:
折线图的数据切换:
向下滚动可查看该省各市的疫情数据:
未完成的扩展功能页面:
包括疫情新闻、同程查询、谣言鉴别
结对过程
作业发布后,我们首先讨论的是采用什么技术和框架来实现,我们决定采用springboot+vue的前后端分离技术来实现作业的要求,原因是我们其中一人在寒假学习过springboot,另一人学习过vue,而且都没有用学到的知识真正搭建过一个像样的东西,正好能拿这次作业练练手。
确定好所采用的技术之后,第一个问题是如何获取数据,为了方便拿到数据我们采用了天行数据的接口
我们根据所采用的技术指定了代码规范,接下来就是进行分工:
- 221701233:后端、后端部分文档博客撰写、博客整合和发布
- 221701234:前端、前端部分文档博客撰写、云服务器部署
之后,我们开始进行协作的磨合,由于负责前端的同学git还不是很熟练,两人之前也从未接触过正式的git协作,我们花了两天的时间进行git的知识储备和协作测试,大致摸清了协作流程之后,开始上手开发。
由于是前后端分离开发,我们可以同时进行项目的推进,并在开发过程中交流保持进度同步,互相测试代码的功能,并且共同解决一些难以解决的问题。
实现了基础功能后,进行紧张的代码复审和部署测试操作,在这个过程中遇到了许许多多的小毛病,但最后还是逐个解决了,并合作完成博客撰写。
结对过程截图
遇到的主要困难以及克服过程
前后端跨域问题
分析:经过查找资料,这应该是前后端分离所导致的问题,由于前后端分别运行在不同端口,之间的数据交互属于跨域,而因为浏览器收到同源策略(不同的域名, 不同端口, 不同的协议不允许共享资源的,保障浏览器安全。)的限制,当前域名的js只能读取同域名下的窗口属性。
解决方案:前端请求接口时加上http://头即可。
刻苦铭心的经历——代码丢失
起因:ddl的前一天,前端同学undo了一次commit,再次commit的时候github只提交了新增文件,而没有提交已有代码的修改,此时前端同学又从GitHub拉取了一次代码,导致对已有代码的修改丢失,一个下午的努力似乎付诸东流。
解决:大家都很紧张,乱了阵脚,熬夜寻找解决方案,最后的解决方法却十分简单——通过IDE的历史记录回滚恢复修改的代码,并再次commit,真的是虚惊一场。
针对该次事件的反思:可能表达语言水平有限,没法体现出我们当时心态有多崩,不过这个问题也反映出了我们对git的不熟练,导致这一星期许多时间都用在解决git多人协作的问题上了,严重拖慢了进度,只勉强完成了基础功能,为了后续的学习和团队实践我们两人都需要抓紧熟练git多人协作的使用了。
云服务器部署问题
前端同学在搭建过程中的探索已经总结并写成了一篇博客,在先贴上博客链接吧
https://www.cnblogs.com/QEEZ/p/12501197.html
主要的问题概括如下:
- 后端链接数据库被拒绝访问,解决方法:linux数据库配置问题,安装时没有配好
- 前端请求资源失败,解决方法:修改前端请求中的localhost为服务器的公网ip。
设计实现
我们决定采用springboot+vue的前后端分离技术来实现作业的要求。
确定好所采用的技术之后,第一个问题是如何获取数据,为了方便拿到数据我们采用了天行数据的接口,同时为了减轻第三方接口压力进行数据的持久化,前端发送至后端的请求是面向数据库的,而后端负责从第三方接口定时获取数据维护数据库,同时因为第三方接口返回的json数据繁杂,不符合前端需要的数据格式,后端需要对其进行过滤和整理。
在前端的界面设计上大致遵循原型设计的风格,决定采用ElementUI组件库美化界面,同时嵌入Echarts完成对图表的绘制。
关键代码——后端
请求第三方接口的数据
初始化数据库时,为了获取过去20天的数据,需要以较高的频率调用第三方接口获取数据,用同一个apikey高频调用会出现不稳定情况,于是使用三个apikey分担压力。
/**
* @param httpUrl 请求接口
* @param httpArg 参数
* @param tag apiKey选择标识
* @return 返回结果
*/
public static String request(String httpUrl, String httpArg, int tag) {
BufferedReader reader = null;
String result = null;
StringBuffer sbf = new StringBuffer();
String apiKey = "";
if (tag == 0) {
apiKey = API_KEY0;
}
else if (tag == 1) {
apiKey = API_KEY1;
}
else if (tag == 2) {
apiKey = API_KEY2;
}
httpUrl = httpUrl + "?key="
+ apiKey
+ ((httpArg == null) ? "" : httpArg);
try {
URL url = new URL(httpUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream is = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String strRead = null;
while ((strRead = reader.readLine()) != null) {
sbf.append(strRead);
sbf.append("\r\n");
}
reader.close();
result = sbf.toString();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
配置定时任务
为保证前端获取的数据的可靠性,设置每隔10分钟更新一次数据库。和启动时的初始化不同,定时任务只会更新当日数据,不会高频调用接口。
@Component
@Slf4j
public class ScheduledJob {
@Resource(name = "provinceServiceImpl")
ProvinceService provinceService;
@Resource(name = "cityServiceImpl")
CityService cityService;
@Resource(name = "nationServiceImpl")
NationService nationService;
private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* 每10分钟更新一次数据库
*/
@Scheduled(cron = "0 */10 * * * ? ")
public void cronJob() {
log.info("=========================== >> 更新数据库...");
updateNationData();
updateProvinceDataToday();
upadateCityData();
log.info("=========================== >> 数据库更新完成。");
}
/**
* 应用启动时更新数据库
*/
@PostConstruct
public void updateAtStart() {
log.info("=========================== >> 更新数据库...");
updateNationData();
updateProvinceData();
upadateCityData();
log.info("=========================== >> 数据库更新完成。");
}
...
}
定时任务的截图👆
控制层暴露接口
主要分为全国数据、省数据和市数据的获取接口
@Controller
@RequestMapping("/cities")
@Slf4j
public class CityController {
@Resource(name = "cityServiceImpl")
CityService cityService;
/**
* 获取某省的所有城市疫情
*
* @param province URL编码后的省名称
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("list/{province}")
@ResponseBody
public List<CityVO> getCities(@PathVariable String province) throws UnsupportedEncodingException {
String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
log.info("收到请求:/citieslist/" + decodedName);
return cityService.selectCities(decodedName);
}
}
@Controller
@RequestMapping("/nations")
@Slf4j
public class NationController {
@Resource(name = "nationServiceImpl")
NationService nationService;
/**
* 获取全国疫情数据
*
* @return
*/
@GetMapping("/all")
@ResponseBody
NationVO getNation() {
log.info("收到请求:/nations/all");
return nationService.select();
}
}
@Controller
@RequestMapping("/statistics")
@Slf4j
public class ProvinceController {
@Resource(name = "provinceServiceImpl")
ProvinceService provinceService;
/**
* 获取全国各省疫情数据
*
* @return
*/
@GetMapping("/provinces/{type}")
@ResponseBody
ProvinceMapVO getNationalProvinces(@PathVariable String type) {
log.info("收到请求:/statistics/provinces/" + type);
return provinceService.getNationalProvince(LocalDate.now(), type);
}
/**
* 获取某个省的数据
*
* @param province URL编码后的省名称
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("/provinces/one/{province}")
@ResponseBody
ProvinceVO getProvince(@PathVariable String province) throws UnsupportedEncodingException {
String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
log.info("收到请求:/statistics/one/" + decodedName);
return provinceService.selectByNameAndDate(decodedName, LocalDate.now());
}
/**
* 获取某省疫情趋势
*
* @param province URL编码后的省名称
* @param type 数据类型
* @return
* @throws UnsupportedEncodingException
*/
@GetMapping("/provinces/one/tends/{province}/{type}")
@ResponseBody
ProvinceTendencyVO getTendency(@PathVariable String province, @PathVariable String type)
throws UnsupportedEncodingException {
String decodedName = URLDecoder.decode(URLDecoder.decode(province, "UTF-8"), "UTF-8");
log.info("收到请求:/statistics/one/tends/" + decodedName + "/" + type);
return ProvinceMapper.mapToTendency(provinceService.selectByName(decodedName), type);
}
}
关键代码——前端
获取数据渲染地图
//提前设置关于地图的相关配置
drawLine () {
let that = this
// 基于准备好的dom,初始化echarts实例
that.myChart = this.$echarts.init(document.getElementById('myChart'))
that.myChart.showLoading()
that.myChart.on('click', that.getProvinces)
// that.myChart.off('click')
// 设置相关参数
that.option = {
// backgroundColor:'aliceblue',
dataRange: {
x: '5%',
y: '60%',
splitList: [
{start: 10000},
{start: 1000, end: 9999},
{start: 100, end: 999},
{start: 10, end: 99},
{start: 1, end: 9},
{start: 0, end: 0},
],
color: ['#660208', '#8c0d0d', '#cc2929', '#ff7c80', '#ffaa85', '#d9d9d9']
},
tooltip:{
trigger:'item',
formatter: '地区:{b}<br/>确诊:{c} 个'
},
series: [{
x:'center',
y:'top',
type: 'map',
map: 'china',
// roam: true,
label: {
normal: {
position: 'inside',
show: true,
fontsize: '10',
color: 'rgba(0,0,0,0.7)'
}
},
data: that.mydata
}]
}
}
//获取地图需要的数据,并且渲染地图
getData (url) {
let that = this
this.axios.get(url)
.then(function (response) {
let datas = response.data.provinces
for(var i=0;i<datas.length;++i){
that.mydata.push({
name: datas[i].name,
value: datas[i].value
})
}
//重要步骤,再次初始化option以刷新数据
that.drawLine()
that.myChart.setOption(that.option)
that.myChart.hideLoading()
}, function (err) {
console.log(err)
}).finally(function () {
//清空原有数据
that.mydata=[]
})
}
心路历程与收获
221701233(后端、后端部分文档博客撰写、博客整合和发布)
这一次结对编程是对上次结对原型的实现,上次结对作业中我描述了遇到的协作问题,之后经过老师的引导,我进行了反思,并在这次作业中进行了改进,在与队友交流的方面明显比上次高效很多,许多问题能得到清楚的描述和很快的响应。
不过在这次结对编程中还是暴露出了新的问题,比如磨合阶段的磕磕绊绊,花太多时间在细致末节的小问题上,面对严重的事故会乱了阵脚,导致只勉强实现了基本需求,归根结底还是对采用的技术不够熟悉,对规范的项目开发缺乏经验,需要长时间的实践加以学习进步。
对下面这个人的评价:
其实作业刚开始起步的时候,我对队友能否及时进入状态表示担心,要知道生活中他是一个有拖延症的宅233。但随着进度的推进,我发现队友能够很好地进入状态,能积极地为了实现需求而学习知识,在实践中成长,并且也能积极主动地和我沟通,因为他基础比较薄弱,产生的问题比较多,也经常要向我寻求帮助,某种程度上也是让我学到了很多。
221701234(前端、前端部分文档博客撰写)
这一次的结对作业,不仅仅是上一次的结对作业的延续,对我而言也是为接下来的团队作业打基础。虽然学了一小段时间的vue,但是仅仅停留于纸面上,只做过一点点小东西 ,可以说对于真正的项目开发还是毫无经验。也正是因为基础不好的缘故,在作业中遇到了不少问题,经过一番搜索学习,最后解决问题,在这过程中真切体会到了编程的乐趣,不断地解决遇到的一个又一个问题。
对上面那个人的评价:
我的结对队友,同时也是我的舍友兼好友,以我对他的了解,他是一个勤勉上进的人,我是一个有拖延症的人,事情喜欢拖着做,鉴于这次作业对于我这种基础不太好的人而言开头是很慢的,需要花费很多时间“摔倒再爬起”,正是他调动我的积极性,才使得我们得以如期完成我个人而言较为满意的作品。在外观上我大部都会询问他的意见来决定最终采取什么样的效果,以及开发过程中遇到的一些其他问题,我们也做了很多交流,在此再次感谢我的队友。