# 大学生健康档案管理系统后端代码
**Repository Path**: out-project/StudentDocumentBack
## Basic Information
- **Project Name**: 大学生健康档案管理系统后端代码
- **Description**: SpringBoot+Mybatisplus+Shiro进行开发,适用于刚入门的Java程序员进行学习。
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 33
- **Forks**: 16
- **Created**: 2019-04-28
- **Last Updated**: 2025-07-17
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 大学生健康档案管理系统后端代码
#### 介绍
大学生健康档案管理系统,体检表,健康文档,体检数据图标展示等进行管理,以及权限管理,指定不同科室医生进行不同的操作。
#### 软件架构
软件架构说明
- springboot
- mysql
- mybatis
- jpa
- swagger-ui
- lombok
#### 项目说明
项目是采用SpringBoot + Mybatis + Shiro 的搭建的单体后台服务。同时通过引入通用mapper减少代码量,已达到重点关注业务
的开发。
```xml
    tk.mybatis
    mapper-spring-boot-starter
    ${mapper-starter-version}
    
        
            javax.persistence
            persistence-api
        
    
```
> 实体
在创建实体时,首先定义了一个`BaseEntity`的类,所有的实体均集成该类,同时该类需要加上`@MappedSuperclass`注解。`BaseEntity`里面
包含了所有实体的公共字段,如:id、createTime、updateTime。这样做是为了减少代码的冗余。
实体上配置了`@Table`注解,以及实体字段配置了 `@Column`注解是为了搭配jpa的自动建表,已达到添加字段时,重新运行项目,数据库表字段也会自动添加。
但是存在缺点: 更改字段类型或删除字段,数据库表不能自动修改或删除字段,需要手动去变更。
> Dao层
再创建相关的dao层接口时,需要接口继承自定义的`BaseDao`接口,该接口集成了通用mapper相关的接口和注解,便于我们自定义一些通用接口。
> Service层
所有的service接口需要继承自定义的`BaseService`接口,该接口定义了一些常用到的接口,例如增、删、改、查等等。
```java
package com.cqjtu.studentdocument.service;
import com.cqjtu.studentdocument.utils.dto.Condition;
import com.github.pagehelper.PageInfo;
import tk.mybatis.mapper.entity.Example;
import java.io.Serializable;
import java.util.List;
/**
 * @author pengyangyan
 */
public interface BaseService {
    /**
     * 查询所有数据
     * @return      结果List
     */
    List selectAll();
    /**
     * 主键查询
     *
     * @param key   主键
     * @return      T
     */
    T selectByKey(Serializable key);
    /**
     * 插入数据
     * @param entity   传入实体
     * @return  受影响行数
     */
    int insert(T entity);
    /**
     * 主键删除
     * @param key   主键
     * @return   受影响行数
     */
    int deleteByKey(Serializable key);
    /**
     * 批量删除
     * @return      受影响行数
     */
    int deletes(List keys);
    /**
     * 删除数据
     *
     * @param entity 实体
     * @return       受影响行数
     */
    int delete(T entity);
    /**
     * 更新数据
     *
     * @param  entity 实体
     * @return       受影响行数
     */
    int update(T entity);
    /**
     * mapper Example原生查询方式
     *
     * @param example  example
     * @return         结果List
     */
    List selectByExample(Example example);
    /**
     * 条件排序查询
     * 1.查询条件clauses和排序条件sortMap都可以为空
     * 2.如果查询条件和排序map都为空,则返回所有数据
     * 3.pageSize和pageNum该方法无效
     * 4.clauses定义查询条件
     * 5.sortMap定义排序条件,key为属性字段,value="DESC" 为倒序,value="ASC"为正序。
     *
     * @param     condition 条件
     * @return    结果List
     */
    List select(Condition condition);
    /**
     * 条件分页排序查询
     * 1.查询条件clauses和排序条件sortMap都可以为空
     * 2.如果查询条件和排序map都为空,则返回所有数据
     * 3.clauses定义匹查询条件
     * 4.sortMap定义排序条件,key为属性字段,value="DESC" 为倒序,value="ASC"为正序。
     *
     * @param condition   条件
     * @return         结果List
     */
    PageInfo selectPage(Condition condition);
}
```
同时创建接口实现类时需要继承自定义的`BaseServiceImpl`类,该类实现了`BaseService`接口的所有方法,这样可以达到不用书写这些接口,就可在需要的时候
调用,如果需要自定义某个方法时,只需要实通过`@Override`重写某个方法即可。
```java
package com.cqjtu.studentdocument.service.Impl;
import com.alibaba.fastjson.JSONObject;
import com.cqjtu.studentdocument.dao.BaseDao;
import com.cqjtu.studentdocument.service.BaseService;
import com.cqjtu.studentdocument.utils.dto.Clause;
import com.cqjtu.studentdocument.utils.dto.Condition;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.mapperhelper.EntityHelper;
import javax.persistence.Id;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * @author pengyangyan
 */
@Slf4j
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true,rollbackFor = Exception.class)
public abstract class BaseServiceImpl> implements BaseService {
    private Class> clazz = (Class>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    @Autowired
    protected M entityDao;
    @Override
    public List selectAll() {
        return entityDao.selectAll();
    }
    @Override
    public T selectByKey(Serializable key) {
        log.info("执行筛选==>参数为【"+key+"】");
        return entityDao.selectByPrimaryKey(key);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insert(T entity) {
        log.info("执行插入==>参数为【"+entity+"】");
        return entityDao.insertSelective(entity);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteByKey(Serializable key) {
        log.info("执行删除==>参数为【"+key+"】");
        return entityDao.deleteByPrimaryKey(key);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deletes(List keys) {
        log.info("执行批量删除==>参数为【"+keys+"】");
        String keyName = null;
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Id.class)) {
                keyName = field.getName();
                break;
            }
        }
        Example example = new Example(clazz);
        example.createCriteria().andIn(keyName,keys);
        return entityDao.deleteByExample(example);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int delete(T entity) {
        log.info("执行删除==>参数为【"+entity+"】");
        if (entity != null){
            return entityDao.delete(entity);
        }
        return 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int update(T entity) {
        log.info("执行修改==>参数为【"+entity+"】");
        return entityDao.updateByPrimaryKeySelective(entity);
    }
    @Override
    public List selectByExample(Example example) {
        return entityDao.selectByExample(example);
    }
    @Override
    public List select(Condition condition) {
        log.info("执行条件查询===>参数为【"+JSONObject.toJSONString(condition) +"】");
        Map propertyMap = EntityHelper.getEntityTable(clazz).getPropertyMap();
        Example example = new Example(clazz);
        List clauses = condition.getClauses();
        if (!CollectionUtils.isEmpty(clauses)) {
            Example.Criteria criteria = 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 sortMap = condition.getSortMap();
        if (sortMap.size()>0) {
            StringBuffer sb = new StringBuffer();
            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());
        }
        return selectByExample(example);
    }
    @Override
    public PageInfo selectPage(Condition condition) {
        PageHelper.startPage(condition.getPageNum(), condition.getPageSize());
        List list = select(condition);
        return new PageInfo<>(list);
    }
}
```
> Web层
在web层,项目定义了一个`BaseController`的抽象类,里面实体了基本的增删改查的接口定义。再我们创建某个模块的Controller时只需要继承该类就
可达到不用在创建的类下面定义接口,也会自动拥有相关接口,如果需要对某个接口方法做定制时,只需要重新某个方法就可以了。
```java
package com.cqjtu.studentdocument.controller;
import com.cqjtu.studentdocument.advice.ExceptionEnums;
import com.cqjtu.studentdocument.advice.MyException;
import com.cqjtu.studentdocument.service.BaseService;
import com.cqjtu.studentdocument.utils.dto.Condition;
import com.github.pagehelper.PageInfo;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.io.Serializable;
import java.util.List;
/**
 * @author pengyangyan
 */
@Slf4j
public abstract class BaseController,T,ID extends Serializable> {
    @Autowired
    protected S service;
    @ApiOperation(value = "基础接口: 新增操作")
    @PostMapping(value = "add")
    public ResponseEntity save(@RequestBody T entity){
        try {
            if ( service.insert(entity)<1){
                throw new MyException(ExceptionEnums.ADD_ERROR);
            }
        }catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.ADD_ERROR);
        }
        return ResponseEntity.ok(entity);
    }
    @ApiOperation(value = "基础接口: 返回指定ID的数据")
    @GetMapping(value = "get/{id}")
    public ResponseEntity get(@PathVariable("id")ID id){
        T t = null;
        try {
            t = service.selectByKey(id);
        }catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.GET_ITEM);
        }
        return ResponseEntity.ok(t);
    }
    @ApiOperation(value = "基础接口: 返回所有数据")
    @GetMapping(value = "all")
    public ResponseEntity> all(){
        List list;
        try {
            list = service.selectAll();
        }catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.GET_LIST_ERROR);
        }
        return ResponseEntity.ok(list);
    }
    @ApiOperation(value = "基础接口: 分页返回数据")
    @PostMapping(value = "page")
    public ResponseEntity> page(@RequestBody Condition condition){
        PageInfo page ;
        try {
            page = service.selectPage(condition);
        }catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.GET_LIST_ERROR);
        }
        return ResponseEntity.ok(page);
    }
    @ApiOperation(value = "基础接口: 修改数据")
    @PostMapping(value = "update")
    public ResponseEntity update(@RequestBody T entity){
        try {
            if (service.update(entity)<1){
                throw new MyException(ExceptionEnums.UPDATE_ERROR);
            }
        }catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.UPDATE_ERROR);
        }
        return ResponseEntity.ok(entity);
    }
    @ApiOperation(value = "基础接口: 删除指定ID的数据")
    @GetMapping(value = "delete/{id}")
    public ResponseEntity delete(@PathVariable("id")ID id){
        try {
            if ( service.deleteByKey(id)<1){
                throw new MyException(ExceptionEnums.DELETE_ERROR);
            }
        } catch (Exception e){
            log.error(e.getMessage());
            throw new MyException(ExceptionEnums.DELETE_ERROR);
        }
        return ResponseEntity.ok("删除成功");
    }
}
```
> 权限相关
权限主要采用apache shiro,需要引入相关依赖
```xml
    
        org.apache.shiro
        shiro-core
        1.3.2
    
    
        org.apache.shiro
        shiro-spring
        1.3.2
    
```
在使用shiro时,需要创建一个realm进行进行权限的授权和认证,同时需要创建一个自定义的凭证验证器来校验密码是否一致。配置好这些后需要定义
一个shiro的配置类`ShiroConfiguration`来配置shiro的权限过滤工厂bean`ShiroFilterFactoryBean`,在里面引入自定义的`AuthRealm`bean以达到权限的校验。
因为项目采用的是前后端分离,会产生跨域请求等问题,所以需要开放所有来源对该服务的请求,所以需要配置相关过滤器`CorsFilter`。
```java
package com.cqjtu.studentdocument.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CorsFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        servletRequest.setCharacterEncoding("utf-8");
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        HttpServletRequest httpServletRequest = (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);
    }
    @Override
    public void destroy() {
    }
}
```
使用shiro权限验证时,第一次请求为option方式,所以要配置shiro过滤掉option方式,不对此方式做权限拦截。故配置`ShiroFilter`
```java
package com.cqjtu.studentdocument.filter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
@Slf4j
public class ShiroFiter extends FormAuthenticationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean allowed = super.isAccessAllowed(request, response, mappedValue);
        if (!allowed) {
            // 判断请求是否是options请求
            String method = WebUtils.toHttp(request).getMethod();
            if (StringUtils.equalsIgnoreCase("OPTIONS", method)) {
                return true;
            }
        }
        return allowed;
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        return super.onAccessDenied(request, response);
    }
}
```
> 其他
项目中定义了全局异常捕获,再项目中需要抛出异常时只需要抛出自定义的异常`MyException`,就可对该异常进行捕获,当用户请求某个接口如果出现异常时,
后台服务就会返回自定义的错误代码,不会出现乱码的情况,以便于前端对返回结果做统一处理或错误拦截。同时我们也可预先自定义需要抛出的错误,在前段可对错误code进行区别,采用不同的样式提示。
```java
package com.cqjtu.studentdocument.advice;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public enum ExceptionEnums {
    /**
     * 错误返回信息
     */
    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,"当前档案未完善身高体重信息")
    ;
    /**
     * 代码值
     */
    private int code;
    /**
     * 消息
     */
    private String msg;
}
```
#### 安装教程
1. 克隆下项目
2. mysql本地创建数据库,导入sql文件
3. 修改项目yml数据库配置文件为自己的配置
4. 运行项目
5. 在运行前端项目