此文章适用于 学生管理系统成绩管理系统在线考试系统图书管理系统 等,提供源码下载。

在这里插入图片描述技术架构:Java + SpringBoot + Vue3 + MySQL

一、项目搭建

1.1 开发工具

2024年了,我们就不考虑Eclipse了好吧,直接下载IDEA社区版。
下载地址:https://www.jetbrains.com/idea/download/other.html

1.2 环境配置

1.2.1 JDK

(1)下载JDK1.8 windows64位安装版:https://tool4j.com/files/software/jdk-8u102-windows-x64.exe
(2)安装及环境变量配置:https://blog.csdn.net/i_for/article/details/131128502

1.2.2 Maven

(1)下载Maven3.6.3:https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.zip
(2)配置教程:https://www.cnblogs.com/yu-si/p/14586626.html

1.2.3 MySQL

安装配置教程:https://blog.csdn.net/fattigers/article/details/135558127

1.2.4 NodeJs

下载Nodejs16.20.2:https://nodejs.org/dist/v16.20.2/node-v16.20.2-x64.msi
安装教程:没什么特别的步骤,无脑下一步即可,会自动配置好环境变量

1.3 模板工程下载

在这里插入图片描述

目前后台管理系统用的比较多的模板工程一般是若依,或者是基于若依改造而来的,虽然若依这套框架存在很多问题,但是对于初学者或者小公司来说,确实可以减少很多工作量,还是有可取之处的。
博主使用的模板工程是:youlai-boot,并进行了优化,删除了大量冗余代码,更易于初学者使用,有需要的可以通过Gitee下载。

前端项目地址:https://gitee.com/dwp1216/boot4j_ui.git
后台项目地址:https://gitee.com/dwp1216/boot4j.git

二、数据库表设计

基础工程所需的数据库表SQL文件已经放在项目根目录 /src/resources/sql 下了。
系统内置表如下:

create table sys_menu
(
  id          bigint auto_increment
  primary key,
  parent_id   bigint                  not null comment '父菜单ID',
  tree_path   varchar(255)            null comment '父节点ID路径',
  name        varchar(64)  default '' not null comment '菜单名称',
  type        tinyint                 not null comment '菜单类型(1:菜单 2:目录 3:外链 4:按钮)',
  path        varchar(128) default '' null comment '路由路径(浏览器地址栏路径)',
  component   varchar(128)            null comment '组件路径(vue页面完整路径,省略.vue后缀)',
  perm        varchar(128)            null comment '权限标识',
  visible     tinyint(1)   default 1  not null comment '显示状态(1-显示;0-隐藏)',
  sort        int          default 0  null comment '排序',
  icon        varchar(64)  default '' null comment '菜单图标',
  redirect    varchar(128)            null comment '跳转路径',
  create_time datetime                null comment '创建时间',
  update_time datetime                null comment '更新时间',
  always_show tinyint                 null comment '【目录】只有一个子路由是否始终显示(1:是 0:否)',
  keep_alive  tinyint                 null comment '【菜单】是否开启页面缓存(1:是 0:否)'
)
    comment '菜单管理' charset = utf8mb3;

create table sys_role
(
  id        bigint auto_increment comment '主键id'
  primary key,
  role_code varchar(100)                       null comment '角色标识',
  role_name varchar(100)                       null comment '角色名称',
  remark    varchar(255)                       null comment '描述',
  create_by varchar(100)                       null comment '创建人',
  create_at datetime default CURRENT_TIMESTAMP null comment '创建时间',
  update_by varchar(100)                       null comment '修改人',
  update_at datetime                           null on update CURRENT_TIMESTAMP comment '修改时间'
)
    comment '系统管理-角色信息表' collate = utf8mb4_bin;

create table sys_role_permission
(
  id            bigint auto_increment comment '主键id'
  primary key,
  resource_id   bigint                             null comment '资源ID',
  resource_type varchar(100) charset utf8mb4       null comment '资源类型(menu:菜单、btn:按钮、api:服务)',
  role_id       bigint                             null comment '角色ID',
  create_by     varchar(100) charset utf8mb4       null comment '创建人',
  create_at     datetime default CURRENT_TIMESTAMP null comment '创建时间',
  update_by     varchar(100) charset utf8mb4       null comment '修改人',
  update_at     datetime                           null on update CURRENT_TIMESTAMP comment '修改时间'
)
    comment '系统管理-角色权限表' collate = utf8mb4_bin;

create table sys_user
(
  id           bigint auto_increment comment '主键'
  primary key,
  user_id      bigint                              null comment '用户ID',
  account      varchar(255)                        null comment '账号',
  password     varchar(255)                        null comment '密码',
  role_id      bigint                              null comment '角色ID',
  created_date timestamp default CURRENT_TIMESTAMP null,
  updated_date datetime                            null on update CURRENT_TIMESTAMP comment '修改时间'
)
    comment '系统管理-用户信息表' charset = utf8mb4;

create table sys_user_detail
(
  id           bigint auto_increment comment '主键'
  primary key,
  user_id      bigint                              null comment '用户ID',
  username     varchar(255)                        null comment '用户名',
  avatar       varchar(255)                        null comment '头像',
  sex          varchar(10)                         null comment '性别:1-男、0-女、2-保密',
  age          int                                 null comment '年龄',
  region       varchar(255)                        null comment '地区',
  remark       varchar(255)                        null comment '简介',
  created_date timestamp default CURRENT_TIMESTAMP null,
  updated_date datetime                            null on update CURRENT_TIMESTAMP comment '修改时间',
  phone_num    varchar(255)                        null comment '手机号',
  email        varchar(255)                        null comment '邮箱'
)
    comment '系统管理-用户详情信息表' charset = utf8mb4;

在设计数据库表时,我们可以按如下思路来设计:

  1. 根据需求先设计出有哪些对象,一个对象就是一张表
  2. 对象之间的关联关系

(1) 如果是一对一、一对多的关系,就在对象表上增加另一个对象的ID字段来关联
(2) 如果是多对多的关系,建议使用单独的关系表来保存关联关系

  1. 对于比较通用的信息,建议使用更加通用的对象表来存储,例如文件信息表、用户消息表。

三、模板代码生成

表结构设计好以后,可通过 代码生成器 一键生成代码。image.png
复制或者上传sql文件,即可一键生成。
生成后点击下载,会下载一个zip压缩包,解压之后放到src/main/java目录下即可使用。

四、功能实现

4.1 登录认证功能

对于小型项目,我们可以使用Spring提供的 HandlerInterceptor拦截前端请求,进行登录认证。

  1. 首先创建一个拦截器类SecurityInterceptor:
import com.privacy.guard.util.UserPermission;
import com.privacy.guard.util.security.AuthUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {

    @Autowired
    private AuthUtil authUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        return authUtil.verify(request);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
        UserPermission.remove();
        UserPermission.removeToken();
    }

}
  1. AuthUtil 校验工具类:
import com.privacy.guard.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;

@Component
@Slf4j
public class AuthUtil {

    public boolean verify(HttpServletRequest request) {
        try {
            String token = request.getHeader("Authorization");
            if (token.startsWith("Bearer ")) {
                token = token.substring(7);
            }
            Long userId = JwtUtil.verify(token);
            Assert.notNull(userId, "Token不合法,请勿非法访问");
            return true;
        } catch (Exception e) {
            String requestURI = request.getRequestURI();
            log.error("鉴权未通过, 请求地址 = {}", requestURI, e);
            throw new RuntimeException(e.getMessage());
        }
    }

}
  1. JwtUtil 生成 Token 工具类:
import cn.hutool.json.JSONUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import java.util.Map;

@Slf4j
public class JwtUtil {

    private static final Algorithm key = Algorithm.HMAC256("abcdjhtsrgarehdgjheras");

    /**
     * 生成Token
     *
     * @return
     */
    public static String createToken(Long userId) {
        String sign = JWT.create()
        .withClaim("userId", userId)
        .sign(key);
        return sign;
    }

    /**
     * 验证Token
     *
     * @param jwtStr
     * @return
     */
    public static Long verify(String jwtStr) {
        JWTVerifier build = JWT.require(key).build();
        try {
            //验签
            DecodedJWT verify = build.verify(jwtStr);
            byte[] bytes = Base64.decodeBase64(verify.getPayload());
            String json = new String(bytes);
            return Long.parseLong(JSONUtil.toBean(json, Map.class).get("userId").toString());
        } catch (AlgorithmMismatchException e) {
            //算法错误
            log.error("加密算法和解密算法不一致");
        } catch (SignatureVerificationException e) {
            //验签失败,验签pwd密码错误
            log.error("解密密码错误");
        } catch (TokenExpiredException e) {
            //token过期
            log.error("Token过期");
        } catch (InvalidClaimException e) {
            //获取不到对应的负荷信息
            log.error("获取不到对应的负荷信息");
        } catch (Exception e) {
            log.error("未获取到正确的userId", e);
        }
        return null;
    }

}
  1. 然后创建一个WebConfig类,继承WebMvcConfigurer,实现请求拦截与过滤配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private SecurityInterceptor securityInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        /**
        * 所有 /api 开头的接口会被 securityInterceptor 拦截器拦截
        * 所有 /auth 开头的接口会被 securityInterceptor 拦截器拦截
        * 所有 /api/auth 开头的接口不会被拦截
        */
        registry.addInterceptor(securityInterceptor)
        .addPathPatterns("/api/**")
        .addPathPatterns("/auth/**")
        .excludePathPatterns("/api/auth/**");
    }

}

4.2 菜单权限控制

在模板工程中,我们已经定义好了菜单表的结构,根据用户权限,查询出用户具有的菜单集合,并进行组装,形成菜单树。(完整代码可下载源码查看)

/**
 * 获取路由列表
 */
@Override
public List<RouteVO> listRoutes() {
    SysUser user = userService.findByUserId(UserPermission.get());
    List<RouteBO> menuList = sysMenuMapper.listRoutes(user.getRoleId());
    return buildRoutes(Constants.ROOT_NODE_ID, menuList);
}

/**
 * 递归生成菜单路由层级列表
 *
 * @param parentId 父级ID
 * @param menuList 菜单列表
 * @return 路由层级列表
 */
private List<RouteVO> buildRoutes(Long parentId, List<RouteBO> menuList) {
    List<RouteVO> routeList = new ArrayList<>();
    for (RouteBO menu : menuList) {
        if (menu.getParentId().equals(parentId)) {
            RouteVO routeVO = toRouteVo(menu);
            List<RouteVO> children = buildRoutes(menu.getId(), menuList);
            if (!children.isEmpty()) {
                routeVO.setChildren(children);
            }
            routeList.add(routeVO);
        }
    }
    return routeList;
}

/**
 * 根据RouteBO创建RouteVO
 */
private RouteVO toRouteVo(RouteBO routeBO) {
    RouteVO routeVO = new RouteVO();
    String routeName = StringUtils.capitalize(StrUtil.toCamelCase(routeBO.getPath().replaceAll("-", "_")));  // 路由 name 需要驼峰,首字母大写
    routeVO.setName(routeName); // 根据name路由跳转 this.$router.push({name:xxx})
    routeVO.setPath(routeBO.getPath()); // 根据path路由跳转 this.$router.push({path:xxx})
    routeVO.setRedirect(routeBO.getRedirect());
    routeVO.setComponent(routeBO.getComponent());
    
    RouteVO.Meta meta = new RouteVO.Meta();
    meta.setTitle(routeBO.getName());
    meta.setIcon(routeBO.getIcon());
    meta.setRoles(routeBO.getRoles());
    meta.setHidden(StatusEnum.DISABLE.getValue().equals(routeBO.getVisible()));
    // 【菜单】是否开启页面缓存
    if (MenuTypeEnum.MENU.getValue().equals(routeBO.getType())
        && routeBO.getKeepAlive() != null && 1 == routeBO.getKeepAlive()) {
        meta.setKeepAlive(true);
    }
    // 【目录】只有一个子路由是否始终显示
    if (MenuTypeEnum.CATALOG.getValue().equals(routeBO.getType())
        && routeBO.getAlwaysShow() != null && 1 == routeBO.getAlwaysShow()) {
        meta.setAlwaysShow(true);
    }
    routeVO.setMeta(meta);
    return routeVO;
}

五、源码下载

前端项目地址:https://gitee.com/dwp1216/boot4j_ui.git
后台项目地址:https://gitee.com/dwp1216/boot4j.git

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐