基于Springboot+Vue+Sercurity实现的大学生健康管理平台(上)

简介: 基于Springboot+Vue+Sercurity实现的大学生健康管理平台

软件架构


健康管理平台的总目标是:在计算机网络,数据库和先进的开发平台上,利用现有的软件,配置一定的硬件,开发一个具有开放体系结构的、易扩充的、易维护的、具有良好人机交互界面的大学生健康管理平台。

具体的要求如下:


  • 大学生可以方便的使用五大基本功能
  • 独立于具体的数据库平台。对于与数据库交互的部分充分考虑兼容性
  • 容易在原先基础上进行二次开发
  • 中间基础平台的无型性。尽量采用开放的标准技术,达到跨平台运行的效果


为了达到以上的开发目的,健康管理平台应遵循一定的设计和开发与原则:

  • 模块接口定义清晰
  • 基础类库和接口设计合理,尽量建立叮扩展的接口和抽象类
  • 数据库操作使用标准SQL语句,如果一定要使用有差别的SQL语句,则尽量集中在一起,以减少将来维护和移植的难度
  • 采用开放的标准和工具等

2.2 系统的功能要求


2.2.1 前台功能

在前台的功能实现上,可以分为以下几个部分:

  • 大学生健康阅读:每日可以在首页进行健康资料阅读
  • 大学生健康日志:用户可以记录自己健康日记,记录每一天的睡眠,饮酒,吸烟,血压,体重等情况
  • 大学生体检管理:大学生可通过该平台查询每次的体检结果,显示部分项目随时间变化的曲线
  • 疾病预测评估:针对糖尿病和高血压两种疾病,利用数学线性回归的方法,模拟计算出大学生患病概率


2.2.2 后台功能

后台主要有四个功能:

  • 管理员用户登录功能:通过编号登录系统
  • 管理员管理大学生功能:可以增删改查大学生基本信息
  • 管理员管理体检结果:可以增删改查大学生体检记录
  • 管理员对体检表,健康文档,体检数据图标展示等进行管理,以及权限管理:指定不同科室医生进行不同的操作

2.3 系统的性能需求


健康管理平台的特性要求网站系统需要具备以下几个主要特性:


  • 多样性:健康管理平台在设计时必须遵循的一个重要原则,就是以用户为系统设计的中心。网络的发展大大降低了信息咨询的成本,用户面临更多的选择,所以,健康管理平台除了降低成本以外,还必须在外观,功能的多样性上下工夫,才能够得到用户的认可。具体来说,应用系统应能实现用户特定的需求,操作界面友好、方便,功能设置符合用户的习惯和要求
  • 易维护:管理员能够简单方便地管理系统,定义新的应用,并对系统进行日常维护;随着健康管理平台信息的变更或增加、数据规模的增大,系统升级、维护应易于操作;各子系统、功能模块的设计应具有良好的灵活性,保证在健康管理平台系统建设过程中后期系统功能的实现不影响前期已投入使用的系统功能
  • 安全性:系统应充分考虑信息、设备、运行和管理的安全性,建立完善的多层次的安全保障体系,保证用户的个人信息等资料的数据安全
  • 可扩展:健康管理平台的用户从事着各种行业,他们的需求各不相同,系统在设计时应充分考虑到用户需求不断变化的需要,降低系统在扩展新的业务功能时的复杂度[5]


2.4 系统的数据要求


2.4.1 数据的性质


数据录入和处理的准确性和实时性


数据的输入是否准确是数据处理的前提,错误的输入会导致系统输出的不正确和不可用,从而使系统的工作失去意义。数据的输入来源是用户的手工输入。手工输入要通过系统界面上的安排系统具有容错性,并且对用户操作进行实时的跟踪和错误提示。


在系统中,数据的输入往往是大量的,因此系统要有一定的处理能力,以保证迅速的处理数据。


数据的一致性与完整性


由于网站的数据是共享的,所以如何保证这些数据的一致性,是网站必须解决的问题。要解决这一问题,要有一定的人员维护数据的一致性,在数据录入处控制数据的去向,并且要求对数据库的数据完整性进行严格的约束。


对于输入的数据,要为其定义完整性规则,如果不能符合完整性约束,网站应该拒绝该数据。


数据的共享与独立性


整个健康管理平台的数据是共享的。然而,从网站开发的角度上看,共享会给设计和调试带来困难。因此,应该提供灵活的配置,使各个分系统能够独立运行,而通过人工干预的手段进行系统数据的交换。这样,也能提供系统的强壮性。


2.4.2 数据字典


健康管理平台主要涉及到真实姓名数据字典,如表2.1所示:


真实姓名数据字典


8be97ba0b2d579e642e3bd61eb95d858.png

系统信息表


b409ba597a4a68ea86a75aa374c3d5ff.png


报告信息表



7611eff79a45a435887cd9f9ce59afad.png

权限表


060dd8595a69853c6649118a106f7667.png

权限资源表

c825b5fa28cd4395041132fca7621679.png


资源表


becd2cc5ac27cbff5c4633d2371d9eae.png

健康阅读表


ca7edc118ada309ba77ff8e8736549a2.png

检查报告表


cf9155341bd209d82fb3e92e01c75c4b.png

2.4.3 实体联系图


c725bad244e1c2b4f646eb1ae2dd0b0a.png


3.项目实现

3.1 项目说明


项目是采用SpringBoot + Mybatis + Shiro 的搭建的单体后台服务。同时通过引入通用mapper减少代码量,已达到重点关注业务的开发。

<dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>${mapper-starter-version}</version><exclusions><exclusion><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></exclusion></exclusions></dependency>

3.1.1 实体


在创建实体时,首先定义了一个BaseEntity的类,所有的实体均集成该类,同时该类需要加上@MappedSuperclass注解。BaseEntity里面包含了所有实体的公共字段,如:id、createTime、updateTime。这样做是为了减少代码的冗余。


实体上配置了@Table注解,以及实体字段配置了 @Column注解是为了搭配jpa的自动建表,已达到添加字段时,重新运行项目,数据库表字段也会自动添加。

但是存在缺点: 更改字段类型或删除字段,数据库表不能自动修改或删除字段,需要手动去变更。


3.1.2 Dao层


再创建相关的dao层接口时,需要接口继承自定义的BaseDao接口,该接口集成了通用mapper相关的接口和注解,便于我们自定义一些通用接口。


3.1.3 Service层


所有的service接口需要继承自定义的BaseService接口,该接口定义了一些常用到的接口,例如增、删、改、查等等。


publicinterfaceBaseService<T>{List<T>selectAll();TselectByKey(Serializablekey);intinsert(Tentity);intdeleteByKey(Serializablekey);intdeletes(List<String>keys);intdelete(Tentity);intupdate(Tentity);List<T>selectByExample(Exampleexample);List<T>select(Conditioncondition);PageInfo<T>selectPage(Conditioncondition);}


同时创建接口实现类时需要继承自定义的BaseServiceImpl类,该类实现了BaseService接口的所有方法,这样可以达到不用书写这些接口,就可在需要的时候调用,如果需要自定义某个方法时,只需要实通过@Override重写某个方法即可。

@Slf4j@Transactional(propagation=Propagation.SUPPORTS,readOnly=true,rollbackFor=Exception.class)publicabstractclassBaseServiceImpl<T,MextendsBaseDao<T>>implementsBaseService<T>{privateClass<?>clazz=(Class<?>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];@AutowiredprotectedMentityDao;@OverridepublicList<T>selectAll(){returnentityDao.selectAll();}@OverridepublicTselectByKey(Serializablekey){log.info("执行筛选==>参数为【"+key+"】");returnentityDao.selectByPrimaryKey(key);}@Override@Transactional(rollbackFor=Exception.class)publicintinsert(Tentity){log.info("执行插入==>参数为【"+entity+"】");returnentityDao.insertSelective(entity);}@Override@Transactional(rollbackFor=Exception.class)publicintdeleteByKey(Serializablekey){log.info("执行删除==>参数为【"+key+"】");returnentityDao.deleteByPrimaryKey(key);}@Override@Transactional(rollbackFor=Exception.class)publicintdeletes(List<String>keys){log.info("执行批量删除==>参数为【"+keys+"】");StringkeyName=null;Field[]fields=clazz.getDeclaredFields();for(Fieldfield:fields){if(field.isAnnotationPresent(Id.class)){keyName=field.getName();break;}}Exampleexample=newExample(clazz);example.createCriteria().andIn(keyName,keys);returnentityDao.deleteByExample(example);}@Override@Transactional(rollbackFor=Exception.class)publicintdelete(Tentity){log.info("执行删除==>参数为【"+entity+"】");if(entity!=null){returnentityDao.delete(entity);}return0;}@Override@Transactional(rollbackFor=Exception.class)publicintupdate(Tentity){log.info("执行修改==>参数为【"+entity+"】");returnentityDao.updateByPrimaryKeySelective(entity);}@OverridepublicList<T>selectByExample(Exampleexample){returnentityDao.selectByExample(example);}@OverridepublicList<T>select(Conditioncondition){log.info("执行条件查询===>参数为【"+JSONObject.toJSONString(condition)+"】");Map<String,EntityColumn>propertyMap=EntityHelper.getEntityTable(clazz).getPropertyMap();Exampleexample=newExample(clazz);List<Clause>clauses=condition.getClauses();if(!CollectionUtils.isEmpty(clauses)){Example.Criteriacriteria=example.createCriteria();clauses.forEach(clause->{if(!StringUtils.isEmpty(clause.getColumn())){if("isNull".equalsIgnoreCase(clause.getOperation())){criteria.andIsNull(clause.getColumn());}if("isNotNull".equalsIgnoreCase(clause.getOperation())){criteria.andIsNotNull(clause.getColumn());}else{if(!StringUtils.isEmpty(clause.getValue())){if("between".equalsIgnoreCase(clause.getOperation())){ArrayList<?>list=(ArrayList<?>)clause.getValue();criteria.andBetween(clause.getColumn(),list.get(0),list.get(1));}if("like".equalsIgnoreCase(clause.getOperation())){criteria.andLike(clause.getColumn(),"%"+clause.getValue()+"%");}if("=".equalsIgnoreCase(clause.getOperation())){criteria.andCondition(propertyMap.get(clause.getColumn()).getColumn()+clause.getOperation(),clause.getValue());}}}}});}Map<String,Object>sortMap=condition.getSortMap();if(sortMap.size()>0){StringBuffersb=newStringBuffer();sortMap.forEach((k,v)->{if(!StringUtils.isEmpty(k)&&!StringUtils.isEmpty(v)){sb.append(propertyMap.get(k).getColumn());sb.append(" ");sb.append(v);sb.append(",");}});if(sb.toString().endsWith(",")){sb.deleteCharAt(sb.length()-1);}example.setOrderByClause(sb.toString());}returnselectByExample(example);}@OverridepublicPageInfo<T>selectPage(Conditioncondition){PageHelper.startPage(condition.getPageNum(),condition.getPageSize());List<T>list=select(condition);returnnewPageInfo<>(list);}}

3.1.4 Web层


在web层,项目定义了一个BaseController的抽象类,里面实体了基本的增删改查的接口定义。再我们创建某个模块的Controller时只需要继承该类就 可达到不用在创建的类下面定义接口,也会自动拥有相关接口,如果需要对某个接口方法做定制时,只需要重新某个方法就可以了。

@Slf4jpublicabstractclassBaseController<SextendsBaseService<T>,T,IDextendsSerializable>{@AutowiredprotectedSservice;@ApiOperation(value="基础接口: 新增操作")@PostMapping(value="add")publicResponseEntity<T>save(@RequestBodyTentity){try{if(service.insert(entity)<1){thrownewMyException(ExceptionEnums.ADD_ERROR);}}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.ADD_ERROR);}returnResponseEntity.ok(entity);}@ApiOperation(value="基础接口: 返回指定ID的数据")@GetMapping(value="get/{id}")publicResponseEntity<T>get(@PathVariable("id")IDid){Tt=null;try{t=service.selectByKey(id);}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.GET_ITEM);}returnResponseEntity.ok(t);}@ApiOperation(value="基础接口: 返回所有数据")@GetMapping(value="all")publicResponseEntity<List<T>>all(){List<T>list;try{list=service.selectAll();}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.GET_LIST_ERROR);}returnResponseEntity.ok(list);}@ApiOperation(value="基础接口: 分页返回数据")@PostMapping(value="page")publicResponseEntity<PageInfo<T>>page(@RequestBodyConditioncondition){PageInfo<T>page;try{page=service.selectPage(condition);}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.GET_LIST_ERROR);}returnResponseEntity.ok(page);}@ApiOperation(value="基础接口: 修改数据")@PostMapping(value="update")publicResponseEntity<T>update(@RequestBodyTentity){try{if(service.update(entity)<1){thrownewMyException(ExceptionEnums.UPDATE_ERROR);}}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.UPDATE_ERROR);}returnResponseEntity.ok(entity);}@ApiOperation(value="基础接口: 删除指定ID的数据")@GetMapping(value="delete/{id}")publicResponseEntity<String>delete(@PathVariable("id")IDid){try{if(service.deleteByKey(id)<1){thrownewMyException(ExceptionEnums.DELETE_ERROR);}}catch(Exceptione){log.error(e.getMessage());thrownewMyException(ExceptionEnums.DELETE_ERROR);}returnResponseEntity.ok("删除成功");}}

3.2 权限相关


权限主要采用apache shiro,需要引入相关依赖


<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency>

在使用shiro时,需要创建一个realm进行进行权限的授权和认证,同时需要创建一个自定义的凭证验证器来校验密码是否一致。配置好这些后需要定义一个shiro的配置类ShiroConfiguration来配置shiro的权限过滤工厂beanShiroFilterFactoryBean,在里面引入自定义的AuthRealmbean以达到权限的校验。


因为项目采用的是前后端分离,会产生跨域请求等问题,所以需要开放所有来源对该服务的请求,所以需要配置相关过滤器CorsFilter。


publicclassCorsFilterimplementsFilter{@Overridepublicvoidinit(FilterConfigfilterConfig)throwsServletException{}@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsIOException,ServletException{servletRequest.setCharacterEncoding("utf-8");HttpServletResponsehttpServletResponse=(HttpServletResponse)servletResponse;HttpServletRequesthttpServletRequest=(HttpServletRequest)servletRequest;//是否支持cookie跨域
httpServletResponse.setHeader("Access-Control-Allow-Credentials","true");//指定允许其他域名访问
httpServletResponse.setHeader("Access-Control-Allow-Origin",httpServletRequest.getHeader("Origin"));//响应头设置
httpServletResponse.setHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept,client_id, uuid, Authorization,user-agent,X-XSRF-TOKEN");// 设置过期时间
httpServletResponse.setHeader("Access-Control-Max-Age","3600");//响应类型
httpServletResponse.setHeader("Access-Control-Allow-Methods","POST, GET, PUT, OPTIONS, DELETE");// 支持HTTP1.1.
httpServletResponse.setHeader("Cache-Control","no-cache, no-store, must-revalidate");// 支持HTTP 1.0. 
httpServletResponse.setHeader("Pragma","no-cache");httpServletResponse.setHeader("Allow","GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");if("OPTIONS".equals(httpServletRequest.getMethod())){httpServletResponse.setStatus(204);}filterChain.doFilter(servletRequest,servletResponse);}@Overridepublicvoiddestroy(){}}

使用shiro权限验证时,第一次请求为option方式,所以要配置shiro过滤掉option方式,不对此方式做权限拦截。故配置ShiroFilter

@Slf4jpublicclassShiroFiterextendsFormAuthenticationFilter{@OverrideprotectedbooleanisAccessAllowed(ServletRequestrequest,ServletResponseresponse,ObjectmappedValue){booleanallowed=super.isAccessAllowed(request,response,mappedValue);if(!allowed){// 判断请求是否是options请求
Stringmethod=WebUtils.toHttp(request).getMethod();if(StringUtils.equalsIgnoreCase("OPTIONS",method)){returntrue;}}returnallowed;}@OverrideprotectedbooleanonAccessDenied(ServletRequestrequest,ServletResponseresponse)throwsException{returnsuper.onAccessDenied(request,response);}}

3.3 其他


项目中定义了全局异常捕获,再项目中需要抛出异常时只需要抛出自定义的异常MyException,就可对该异常进行捕获,当用户请求某个接口如果出现异常时,后台服务就会返回自定义的错误代码,不会出现乱码的情况,以便于前端对返回结果做统一处理或错误拦截。同时我们也可预先自定义需要抛出的错误,在前段可对错误code进行区别,采用不同的样式提示。

@Getter@NoArgsConstructor@AllArgsConstructorpublicenumExceptionEnums{/**
     * 错误返回信息
     */NO_CHECK_INFO(400,"暂无健康档案表,无法进行数据分析"),CHOOSE_FILE(400,"文件未上传"),IS_NOT_LOGIN(400,"用户未登录"),UPLOAD_FAIL(400,"上传失败"),RESOURCE_FOUNT_FAIL(400,"获取菜单权限失败!"),NOT_AUTHORIZED(403,"您暂无权限访问该资源!"),ACCOUNT_IS_EXIT(400,"用户名已存在"),PASSWORD_WRONG(400,"密码错误"),ACCOUNT_IS_NOT_EXIT(400,"用户名不存在"),UPDATE_ERROR(400,"更新失败"),ADD_ERROR(400,"新增失败"),DELETE_ERROR(400,"删除失败"),GET_LIST_ERROR(400,"获取列表失败"),GET_ITEM(400,"获取对象失败"),NO_WEIGHT_HEIGHT(400,"当前档案未完善身高体重信息");/**
     * 代码值
     */privateintcode;/**
     * 消息
     */privateStringmsg;}


相关文章
|
15天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
94 62
|
12天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
31 2
|
15天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
11天前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
27 0
|
2天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
3天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
3天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
3天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
2天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
3天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。