项目编号:BS-XX-131
前言:
目前全国的疫情防控整体上还是比较严峻的,国家主导的以人为本的动态清零政策,有效的遏制了疫情病毒的传播,但病毒的不断变异大大增加了防控的难度。而居民所处的社区管控一直是疫情防控的一线,社区管控的力度直接决定着全国整个疫情防控的成败。国家对于疫情的防控可以通过信息化和大数据对人的行踪进行有效的流调,通过信息化技术的应用大大降低了整体疫情防控的力度和患者排查的难度,但是处于一线的社区防控工作,在信息化的建设和应用上还是相对比较空白的,目前好多社区还是通过保安人工记录和统计社区人员的流动及相关信息,在此背景下,希望能够利用信息化技术,为社区防控开发一套管理系统,能帮助社区人员更好的管理社区居民疫情情况、进入情况,能够方便的实现对社区内各小区的人员情况有着更好的管理。本文就基于Springboot框架开发和实现一个应用于社区居民疫情防控管理系统的相关问题进行分析和研究。
一,项目简介
本次开发设计的社区疫情管理系统,主要为一线的社区人员更好的管理辖区内人员疫情风险信息提供信息化的管理手段。本系统基于Springboot+Vue开发实现了一个前后端分离的信息化管理系统。系统功能完整,界面简洁大方。系统用户主要分为居民用户和管理员用户,居民注册登陆后主要可以在线提交自己的体温信息、外出信息、行程信息等,管理员用户主要管理和查看这些基础的信息数据。
特色:
1.系统利用定时任务每天自动爬取百度的疫情统计数据并更新到本地的数据库表中,在本系统中可以查看国内的最新疫情数据。
2.使用SpringSecutiry权限管理框架来管理用户的权限身份
3.使用前后端分离开发模式,将前端和后台有效的分离,通过Axios向后台代理转发请求接口
4.使用Jsoup爬虫来爬取百度的实时疫情数据,并进行解析,将解析的数据保存到mysql数据库中
二,环境介绍
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
后台开发技术:Springboot+Mybatis-Plus+SpringSecutiry+定时任务Quartz
前端开发技术:Vue+ElementUI+Axios
三,系统展示
下面展示一下社区疫情防控系统:
前端用户注册
登陆
管理主界面
个人每天体温上报及查询
外出管理:高风险地区或体温异常者无法添加外出信息,也就是无法外出
行程管理
个人信息管理
管理员登陆
体温管理
外出管理
居民行程管理
社区和小区地址管理
用户管理
异常用户管理
四,核心代码展示
爬虫核心代码:
package com.gad.epidemicmanage.task; import com.gad.epidemicmanage.common.GlobalConstant; import com.gad.epidemicmanage.pojo.entity.RealTimeData; import com.gad.epidemicmanage.service.IRealTimeDataService; import com.gad.epidemicmanage.common.utils.CommonUtil; import com.gad.epidemicmanage.common.utils.HttpUnitUtil; import lombok.extern.slf4j.Slf4j; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * 获取国内疫情实时数据 */ @Slf4j @Component @DisallowConcurrentExecution public class RealTimeDataTask implements Job { @Resource private IRealTimeDataService realTimeDataService; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { int i = 0; int maxTimes = 5; while(i < maxTimes){ String html = HttpUnitUtil.getHtmlPage(GlobalConstant.REAL_TIME_URL); if ("".equals(html)) { log.error("国内实时数据获取页面失败"); return; }else { log.info("国内实时数据获取页面成功,开始第 "+ (i+1) +" 次分析"); } try{ Document document = Jsoup.parse(html); //获取模块节点 Element f1 = document.getElementById("ptab-0"); Element f2 = f1.child(0); //取地区class标签父节点 Element f3 = f2.child(0); //取数字父节点 Element f4 = f2.child(2).child(0); //获取标签属性值 String areaClassName = f3.child(0).attr("class"); String numClassName = f4.child(1).attr("class"); RealTimeData realTimeData = new RealTimeData(); //地区 Element e1 = document.getElementsByClass(areaClassName).get(0); realTimeData.setPlace(e1.text()); //现存确诊 Element e2 = document.getElementsByClass(numClassName).get(0); realTimeData.setExitDiagnosis(CommonUtil.strToNum(e2.text())); //累计确诊 Element e3 = document.getElementsByClass(numClassName).get(4); realTimeData.setCountDiagnosis(CommonUtil.strToNum(e3.text())); //境外输入 Element e4 = document.getElementsByClass(numClassName).get(5); realTimeData.setAbroad(CommonUtil.strToNum(e4.text())); //无症状 Element e5 = document.getElementsByClass(numClassName).get(1); realTimeData.setAsymptomatic(CommonUtil.strToNum(e5.text())); //现存疑似 Element e6 = document.getElementsByClass(numClassName).get(2); realTimeData.setExitSuspected(CommonUtil.strToNum(e6.text())); //现存重症 Element e7 = document.getElementsByClass(numClassName).get(3); realTimeData.setExitSevere(CommonUtil.strToNum(e7.text())); //累计治愈 Element e8 = document.getElementsByClass(numClassName).get(6); realTimeData.setCountCure(CommonUtil.strToNum(e8.text())); //累计死亡 Element e9 = document.getElementsByClass(numClassName).get(7); realTimeData.setCountDeath(CommonUtil.strToNum(e9.text())); //当天日期 realTimeData.setDate(CommonUtil.todayDate()); log.info("页面数据分析完毕,存入数据库"); realTimeDataService.addRealTimeData(realTimeData); log.info("国内实时数据已保存"); break; }catch (Exception e){ log.error("第 "+ (i+1) +" 次国内分析实时疫情数据异常:"+e); i++; } } } }
quarzt定时任务核心 代码:
package com.gad.epidemicmanage.task; import com.gad.epidemicmanage.service.IJobAndTriggerService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.JobDataMap; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Component; /** * @Description: 定义启动时配置定时任务 */ @Slf4j @Component @RequiredArgsConstructor public class TaskStart implements ApplicationRunner { private final ConfigurableApplicationContext context; private final IJobAndTriggerService jobAndTriggerService; @Override public void run(ApplicationArguments args) throws Exception { if (context.isActive()) { /** * 获取实时疫情数据,每天更新两次 */ JobDataMap map = new JobDataMap(); jobAndTriggerService.addJob("RealTimeDataTask", "com.gad.epidemicmanage.task.RealTimeDataTask", "default", "10 00 09,18 * * ? ",map); } } }
根据隔离天数到期自动将高危用户删除
package com.gad.epidemicmanage.task; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.gad.epidemicmanage.pojo.entity.States; import com.gad.epidemicmanage.service.IStatesService; import com.gad.epidemicmanage.service.IJobAndTriggerService; import lombok.extern.slf4j.Slf4j; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Slf4j @Component @DisallowConcurrentExecution public class HomeQuarantineDayTask implements Job { @Resource IStatesService statesService; @Resource IJobAndTriggerService jobAndTriggerService; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { Integer userId = jobExecutionContext.getJobDetail().getJobDataMap().getInt("userId"); States states = statesService.getOne(new LambdaQueryWrapper<States>() .eq(States::getUserId,userId)); //获取当前剩余隔离天数 int curDays = states.getHomeQuarantineDay(); //0天时删除定时任务 if (curDays == 0){ jobAndTriggerService.deleteJob("com.gad.epidemicmanage.task.HomeQuarantineDayTask", "default"); log.info("userId:" +userId + " 居家隔离已结束"); }else{ //减一天后重新存入 statesService.updateHomeQuarantineDay(userId,curDays-1); } } }
权限控制核心配置类
package com.gad.epidemicmanage.config; import com.gad.epidemicmanage.service.impl.UserDetailServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import javax.annotation.Resource; /** * @author znz * @date 2022/2/18 14:42 * @desc security配置类 **/ @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean @Override public UserDetailServiceImpl userDetailsService() { return new UserDetailServiceImpl(); } @Override protected void configure(HttpSecurity http) throws Exception { http //不受认证: /login .authorizeRequests() .antMatchers("/user/login").permitAll(); http.httpBasic().disable() //关闭formLogin 自定义controller .formLogin().disable() .csrf().disable() .logout().disable(); } @Override @Bean //注入authenticationManager public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean public AuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService()); daoAuthenticationProvider.setPasswordEncoder(new BCryptPasswordEncoder()); daoAuthenticationProvider.setHideUserNotFoundExceptions(false); return daoAuthenticationProvider; } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder .authenticationProvider(daoAuthenticationProvider()); } }
跨域访问配置
package com.gad.epidemicmanage.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author znz * @date 2022/2/15 17:27 * @desc 全局跨域配置类 **/ @WebFilter public class GlobalCorsConfig implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "*"); response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(req, res); } }
用户管理控制器
package com.gad.epidemicmanage.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.gad.epidemicmanage.common.GlobalConstant; import com.gad.epidemicmanage.pojo.dto.UpdatePasswdDto; import com.gad.epidemicmanage.pojo.dto.UserListDto; import com.gad.epidemicmanage.pojo.dto.UserRigisterDto; import com.gad.epidemicmanage.pojo.entity.User; import com.gad.epidemicmanage.pojo.vo.Result; import com.gad.epidemicmanage.service.IRoleService; import com.gad.epidemicmanage.service.IUserBaseInfoService; import com.gad.epidemicmanage.service.IUserService; import lombok.extern.slf4j.Slf4j; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; /** * @author znz * @date 2022/2/4 13:56 * @desc 用户管理controller **/ @Slf4j @RestController public class UserController { @Resource IUserService userService; @Resource IUserBaseInfoService userBaseInfoService; @Resource IRoleService roleService; /** * 账户注册 */ @PostMapping("/register") public Result accountRegister(@RequestBody UserRigisterDto userRigisterDto){ log.info("开始注册"); Result result = new Result(true, "注册成功"); try{ int flag = userService.insertUser(userRigisterDto); //根据返回值判断是否重名,重名未注册成功 if(flag == GlobalConstant.STATE_FALSE){ result.setMessage("注册失败,用户名已被使用"); } else if(flag == 2){ result.setMessage("注册失败,两次输入密码不一致"); }else{ result.setData(userService.getOne(new LambdaQueryWrapper<User>() .eq(User::getUserName,userRigisterDto.getUserName()))); log.info("注册成功"); } }catch (Exception e){ log.error("注册失败:"+e); result.setCode(GlobalConstant.REQUEST_ERROR_STATUS_CODE); result.setMessage("注册失败"); } return result; } /** * 条件分页查询所有用户 */ @PostMapping("/queryUsers") public Result queryUsers(@RequestBody UserListDto userListDto){ log.info("查询所有用户开始"); Result result = new Result(true, "查询所有用户成功"); try{ IPage<User> userPage = userService.queryUsers(userListDto); result.setData(userPage); log.info("查询所有用户成功"); }catch (Exception e){ log.error("查询所有用户失败:"+e); result.setCode(GlobalConstant.REQUEST_ERROR_STATUS_CODE); result.setMessage("查询所有用户失败"); } return result; } /** * 修改用户密码 */ @PostMapping("/updateUser") public Result updateUser(@RequestBody UpdatePasswdDto updatePasswdDto){ log.info("修改用户密码开始"); Result result = new Result(true, "修改用户密码成功"); try{ Integer flag = userService.updateUser(updatePasswdDto); if(flag == GlobalConstant.STATE_FALSE){ result.setMessage("原密码错误,请检查输入"); return result; } if(flag == 2){ result.setMessage("两次密码不一致,请检查输入"); return result; } log.info("修改用户密码成功"); }catch (Exception e){ log.error("修改用户密码失败:"+e); result.setCode(GlobalConstant.REQUEST_ERROR_STATUS_CODE); result.setMessage("修改用户密码失败"); } return result; } /** * 删除用户,该操作会同时删除用户的详细信息和角色还有状态信息 */ @Transactional @PostMapping("/deleteUser/{userId}") public Result deleteUser(@PathVariable Integer userId){ log.info("开始删除用户"); Result result = new Result(true, "删除用户成功"); //设置回滚点 Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); try{ userService.deleteUser(userId); log.info("删除用户成功"); }catch (Exception e){ //回滚事务 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); log.error("删除用户失败:"+e); result.setCode(GlobalConstant.REQUEST_ERROR_STATUS_CODE); result.setMessage("删除用户失败"); } return result; } }
五,项目总结
经过几个月的努力与坚持,社区疫情防控管理系统终于完成了,关于社区防控的相关功能实现均按照初期的需求分析来进行并实现,整个开发过程从明确用户需求,到开发工具选择,比如选择什么框架,前后端是否要分离,再到具体的系统总体设计,做出总体设计说明书,再将总体设计细化为概要设计,以及后期的编码实现和内外部测试,最通过了系统的功能性测试。