项目描述

  • 一个项目中有很多资源,而资源的表现形式就是功能菜单,通过将不同菜单赋给不同的角色,而不同的用户拥有不同的角色来达到权限控制

    原型设计

    image.png

    业务关系及表设计

    image.png
    image.png
    dbpms.sql

    1. Set names utf8;
    2. source d:/dbpms.sql

    核心 API 分析与设计

    image.png

    项目构建

  • parent中只有依赖,parent打包方式为pom

  • common中是项目共性代码,实现复用
    • 统一结果返回规范
    • 全局异常处理
    • 拦截器
    • 工具类
    • 切面
    • …….
  • admin中是具体业务的实现代码

image.png

父模块

pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--表示以下的模块依赖依赖继承我,添加依赖继承后自动生成-->
    <modules>
        <module>05-dbpms-common</module>
        <module>05-dbpms-admin</module>
    </modules>

    <!--Spring Boot parent 依赖-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.8.RELEASE</version>
    </parent>

    <!--当前项目工程的坐标-->
    <groupId>org.cy</groupId>
    <artifactId>05-dbpms-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!--定义一些依赖的版本,有些没有定义版本依赖是因为Spring boot parent中自带了某些依赖的版本-->
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <mybatis.starter>2.1.3</mybatis.starter>
        <pagehelper.starter>1.3.0</pagehelper.starter>
        <spring.shiro>1.7.1</spring.shiro>
    </properties>

    <!--依赖-->
    <dependencies>
        <!--Spring DataSource-->
        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--JDBC依赖,封装了HikariCP数据库连接池-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <!--Spring mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.starter}</version>
        </dependency>
        <!--PageHelper (基于mybatis框架实现的分页插件)-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>${pagehelper.starter}</version>
        </dependency>
        <!--Spring Web (提供了web请求的分层设计)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Spring AOP (提供面向切面的实现)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--Lombok (提供对类的字节码功能增强(类中添加set,get,toString,...))-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--热部署依赖(修改完业务代码和配置文件时自动重启服务)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
        <!--Spring Shiro (权限管理框架)-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>${spring.shiro}</version>
        </dependency>
        <!--单元测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <!--排除一些不需要的依赖-->
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <!--项目构建或打包时候需要的而一些依赖-->
        <plugins>
            <!--假如当前插件显示红色,可以为其指定version-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.8.RELEASE</version>
            </plugin>
        </plugins>
    </build>
</project>

通用模块

pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!--在创建此maven项目时,指定自己继承依赖于05-dbpms-parent项目-->
    <parent>
        <artifactId>05-dbpms-parent</artifactId>
        <groupId>org.cy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>05-dbpms-common</artifactId>
    <groupId>org.cy</groupId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

领域对象设计

  • Node
    • 基于此对象存储节菜单节点信息 ```java package com.cy.pj.common.pojo;

import lombok.Data; import java.io.Serializable; //菜单的结构图对应的对象 @Data public class Node implements Serializable { private static final long serialVersionUID = -5843725325348704821L; private Integer id; private String name; private String parentId; }


- CheckBox
   - 存储复选框选项信息
```java
package com.cy.pj.common.pojo;

@Data
public class CheckBox implements Serializable{
    private static final long serialVersionUID = 5531553051566760402L;
    private Integer id;
    private String name;
}
  • JsonResult
    • 统一响应标准 API,对服务端响应到客户端的数据进行统一标准设计 ```java package com.cy.pj.common.pojo;

/**

  • 通过此对象封装服务端响应到客户端的数据,让数据以一种规范化的格式呈现给客户端 / public class JsonResult implements Serializable{ private static final long serialVersionUID = 5531553051566760402L; /**状态码/ private Integer state = 1; //1表示OK,0表示Error /状态码对应的信息*/ private String message=”ok”; /封装正确的查询结果*/ private Object data;

    public JsonResult(){} public JsonResult(String message){

     this.message = message;
    

    } public JsonResult(Integer state, String message){

     this(message);
     this.state = state;
    

    } public JsonResult(Object data){ //new JsonResult(list)

     this.data = data;
    

    } //当出现异常时,可以通过此构造方法对异常信息进行封装 public JsonResult(Throwable exception){ //new JsonResult(exception)

     this(0,exception.getMessage());
    

    } //set与get省略 } ```

    日志切面

  • 定义切入点注解 ```java package com.cy.pj.common.annotation;

/**

  • 自定义注解,希望通过此注解对一些业务方法做标记(对方法进行描述)
  • ,在面向切面编程中,被该注解标记的方法为我们的切入点方法。 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiredLog { String operation(); } ```

    异常类设计

  • 定义业务层特定异常处理类,基于此类封装业务层异常信息

    package com.cy.pj.common.exception;
    /**自定义业务异常*/
    public class ServiceException extends RuntimeException{
      public ServiceException() {
      }
    
      public ServiceException(String message) {
          super(message);
      }
    
      public ServiceException(Throwable cause) {
          super(cause);
      }
    }
    

    工具类

  • IpUtil

    • 获得请求IP的工具类 ```java package com.cy.pj.common.util;

public class IpUtil { private static Logger log = LoggerFactory.getLogger(IpUtil.class);

public static String getIpAddr() {
    HttpServletRequest request = ((ServletRequestAttributes)
            RequestContextHolder.getRequestAttributes()).getRequest();
    String ip = null;
    try {
        ip = request.getHeader("x-forwarded-for");
        if (StringUtil.isEmpty(ip) ||
                "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtil.isEmpty(ip) || ip.length() == 0 ||
                "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtil.isEmpty(ip) ||
                "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtil.isEmpty(ip) ||
                "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtil.isEmpty(ip) ||
                "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
    } catch (Exception e) {
        log.error("IPUtils ERROR ", e);
    }
    return ip;
}

}


- StringUtil
   - 判断字符串是否为null或空串的工具类
```java
package com.cy.pj.common.util;

public class StringUtil {
    public static boolean isEmpty(String str){
        return str==null||"".equals(str);
    }
}
  • ServletUtil
    • 获得请求对象的工具类 ```java package com.cy.pj.common.util;

public class ServletUtil { /*获取请求对象/ public static HttpServletRequest getRequest(){ return getServletRequestAttributes().getRequest(); }

/**通过RequestContextHolder类型获取请求属性*/
public static ServletRequestAttributes getServletRequestAttributes(){
    return (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
}

}


- PageUtil
   - 分页工具类,使用了分页插件PageHelper
```java
package com.cy.pj.common.util;

public class PageUtil {
    /**
     * 通过此方法启动分页查询
     * @param <T> 这里的T为泛型,返回值类型左侧有<T>这种符号的表示方法为泛型方法
     * @return
     */
    public static <T>Page<T> startPage(){
        HttpServletRequest request = ServletUtil.getRequest();
        //页面大小(每页最多显示多少条记录)
        String pageSizeStr = request.getParameter("pageSize");
        //当前页码值(要查第几页的数据)
        String pageCurrentStr = request.getParameter("pageCurrent");
        System.out.println(pageSizeStr);
        System.out.println(pageCurrentStr);
        //在此位置调用PageHelper中的一个方法启动分页
        //在项目中去添加一个PageHelper依赖(后缀是starter的)
        Integer pageCurrent = StringUtil.isEmpty(pageCurrentStr)?1:Integer.parseInt(pageCurrentStr);
        Integer pageSize = StringUtil.isEmpty(pageSizeStr)?10:Integer.parseInt(pageSizeStr);
        //启动PageHelper中的分页拦截器(PageInterceptor)
        return PageHelper.startPage(pageCurrent, pageSize);
    }
}

统一异常处理

package com.cy.pj.common.web;

/**
 * 此注解描述的类为全局异常处理类
 * 从控制层传来的异常会经过前端处理器,前端处理器找被@RestControllerAdvice修饰的全局异常处理类来处理
 * 传过来的异常类型是@ExceptionHandler注解定义的异常类型(此处为RuntimeException)或者它的子异常类型时
 * 才可以使用注解@ExceptionHandler所修饰的方法进行处理
 */
@RestControllerAdvice //@ControllerAdvice+@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    //private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**登录时异常处理
      */
    @ExceptionHandler(ShiroException.class)
    public JsonResult doShiroException(ShiroException e){
        e.printStackTrace();
        JsonResult jr = new JsonResult();
        jr.setState(0);
        if (e instanceof UnknownAccountException){
            jr.setMessage("账户不存在");
        }else if (e instanceof IncorrectCredentialsException){
            jr.setMessage("密码不正确");
        }else if (e instanceof LockedAccountException){
            jr.setMessage("账户被锁定");
        }else if (e instanceof AuthorizationException){
            jr.setMessage("没有权限");
        }else {
            jr.setMessage("认知或授权失败");
        }
        return jr;
    }

    /**@ExceptionHandler注解描述的方法为异常处理方法,
     * 注解中定义的异常类型为方法可以处理的异常类型.*/
    @ExceptionHandler(RuntimeException.class)
    public JsonResult doHandleRuntimeException(RuntimeException e){
        e.printStackTrace(); //打印异常信息到控制台
        log.error("exception msg is {}"+e.getMessage());
        return new JsonResult(e);
    }
    //......
}

拦截器

  • 定义时间拦截器对象

    package com.cy.pj.common.web;
    /**
    * 定义spring web 模块中的拦截器,通过此拦截器对handler中某些方法的进行时间访问限制。
    */
    @Slf4j  //lombok会在当前类编译成class文件时,自动在类文件中添加一个log对象
    public class TimeAccessInterceptor implements HandlerInterceptor {
      // private static final Logger log= LoggerFactory.getLogger(TimeAccessInterceptor.class);
    
      /**preHandle方法会在目标handler方法执行之前进行访问拦截
       * 方法返回值为true时表示请求放行,false表示请求到此结束,
       * 不再去执行handler中的方法
       * */
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          LocalTime now = LocalTime.now(); //JDK8中的时间对象
          int hour = now.getHour(); //获取当前时间对应小时
          log.info("hour="+hour);
          if(hour<8 || hour>22){
              throw new RuntimeException("请在8-22点进行访问");
          }
          return true;
      }
    }
    
  • SpringWebConfig

    • 定义 SpringWebConfig 对象,用于注册拦截器,过滤器等 ```java package com.cy.pj.common.web;

@Configuration //此注解为spring中的一个配置类bean对象 public class SpringWebConfig implements WebMvcConfigurer { /注册拦截器,并且设置要拦截的路径,此方法会在项目启动时就会调用*/ @Override public void addInterceptors(InterceptorRegistry registry) { //注册拦截器,并且设置要拦截的路径 //1、注册拦截器(将拦截器添加到spring容器) registry.addInterceptor(new TimeAccessInterceptor()) //2、设置要拦截的url, .addPathPatterns(“/notice/“); //表示通配符,**表示多层url,拦截所有 .addPathPatterns(“/notice/doSelectBySysNotice”,”/notice/doDeleteSysNotice/“); } }

<a name="A75Pm"></a>
### 主模块
<a name="eHXtJ"></a>
#### pom
```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <!--在创建此maven项目时,指定自己继承依赖于05-dbpms-parent项目-->
    <parent>
        <artifactId>05-dbpms-parent</artifactId>
        <groupId>org.cy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>05-dbpms-admin</artifactId>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--添加common依赖,让admin工程依赖于common工程-->
        <dependency>
            <groupId>org.cy</groupId>
            <artifactId>05-dbpms-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>
  • 配置文件 application.yml

    #server.port
    server:
    port: 8088
    #spring
    spring:
    datasource:
      url: jdbc:mysql:///dbpms?serverTimezone=GMT%2B8&characterEncoding=utf8
      username: root
      password: tarena
    #mybatis
    mybatis:
    mapper-locations: classpath:/mapper/*/*.xml
    #logging
    logging:
    level:
      com.cy: debug
    #shiro
    shiro:
    loginUrl: /login.html
    
  • 添加启动类

    package com.cy;
    @SpringBootApplication
    public class DbpmsApplication {
      public static void main(String[] args) {
          SpringApplication.run(DbpmsApplication.class, args);
      }
    }
    
  • 启动…