项目编号:BS-XCX-014
前言:
校园疫情防控系统是针对学校疫情防控管理的要求,为学校疫情防控设计的一款微信小程序,可以用于学生每日健康信息上报统计,助手于数字化疫情管理,包括健康信息上报、班级学生离校申请审批、校园通知、疫情地区可视化图表。
本文利用Spring Boot与uniapp技术,设计开发出的一个基于C/S架构的校园疫情防控微信小程序。在整个系统的开发周期中,系统后端选用Spring Boot框架,前端选用uniapp框架,持久层选用MYSQL数据库来实现。主要完成以下几个方面的研究:
1.分析在程序开发过程中所采用的C/S结构和B/S结构的优缺点,并确定系统的基本实行方案。
2.对功能需求详细分析,并且也对非功能需求以及系统运行环境进行简单分析,分析各个功能模块的设计和实现。
3.实现了学生打卡、学生请假、辅导员管理学生健康填报、辅导员审批学生假条、校园通知、班级信息管理、疫情可视化图表等功能。
一,项目简介
全国疫情频发,点多面广,形势很严峻,在这样的形势下,校园严格执行省、市的疫情防控政策。学生返校后,实行封闭式管理,学生非必要不准出校,学生长期封闭在校园难免会有不理解、抱怨、烦躁等负面情绪,为了让学生理解目前封校管理政策的正确性,也为了让学生积极配合学校防疫的措施,在学生处领导下,各班级积极组织学生配合学校疫情的防控工作开展。
疫情防控的意义,是为了使疫情更加稳定,了解更多防控意识,有能力去参加疫情防控的志愿者,帮助更多的人。防控意义在于保护好自己,保护好身边的人。疫情时刻牵动着大家的心,疫情是命令,防控就是责任,自愿积极投入到疫情防控工作当中去,用实际行动为打赢疫情防控阻击战贡献志愿的力量。
让大学生能够深刻认识战胜一切艰难险阻的根本保障,是实现中华民族伟大复兴的希望所在。引导大学生自觉拥护自觉成长,成为新时代社会主义的建设者和接班人;引导大学生把满腔的爱国热情转化为建设社会主义现代化祖国的强大动力,刻苦学习、练就本领、报效祖国,担当起青年一代的强国使命[1]。
基于Spring Boot的校园疫情防控系统的设计与实现主要实现了首页、登录、注册、健康填报、请假、批假、校园通知、班级管理、疫情图表等功能模块,系统结构图如下图所示[9]。
学生/辅导员点击打卡按钮,进行每日健康填报,填报的表单会获取地理位置进行定位,然后通过上传健康码、行程码和填写表单信息提交数据到打卡记录表,教师端则可以浏览该打卡记录信息[10]。实例如下图4-2-1所示。
图4-2-1 健康打卡数据流图
学生请假需要填写离校时间、返校时间、请假事由、请假去向、请假总时长,最终提交到教师端等待审批。实例如下图4-2-2所示。
图4-2-2 学生请假数据流图
辅导员收到学生请假申请时,对学生的请假信息进行审核,审核状态分为:通过、不通过,并且辅导员有权限直接撤销学生的请假条。实例如下图4-2-3所示。
图4-2-3 辅导员批假数据流图
校园通知是用于公告全校最新的消息,用户浏览后可以了解到校内的最新状况信息。实例如下图4-2-4所示。
图4-2-4 校园通知数据流图
该功能是学生查看自己所在班级包括自己在内的其他同学打卡状态,如果是辅导员则查看自己所有管理的班级里面学生的打卡状态,并可以通过电话号码通知学生。实例如下图4-2-5所示。
图4-2-5 班级列表数据流图
该功能模块是通用功能模块,学生和教师都可以浏览疫情相关信息,该功能包括疫情定位、疫情分布、疫情分析,其中疫情定位采用的是腾讯地图API,疫情分布图为湖南境内的城市分布,疫情分析是使用数据可视化图表展示。实例如下图4-2-6所示。
图4-2-6 疫情状况数据流图
该功能用于用户修改自己的个人信息,用户可以通过此功能修改自己的姓名、性别、头像、班级。实例如下图4-2-7所示。
图4-2-7 编辑个人信息数据流图
二,环境介绍
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
后台开发:Springboot+mybatis
前端开发:微信小程序
三,系统展示
本次校园疫情防控系统的实现基于C/S结构模式,http超文本传输协议,MySQL数据库引擎采用默认的innodb,接口规范采用Restful API风格[14]。
该系统实现分为登录、注册、首页、健康填报(打卡)、打卡记录、学生请假、请假记录、请假处理、校园通知、班级、疫情定位、疫情分布、疫情分析、个人信息[15]。
5.1 登录
用户通过输入电话号码和密码进行登录,调用LoginController类下的loginByPhone方法来实现登录,当输入格式都正确,该账号已存在的状态下,并且数据查询校验成功,即可登录成功到小程序首页界面。其登录界面展示如图5-1所示。
图5-1 登录界面展示图
5.2 注册
用户通过输入手机号码、姓名、密码、性别、年级、班级、辅导员进行注册,调用UserController类下的addUserInfo方法来实现注册,当输入格式都正确,点击注册,并且后台插入数据成功,即可注册成功并跳转到小程序登录界面。其注册界面展示如图5-2所示。
图5-2 注册界面展示图
5.3 首页
如果是学生端首页展示打卡、打卡记录、请假、请假记录、校园通知,如果是教师端首页展示打卡、打卡记录、请假处理、学生打卡记录、校园通知。其首页界面展示如图5-3所示。
图5-3 首页界面展示图
5.4 健康填报(打卡)
学生或者教师可以每日进行健康打卡,系统会记录每天健康填报信息。当进入打卡界面时,系统自动获取当前地理位置,然后用户需要输入体温,选择健康码颜色,上传健康码和行程码即可进行每日的健康打卡。其打卡界面展示如图5-4所示。
图5-4 打卡界面展示图
5.5 打卡记录
用户可以指定日期查看自己的健康填报记录,默认查看所有的健康填报记录,健康填报记录信息包括用户姓名,打卡地点,体温,健康码,行程码。其打卡记录界面展示如图5-5所示。
图5-5 打卡记录界面展示图
5.6 学生请假
学生外出请假需要通过此功能进行请假审核,当学生请假申请后该请假条会发送给教师端进行审核处理,外出请假报备表单内容包括离校时间,返校时间,请假事由,请假去向,请假时长。其学生请假界面展示如图5-6所示。
图5-6 学生请假界面展示图
5.7 请假记录
请假记录功能用于记录学生的请假历史记录,默认为查看所有的请假记录,主要内容包括假条创建时间,申请人,请假去向,请假事由,学生年级,请假开始时间,请假结束时间,请假总时长。其请假记录界面展示如图5-7所示。
图5-7 请假记录界面展示图
5.8 请假处理
请假处理为教师端的功能,教师可以通过此功能审核学生端发来的请假申请,教师有权限进行审核,审核状态分为未审批,同意,不同意,并且教师可以直接撤销指定的请假申请。其请假处理界面展示如图5-8所示。
图5-8 请假处理界面展示图
5.9 校园通知
校园通知功能模块展示校园内最新的通告通知,多条信息列表分页展示,通过word文档转图片在页面展示。其校园通知界面展示如图5-9所示。
图5-9 校园通知界面展示图
5.10 班级
在教师端班级界面展示教师所管理的班级,在学生端则展示该学生自己所在班级,班级信息包括姓名、学号、电话、打卡状态,当选择仅查看未打卡人员时,会显示所有未打卡的成员,若当前用户角色是教师,则该用户可以通过选择指定的年级和班级查看班级信息,若当前用户角色是学生,则该用户无法选择年级和班级。其班级界面展示如图5-10所示。
图5-10 班级界面展示图
5.11 疫情定位
疫情定位功能使用腾讯地图API实现,在获取全国疫情数据后,通过地点调用腾讯地图API获取经度、纬度,然后在地图上标记出高风险城市。其疫情定位界面展示如图5-11所示。
图5-11 疫情定位界面展示图
5.12 疫情分布
疫情分布可查看湖南境内最新的疫情状况信息,界面顶部文字播报当日现有确诊病例的疫情信息,当点击每个城市地区时,可以显示该城市的现有确诊病例。其疫情分布界面展示如图5-12所示。
图5-12 疫情分布界面展示图
5.13 疫情分析
疫情分析使用数据可视化的方式,清晰地展示疫情数据信息的变化趋势,使得用户能够可观地分析疫情数据的变化,可视化图表包括山峰图、折线图,其中山峰图主要功能是统计湖南近五个月的疫情确诊数量,折线图主要功能是统计湖南近五个月的疫情确诊数量变化趋势。其疫情分析界面展示如图5-13所示。
图5-13 疫情分析界面展示图
5.14 个人信息
个人信息可以查看当前用户的主要信息,并可以修改当前用户的信息。其个人信息界面展示如图5-14所示。
图5-14 个人信息界面展示图
四,核心代码展示
package com.qzdl.controller; import com.qzdl.entity.SchoolRole; import com.qzdl.response.Result; import com.qzdl.service.LoginService; import com.qzdl.util.JwtUtil; import com.qzdl.util.RedisUtil; import com.qzdl.vo.LoginUserVO; import io.swagger.annotations.ApiOperation; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/user") public class LoginController { @Autowired private LoginService loginService; @Autowired private RedisUtil redisUtil; @PostMapping("/hello") public Result doHello(){ return Result.success(); } /** * 通过学号/工号登录 * @param loginUserVO * @return */ @ApiOperation(value = "通过学号/工号登录") @PostMapping("/login") public Result doUserLogin(@RequestBody LoginUserVO loginUserVO){ SchoolRole schoolRole = new SchoolRole(); BeanUtils.copyProperties(loginUserVO,schoolRole); SchoolRole user = loginService.login(schoolRole.getStudentId(), schoolRole.getPassword()); String token = JwtUtil.getToken(user.getStudentId().toString()); redisUtil.hset("token",schoolRole.getStudentId().toString(),token,60*60*24*2); Map<String,Object> data = new HashMap<>(); data.put("user",user); data.put("token",token); return Result.success().data(data); } /** * 通过电话号码登录 * @param schoolRole * @return */ @ApiOperation(value = "通过电话号码登录") @PostMapping("/loginByPhone") public Result doUserLoginByPhone(@RequestBody SchoolRole schoolRole){ SchoolRole user = loginService.loginByPhone(schoolRole.getPhone(), schoolRole.getPassword()); if (user == null) { return Result.failure(); } String token = JwtUtil.getToken(user.getStudentId().toString()); redisUtil.hset("token",user.getStudentId().toString(),token,60*60*24*2); Map<String,Object> data = new HashMap<>(); data.put("user",user); data.put("token",token); return Result.success().data(data); } public Result doAddImg(MultipartFile multipartFile){ return Result.success(); } }
package com.qzdl.system.controller; import com.qzdl.entity.SchoolRole; import com.qzdl.response.Result; import com.qzdl.system.service.ClassService; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.text.Collator; import java.util.List; import java.util.Locale; @RequestMapping("/classinfo") @RestController public class ClassController { @Autowired private ClassService classService; /** * 根据教师tid查询学生信息 * @param studentId * @return */ @ApiOperation(value = "根据教师tid查询学生信息") @GetMapping("/selectStudentInfoByTid") public Result selectStudentInfoByTid(Integer studentId) { List<SchoolRole> result = classService.selectStudentInfoByTid(studentId); result.sort((o1, o2) -> Collator.getInstance(Locale.CHINA).compare(o1.getStudentName(), o2.getStudentName())); return Result.success().data("result", result); } /** * 根据教师id、年级id、班级id查询学生信息 */ @ApiOperation(value = "根据教师id、年级id、班级id查询学生信息") @PostMapping("/selectStudentInfoBySidGidCid") public Result selectStudentInfoBySidGidCid(@RequestBody SchoolRole schoolRole) { List<SchoolRole> result = classService.selectStudentInfoBySidGidCid(schoolRole); result.sort((o1, o2) -> Collator.getInstance(Locale.CHINA).compare(o1.getStudentName(), o2.getStudentName())); return Result.success().data("result", result); } /** * selectStudentInfoByGidCid */ @ApiOperation(value = "根据年级id、班级id查询学生信息") @GetMapping("/selectStudentInfoByGidCid") public Result selectStudentInfoByGidCid(Integer gradeId, Integer classid) { List<SchoolRole> result = classService.selectStudentInfoByGidCid(gradeId,classid); result.sort((o1, o2) -> Collator.getInstance(Locale.CHINA).compare(o1.getStudentName(), o2.getStudentName())); return Result.success().data("result", result); } }
package com.qzdl.system.controller; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import com.qzdl.response.Result; import com.qzdl.util.RedisUtil; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @RequestMapping("/epidemic") @RestController public class EpidemicController { private final String key = "e4aee1f5e8b01103606a4d61cb98232d"; @Autowired private RestTemplate restTemplate; @Autowired private RedisUtil redisUtil; /** * 获取湖南境内的疫情数据 * @return */ @GetMapping("/getEpidemicDataInHunan") public Result getEpidemicDataInHunan() { if(redisUtil.sGet("jsonData").size() > 0){ System.out.println("jsonData缓存..."); return Result.success().data("jsonData", redisUtil.sGet("jsonData").toArray()[0]); } String url = "https://interface.sina.cn/news/wap/fymap2020_data.d.json"; ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); JSONObject jsonObject = new JSONObject(forEntity.getBody()); JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list"); //过滤得到湖南的数据 JSONArray jsonData = jsonArray.stream().filter(o -> ((JSONObject) o).get("ename").equals("hunan")).collect(Collectors.toCollection(JSONArray::new)); redisUtil.sSetAndTime("jsonData", 60*60*3, jsonData); return Result.success().data("jsonData", jsonData); } /** * 获取湖南境内的历史数据 * @return */ @GetMapping("/getEpidemicDataInHunanHistory") public Result getEpidemicDataInHunanHistory() { if(redisUtil.sGet("shanfengData").size() > 0){ System.out.println("shanfengData缓存..."); return Result.success().data("shanfengData", redisUtil.sGet("shanfengData").toArray()[0]); } String url = "https://voice.baidu.com/newpneumonia/getv2?from=mola-virus&stage=publish&target=trend&isCaseIn=1&area=湖南&callback=jsonp_1660908362757_64109"; ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); String[] strArr = StrUtil.subBetweenAll(forEntity.getBody(), "(", ")"); JSONObject jsonObject = new JSONObject(strArr[0]); JSONArray jsonArray = ((JSONObject) jsonObject.getJSONArray("data").get(0)) .getJSONObject("trendMonth").getJSONArray("list"); //monthData:每个月的数据 JSONArray monthData = ((JSONObject) jsonObject.getJSONArray("data").get(0)) .getJSONObject("trendMonth").getJSONArray("updateMonth"); //shanfengData:新增本土数据 JSONArray bentuData = ((JSONObject) jsonArray.stream().filter(item -> { return "新增本土".equals(((JSONObject) item).get("name")); }).collect(Collectors.toCollection(JSONArray::new)).get(0)).getJSONArray("data"); //新建一个jsonObject,用来存放每个月的数据 JSONArray shanfengData = new JSONArray(); for (int i = 0; i < monthData.size(); i++) { String jsonStr = "{\"name\":\"" + monthData.get(i) + "\",\"value\":" + bentuData.get(i) + "}"; //jsonStr转json对象 shanfengData.add(i, new JSONObject(jsonStr)); } redisUtil.sSetAndTime("shanfengData", 60*60*3, shanfengData); return Result.success().data("shanfengData", shanfengData); } /** * 定时获取全国疫情数据 * @return */ @ApiOperation(value = "定时获取全国疫情数据") @PostMapping("/getEpidemicCity") @Scheduled(cron = "0 10 20 * * ?") public void getEpidemicCity() throws InterruptedException { System.out.println("正在更新全国城市疫情数据..."); String url = "https://interface.sina.cn/news/wap/fymap2020_data.d.json"; ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class); JSONObject jsonObject = new JSONObject(forEntity.getBody()); JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("list"); //定义一个列表存储城市 List<String> province = new ArrayList<>(); for (Object o1 : jsonArray) { for (Object o2 : ((JSONObject)o1).getJSONArray("city")) { if (Integer.parseInt(((JSONObject) o2).get("econNum").toString()) > 0 && !"".equals(((JSONObject) o2).get("citycode").toString())){ String mapName = ((JSONObject) o2).get("mapName").toString(); province.add(mapName); } } } System.out.println("城市数量=》" + province.size()); //存储经纬度 List<String> locationList = new ArrayList<String>(); for (Object o : new JSONArray(province)) { Thread.sleep(50); String city = (String)o; String url2 = "https://restapi.amap.com/v3/geocode/geo?address=" + city + "&output=JSON&key=" + key; ResponseEntity<String> forEntity2 = restTemplate.getForEntity(url2, String.class); String jsonStr = forEntity2.getBody(); System.out.println("jsonStr = " + jsonStr); JSONObject jsonObject2 = new JSONObject(jsonStr); String location = (String) ((JSONObject)jsonObject2.getJSONArray("geocodes").get(0)).get("location"); locationList.add(location); } System.out.println("定时更新locationList=》" + locationList); //将获取的数据放到redis中 redisUtil.sSet("locationList",locationList); System.out.println("更新成功!!!..."); } @ApiOperation(value = "从缓存中获取全国疫情数据") @PostMapping("/getEpidemicCityByCache") public Result getEpidemicCityByCache(){ if(redisUtil.sGet("locationList").size() > 0){ return Result.success().data("locationList", redisUtil.sGet("locationList")); } return Result.failure(); } }
五,项目总结
疫情防控是我国基层治理能力的一次重要检验。我国的高校校园内外通常有明显的边界线,可以被视为一个大型的难以分割的管理单元。然而,高校校园普遍拥有普通居民社区的居住属性和其他机关社会企事业单位作为社会生产单位的生产属性,需要承担起相对较高的疫情防控压力,高校拥有的特点也正是防疫所面临的巨大挑战。
校园人员数量多,人员密度大。相比较于其他机关和企事业单位,高校需要管理大量的学生和教职工。尽管高校土地占地面积相对比较大,但呈现出所管辖范围内,整体人员高密度的特点。
同时,高校的主要实践活动都在校园内完成,教学工作的开展、日常办公、食堂用餐、宿舍休息通常都是在封闭性较强的环境中进行,不利于保持空气的流通。不仅如此,校园内的人员活动在校园中并不是平均分布的,而是高度集中分布的,如白天的教学时间,多数师生集中于教室当中,为保证教学的工作开展,大多数课堂里的每个人员相邻就坐,每间教室的人数从数十人到百人不等;晚上则集中于每间寝室中,一个房间内人员数量往往在4人以上,高于普通社区住宅人数。这一系列的特点决定了在高校开展疫情防控工作最有效的手段仍然是将病毒拒之校门外[2]。