# spingboot05 **Repository Path**: li-shangfeng/spingboot05 ## Basic Information - **Project Name**: spingboot05 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-06-30 - **Last Updated**: 2022-01-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
东莞理工学院网络空间安全学院
实验报告
| 课程名称 | 企业级开发框架实践 | 学期 | 2021年春季 | | :-------------: | :----------------------------: | :----------: | :---------------: | | 实验名称 | **高校健康上报系统设计与实现** | **实验序号** | **五** | | **姓名** | **李尚峰** | **学号** | **201841412214** | | **小组成员** | **廖志豪** | **学号** | **20184142217** | | **班级** | **18软卓2班** | **指导老师** | **黎志雄** | # **一、项目简介** ​ 在全国人民共同抗击新型冠状病毒疫情的严峻形势下,为了让高校师生健康信息上报,方便地了解高校师生身体健康情况,做好高校内部的疫情防控工作与管理,很多高校都自建了健康上报系统。 ​ 本实验项目作为课程的期末大作业,要求学生组队自主设计与实现一个健康上报系统,并可以Saas(软件及服务)架构实现云原生系统。 # **二、需求分析** 本实验项目要求学生团队设计实现的高校健康上报系统可以解决如下需求: **1、前端界面功能模块:** ​ a) 要求界面美观、简约,主题统一; ​ b) 调研国内相关系统界面的输入信息,设计前端各信息录入项目; ​ c) 有防止用户重复提交的功能; ​ d) 有加速渲染前端界面的设计。 **2、后台管理端功能模块:** ​ a) 可以使用vue或其它前端框架构建,实现管理后台各个功能模块; ​ b) 要求界面简洁精美,主题统一; ​ c) 后台登录逻辑需接入腾讯防水墙,https://007.qq.com/; ​ d) 管理员账号管理模块; ​ e) 使用Spring Security安全框架实现认证、访问控制; ​ f) 账号管理模块;可锁定账号; ​ g) 有解决系统高并发问题的设计。 ​ h) 提供大数据报表并导出Excel表格,相关管理人员通过报表可一目了然地查看相关信息; ​ i) 所有列表可以进行综合查询,特别是时间字段,并可以排序; ​ j) 所有列表可以导出excel文件或pdf文件; **3、接入莞工中央认证** # **三、项目分工** 前端:李尚峰、廖志豪 后端:廖志豪、李尚峰 数据库设计:廖志豪、李尚峰 docker部署:廖志豪、李尚峰 # 四、关于项目 ## 项目框架 后端:SpringBoot整合mybatis,SpringSecurity 前端:thymeleaf模板,layui ## 用户端功能 ### 登陆功能 ![image-20210630153314392](README/picture/image-20210630153314392.png) ![image-20210630155043390](README/picture/image-20210630155043390.png) 用户和管理员在同一登陆页面进行登录,通过腾讯防水墙,然后再通过SpringSecurity验证,验证成功后,根据账号的类型来进行判断进入各自的页面。 #### SpringSecurity配置 ```java @Configuration public class UserSecurity extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http)throws Exception{ http.formLogin() .loginPage("/toLogin") .loginProcessingUrl("/suibian") .defaultSuccessUrl("/toMain",true) .failureUrl("/failure"); http.logout() .logoutUrl("/logout") .logoutSuccessUrl("/toLogin") .logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")) .deleteCookies("JSESSIONID") .invalidateHttpSession(true) .and(); http.authorizeRequests() .antMatchers("/toLogin","/register").anonymous() .antMatchers("/failure","/doRegister","https://cas.dgut.edu.cn?appid=javaee","/login/dgut","/token").permitAll() .antMatchers("/css/**", "/img/**", "/js/**" , "/layui/**").permitAll() .antMatchers("/admin1").hasRole("管理员") .antMatchers("/userMange").hasRole("管理员") .antMatchers("/numberMange").hasRole("管理员") .antMatchers("/riskareaMange").hasRole("管理员") .anyRequest().authenticated(); //http.csrf().disable();//关闭CSRF安全协议,为了保证完整流程的可用 } ``` #### 腾讯防水墙 ```javascript ``` #### 数据库获取用户数据 ```java @Component("myUserDetailsService") public class UserServiceImpl implements UserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user=userService.login(username); if(null==user){ throw new UsernameNotFoundException("用户名错误"); }else if(user.getStatus() == 1){ throw new UsernameNotFoundException("账号已锁定"); } List role= userService.findUserByUserId(user); String type=role.get(0).getType(); String []authorities=new String[1]; authorities[0]="ROLE_"+type; System.out.println(authorities[0]); org.springframework.security.core.userdetails.User result= new org.springframework.security.core.userdetails.User( username,user.getPassword(), AuthorityUtils.createAuthorityList(authorities[0]) ); return result; } } ``` ### 注册功能 ![image-20210630153659959](README/picture/image-20210630153659959.png) 注册页面只能够注册为学生或教师,且注册时不能够注册已存在的账号,只能够注册数据库中不存在的账号 ### 用户主页 ![image-20210630160529091](README/picture/image-20210630160529091.png) 实时数据统计为爬取码云仓库获取的: ```yaml Schedules: updateVirusDataCron: 0 0 1 * * * CORONA_VIRUS_CONFIRMED_DATA_URL: https://gitee.com/dgut-sai/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv CORONA_VIRUS_RECOVERED_DATA_URL: https://gitee.com/dgut-sai/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv CORONA_VIRUS_DEATHS_DATA_URL: https://gitee.com/dgut-sai/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv User_Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36 ``` 风险地区为管理员设置后,展示在用户主页中 ```java public Map riskmange() throws IOException{ List riskAreas = riskAreaService.getAllRiskArea(); Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); map.put("data", riskAreas); return map; } ``` ### 个人信息上报 当天未上报时显示“您今天尚未打卡”,且新用户在填写个人信息上报时,除了用户和类型其余均未填写。 上报后的用户隔天再次填写个人信息上报时,会获取上次最近填写的内容,无需重新输入。 ![image-20210630160655251](README/picture/image-20210630160655251.png) ![image-20210630160744012](README/picture/image-20210630160744012.png) ![image-20210630160804578](README/picture/image-20210630160804578.png) 用户当天提交过后,显示“您今天已提交成功”,且不能重复提交,提交过后提交按钮消失。 ![image-20210630162058163](README/picture/image-20210630162058163.png) ### 上报记录查询 ![image-20210630162148654](README/picture/image-20210630162148654.png) 当用户未曾打过卡时 ![image-20210630182621207](README/picture/image-20210630182621207.png) ### 国内省份数据查询 ![image-20210630162330606](README/picture/image-20210630162330606.png) ![image-20210630162349805](README/picture/image-20210630162349805.png) 数据获取同上 ```java @RequestMapping("/data/china") public Map China() throws IOException { List regionStates = dataProvider.getRegionStates("China"); TransferUtil.transferToChinese(regionStates); Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); map.put("data", regionStates); return map; } ``` ### 全球国家数据查询 ![image-20210630162459203](README/picture/image-20210630162459203.png) ![image-20210630162513319](README/picture/image-20210630162513319.png) 数据获取同上 ```java @RequestMapping("/data/global") public Map global() throws IOException { List num = dataProvider.getNum(); Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); map.put("data", num); return map; } ``` ### 全球地区数据查询 ![image-20210630162530559](README/picture/image-20210630162530559.png) ![image-20210630162545869](README/picture/image-20210630162545869.png) 数据获取同上 ```java @RequestMapping("/data/regional") public Map regional() throws IOException { List regionStates = dataProvider.getAllRegionStates(); Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); map.put("data", regionStates); return map; } ``` ## 后台管理功能 ### 登陆功能 同用户 ### 管理员主页 ![image-20210630183507606](README/picture/image-20210630183507606.png) ### 用户管理 ![image-20210630183636254](README/picture/image-20210630183636254.png) 代码实现 ```javascript layui.use('element', function(){ var element = layui.element; }); layui.use('table', function(){ var table = layui.table; table.render({ elem: '#LAY_table_user' ,url:'/mangeuser' ,cellMinWidth: 80 ,toolbar:true ,title:'用户数据表' ,totalRow:true ,cols: [ [ {field:'id', title: 'ID', sort:true} ,{field:'username', title: '用户', sort:true} ,{field:'name', title: '姓名'} ,{field:'idtype', title: '证件类型'} ,{field:'idnumber', title: '证件号码'} ,{field:'type', title: '身份'} ,{field:'campus', title: '校区'} ,{field:'address', title: '地址'} ,{field:'telnumber', title: '电话号码'} ,{field:'emcontact', title: '紧急联系人'} ,{field:'emctelnumber', title: '紧急联系人方式'} ,{field:'health', title: '身体状况'} ,{field:'reportstatus', title: '打卡状态'} ,{field: 'button', title: '操作', toolbar:"#btn"} ] ] ,id: 'testReload' }); ``` ```java @RequestMapping("/mangeuser") public Map userMange() throws IOException{ List userInfos = userInfoService.getAllUserInfo(); List userRecords = new ArrayList<>(); for (UserInfo u:userInfos){ List eRecords = eRecordService.getERcordByUfid(u.getId()); ERecord eRecord = eRecords.get(eRecords.size()-1); UserRecord userRecord = new UserRecord(u, eRecord); java.sql.Date currentDate = new java.sql.Date(System.currentTimeMillis()); if(eRecord.getTime().toString().equals(currentDate.toString())){ userRecord.setReportstatus("已打卡"); }else { userRecord.setReportstatus("未打卡"); } userRecords.add(userRecord); } Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); map.put("data", userRecords); return map; } ``` 有sort:true的字段表示该列可以进行升序或降序 #### 综合查询 可根据查询条件,进行多条件模糊查询查找想要的内容(查询基本上都是多条件查询,下面出现的查询将不再详细解释) ![image-20210630185416944](README/picture/image-20210630185416944.png) 代码实现 ```javascript var $ = layui.$, active = { reload: function(){ var username = $('#username'); var name = $('#name'); var type = $('#type'); var campus = $('#campus'); var address = $('#address'); console.log("key:" + username.val()); table.reload('testReload', { url: '/selectUserByCondition' ,where: { username: username.val(), name:name.val(), type:type.val(), campus:campus.val(), address:address.val(), } } ); } }; $('.demoTable .layui-btn').on('click', function(){ var type = $(this).data('type'); active[type] ? active[type].call(this) : ''; }); ``` ```java @RequestMapping("/selectUserByCondition") public Map selectUserByCondition(SearchUserByCondition searchUserByCondition) throws IOException{ Map map =new HashMap(); map.put("code", 0); map.put("msg", ""); List userInfos = userInfoService.getUserInfoByCondition(searchUserByCondition); if(userInfos != null){ List userRecords = new ArrayList<>(); for (UserInfo u:userInfos){ List eRecords = eRecordService.getERcordByUfid(u.getId()); ERecord eRecord = eRecords.get(eRecords.size()-1); UserRecord userRecord = new UserRecord(u, eRecord); java.sql.Date currentDate = new java.sql.Date(System.currentTimeMillis()); if(eRecord.getTime().toString().equals(currentDate.toString())){ userRecord.setReportstatus("已打卡"); }else { userRecord.setReportstatus("未打卡"); } userRecords.add(userRecord); } map.put("data", userRecords); }else { map.put("data", null); } return map; } ``` #### 未打卡名单 点击未打卡名单可筛选出所有未打卡的用户 ![image-20210630183913982](README/picture/image-20210630183913982.png) #### 用户详情 点击详情可查看用户所有的打卡记录 ![image-20210630183757463](README/picture/image-20210630183757463.png) 详情内部也能进行多条件查询 ![image-20210630185802921](README/picture/image-20210630185802921.png) #### 导出Excel表或pdf文件 右上角的三个图标可根据自己的选择导出excel或pdf文件,且可筛选想要的列进行导出 ![image-20210630184054381](README/picture/image-20210630184054381.png) ![image-20210630184117406](README/picture/image-20210630184117406.png) ![image-20210630184306788](README/picture/image-20210630184306788.png) ![image-20210630184135877](README/picture/image-20210630184135877.png) ### 用户账号管理 ![image-20210630184434474](README/picture/image-20210630184434474.png) 当用户的账号状态表示0时,表示账号可以登录; 用户账号状态为1时,表示账号被锁定,无法登陆进入系统。 #### 锁定账号 ![image-20210630184645178](README/picture/image-20210630184645178.png) 登录账号为201841412202的用户时 ![image-20210630184736917](README/picture/image-20210630184736917.png) #### 解锁账号 ![image-20210630184928477](README/picture/image-20210630184928477.png) #### 删除账号 ![image-20210630184832235](README/picture/image-20210630184832235.png) 删除时提示弹出框,确定则将用户删除,取消则取消删除操作 删除用户201841412203 ![image-20210630185039466](README/picture/image-20210630185039466.png) ### 风险地区管理 ![image-20210630190958943](README/picture/image-20210630190958943.png) 添加的风险地区将会在所有用户的主页进行展示 #### 添加风险地区 ![image-20210630191100219](README/picture/image-20210630191100219.png) #### 删除风险地区 ![image-20210630191207986](README/picture/image-20210630191207986.png) ![image-20210630191227606](README/picture/image-20210630191227606.png) ### 管理员账号管理(超级管理员专属功能) ![image-20210630191438796](README/picture/image-20210630191438796.png) 管理员账号管理模块为超级管理员专属功能,超级管理员默认为数据库中第一个用户,即账号为201841412200的用户,其它管理员用户都没有管理员账号管理功能,且该模块不会显示超级管理员的账号信息。 ![image-20210630191732989](README/picture/image-20210630191732989.png) #### 添加管理员 ![image-20210630191820424](README/picture/image-20210630191820424.png) #### 锁定账号 与用户账号管理相同,账号对象类型为管理员 #### 解锁账号 与用户账号管理相同,账号对象类型为管理员 #### 删除账号 与用户账号管理相同,账号对象类型为管理员 # 五、工程目录结构 ![image-20210630143818113](README/picture/image-20210630143818113.png) bean:实体类 check:防水墙 controller:控制器 converter:转换器 index:地图信息类 oauth2:中央认证登录 security:安全框架 util:工具类 静态资源(css,js,img等)放在resources目录下的static中 mybaitis的xml文件放在resources目录下的mybatis中 属性配置在resources目录下的.yml文件中 # 六、莞工中央认证 ![image-20210630192603825](README/picture/image-20210630192603825.png) 登录成功后进入用户登录成功的主页面,但是中央认证登录只能在本地进行登录,若将项目部署到服务器上,中央认证就不能正常使用(原因中央认证的回调地址为本地的localhost与服务器的地址不符,不能够登录成功) ## 流程图 ![image-20210630145105920](README/picture/image-20210630145105920.png) ## 具体实现 ![image-20210630192217375](README/picture/image-20210630192217375.png) ```java @ResponseBody @RequestMapping(value = "/login/dgut") public void login(HttpServletRequest request, HttpServletResponse response, Model model) throws IOException { // 1从中央验证系统获取token String token = request.getParameter("token"); String username = request.getParameter("usrname"); String password = request.getParameter("password"); System.out.println("token==============================" + token); // 没有Token,把用户重定向到中央认证登陆页 if (token == null || "".equals(token)) { System.out.println("token为空格"); // 跳到中央登录登录验证系统,这个异常判断 response.sendRedirect(casLoginUrl); System.out.println(token); } else { // 有票,开始兑票------第二步,通过token获取access_token和openid String ip = "172.27.15.28"; System.out.println("token===" + token); String userIp = request.getLocalAddr(); System.out.println("userIp===" + userIp); // 开始访问接口,验证token值 Map userInfo = postToken(token, userIp); Authentication request1 = new UsernamePasswordAuthenticationToken(userInfo.get("username"), userInfo.get("username")); System.out.println(request1); Authentication result = am.authenticate(request1); System.out.println(result); SecurityContextHolder.getContext().setAuthentication(result); if(userService.login(userInfo.get("username")) == null){ User user = new User(null, userInfo.get("username"), "123456", "学生", 2); userService.addUser(user); userInfoService1.addUserInfo(userInfo.get("username"), "学生"); } System.out.println("认证成功,Security context 包含:" + SecurityContextHolder.getContext().getAuthentication()); //UserDetails result=new UserServiceImpl().loadUserByUsername(username1); //System.out.println(result); response.sendRedirect(CAS_Server); } } ``` 获取的中央认证的用户信息,密码不知道将其设置为123456,且将账号状态设为2表示其为中央认证登录的账号 # 七、数据库 ## E-R图 ![image-20210627145544129](README/picture/image-20210627145544129.png) ## 用户登录表+实体类 ![image-20210630144930604](README/picture/image-20210630144930604.png) ![image-20210630150919183](README/picture/image-20210630150919183.png) ## 个人信息表+实体类 ![image-20210630144953165](README/picture/image-20210630144953165.png) ![image-20210630150959068](README/picture/image-20210630150959068.png) ## 疫情情况记录表+实体类 ![image-20210630145012324](README/picture/image-20210630145012324-1625036352487.png) ![image-20210630151034326](README/picture/image-20210630151034326.png) ## 风险地区记录表+实体类 ![image-20210630145032008](README/picture/image-20210630145032008.png) ![image-20210630151101121](README/picture/image-20210630151101121.png) ## 相关配置,持久层采用mybatis框架 ![image-20210630151224754](README/picture/image-20210630151224754.png) ![image-20210630151255446](README/picture/image-20210630151255446.png) # 八、项目部署 ### 项目部署地址 120.79.140.61,若访问失败请及时联系队员重新开启服务器(服务器可能被我们关了,买了一个计时的服务器,呜呜呜~) ### 项目部署流程 ![image-20210630200012698](README/picture/image-20210630200012698.png) ![image-20210630200019792](README/picture/image-20210630200019792.png) ![image-20210630200033533](README/picture/image-20210630200033533.png) ![image-20210630200050730](README/picture/image-20210630200050730.png) ### 部署完成 ![image-20210630200107505](README/picture/image-20210630200107505.png) ![image-20210630200150545](README/picture/image-20210630200150545.png)