springboot+mybatisPlus代码层级梳理

## 代码层级梳理

#### 代码层级

![UTOOLS1588253419016.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253419016.png)

#### annotation层

![UTOOLS1588253490167.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253490167.png)

###### ExcelVoAttribut

主要用于写@ExcelVoAttribute注解,可以更灵活的配置Excel数据表导出的一些基本配置

###### LogDirection

主要用于写@LogDirection注解,注解里包含一个字段(接口描述),通过接口添加这个注解,填写上接口的功能,当用户在访问接口时候,可以利用aop技术,把用户的操作记到日志里

#### aspect层

![UTOOLS1588253846594.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588253846594.png)

###### LogDrectionAspect

主要定义了aop切点的位置和增强的方法

```java

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Aspect

@Component

@Slf4j

public class LogDirectionAspect {

    @Pointcut("@annotation(logDirection)")

    public void doLogDirection(LogDirection logDirection){

    }

    @Around(value = "doLogDirection(logDirection)",argNames = "pjp,logDirection")

    public Object doBefore(ProceedingJoinPoint pjp,LogDirection logDirection) throws  Throwable{

        if (!"".equals(logDirection.direction())) {

            log.info(logDirection.direction()+"接口被调用");

            return  pjp.proceed();

        }

        return "接口信息不规范";

    }

}

```

@Pointcut定义切点,此处定义切点为使用@LogDirection的方法

@Around定义增强的方法,通过around的参数ProceedingJoinPoint可以很好的控制程序能不能继续执行下一步,在权限判定发挥着比较重要的作用

拓展:

利用下面的这种方法,需要在xml文件中配置bean实例,当然也可以在方法中通过手动扫包的方法获得bean实例进行操作

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Aspect

public class MyAdvice {

    /**

    * 配置切点

    */

    @Pointcut("execution(* com.soft1851.spring.web.dao..*.insert*(..))")

    public void pointCut() {

    }

    /**

    * 配置前置增强

    */

    @Before("MyAdvice.pointCut()")

    public void beforeMethod() {

        System.out.println("等待数据插入");

    }

    /**

    * 配置后置增强

    */

    @AfterReturning("execution(* com.soft1851.spring.web.dao..*.*(..))")

    public void afterMethod(){

        System.out.println("关闭数据库");

    }

}

```

xml配置实例

```

<bean name="forumDao" class="com.soft1851.spring.web.dao.impl.ForumDaoImpl"/>

    <bean name="myAdvice" class="com.soft1851.spring.web.interceptor.MyAdvice"/>

    <aop:aspectj-autoproxy/>

```

#### common层

![UTOOLS1588254433232.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254433232.png)

ResponseResult和ResultCode

这两个类可以将响应体统一进行封装,将接口的响应体规范化,使前端获取数据也更加友好

贴下代码,防止以后找不到

ResponseResult类

```

package com.soft1851.music.admin.common;

import lombok.Data;

import java.io.Serializable;

/**

* @author Johnny

* @Date: 2020/4/21 19:52

* @Description:

*/

@Data

public class ResponseResult implements Serializable {

    private static final long serialVersionUID = -3948389268046368059L;

    private Integer code;

    private String msg;

    private Object data;

    private ResponseResult() {

    }

    public ResponseResult(Integer code, String msg) {

        this.code = code;

        this.msg = msg;

    }

    public static ResponseResult success() {

        ResponseResult result = new ResponseResult();

        result.setResultCode(ResultCode.SUCCESS);

        return result;

    }

    public static ResponseResult success(Object data) {

        ResponseResult result = new ResponseResult();

        result.setResultCode(ResultCode.SUCCESS);

        result.setData(data);

        return result;

    }

    public static ResponseResult failure(ResultCode resultCode) {

        ResponseResult result = new ResponseResult();

        result.setResultCode(resultCode);

        return result;

    }

    public static ResponseResult failure(ResultCode resultCode, Object data) {

        ResponseResult result = new ResponseResult();

        result.setResultCode(resultCode);

        result.setData(data);

        return result;

    }

    public void setResultCode(ResultCode code) {

        this.code = code.code();

        this.msg = code.message();

    }

}

```

ResultCode类(枚举)

```

package com.soft1851.music.admin.common;

/**

* @author Johnny

* @Date: 2020/4/21 19:52

* @Description:

*/

public enum ResultCode {

    /* 成功状态码 */

    SUCCESS(1, "成功"),

    /* 通用错误:10001-19999 */

    PARAM_IS_INVALID(10001, "参数无效"),

    PARAM_IS_BLANK(10002, "参数为空"),

    PARAM_TYPE_BIND_ERROR(10003, "参数类型错误"),

    PARAM_NOT_COMPLETE(10004, "参数缺失"),

    HTTP_METHOD_ERROR(10005, "请求方法错误"),

    HTTP_METHOD_NOT_ALLOWED(10006, "请求方法不允许"),

    HTTP_NOT_FOUND(10007, "请求地址错误"),

    BOUND_STATEMENT_NOT_FOUND(10008, "Mybatis未绑定"),

    CONNECTION_ERROR(10009, "网络连接错误"),

    ARITHMETIC_ERROR(100010, "计算错误"),

    /* 用户错误:20001-29999*/

    USER_NOT_SIGN_IN(20001, "请先登录"),

    USER_PASSWORD_ERROR(20002, "密码错误"),

    USER_ACCOUNT_ERROR(20003, "账号错误"),

    USER_VERIFY_CODE_ERROR(20004, "验证码错误"),

    USER_CODE_TIMEOUT(20005, "验证码失效"),

    USER_ACCOUNT_FORBIDDEN(20006, "账号已被禁用"),

    USER_SIGN_UP_FAIL(20007, "用户注册失败"),

    USER_SIGN_IN_FAIL(20008, "用户登录失败"),

    USER_NOT_FOUND(20009, "用户不存在"),

    USER_NO_AUTH(20019, "用户权限不足"),

    USER_TOKEN_EXPIRES(200010, "Token已过期"),

    /* 业务错误:30001-39999 */

    SMS_ERROR(30001, "短信业务出现问题"),

    UPLOAD_ERROR(30002, "上传文件业务出现问题"),

    CAPTCHA_ERROR(30003, "验证码业务出现问题"),

    /* 数据错误:40001-49999 */

    RESULT_CODE_DATA_NONE(50001, "数据未找到"),

    DATA_IS_WRONG(50002, "数据有误"),

    DATA_ALREADY_EXISTED(50003, "数据已存在"),

    DATABASE_ERROR(50004, "数据库操作异常"),

    /* 服务器或系统错误:50001-599999 */

    SERVER_ERROR(50000, "服务器错误,请稍后重试"),

    SYSTEM_ERROR(40001, "系统错误,请稍后重试"),

    /* 接口错误:60001-69999 */

    INTERFACE_INNER_INVOKE_ERROR(60001, "内部系统接口调用异常"),

    INTERFACE_OUTER_INVOKE_ERROR(60002, "外部系统接口调用异常"),

    INTERFACE_FORBID_VISIT(60003, "该接口禁止访问"),

    INTERFACE_ADDRESS_INVALID(60004, "接口地址无效"),

    INTERFACE_REQUEST_TIMEOUT(60005, "接口请求超时"),

    INTERFACE_EXCEED_LOAD(60006, "接口负载过高"),

    /* 权限错误:70001-79999 */

    PERMISSION_NO_ACCESS(70001,"无访问权限");

    private Integer code;

    private String message;

    ResultCode(Integer code, String message) {

        this.code = code;

        this.message = message;

    }

    public Integer code() {

        return this.code;

    }

    public String message() {

        return this.message;

    }

    public static String getMessage(String name) {

        for (ResultCode item : ResultCode.values()) {

            if (item.name().equals(name)) {

                return item.message;

            }

        }

        return name;

    }

    public static Integer getCode(String name) {

        for (ResultCode item : ResultCode.values()) {

            if (item.name().equals(name)) {

                return item.code;

            }

        }

        return null;

    }

    @Override

    public String toString() {

        return this.name();

    }

}

```

#### config层

![UTOOLS1588254579776.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588254579776.png)

###### CaptchaConfig

主要是为kaptcha谷歌验证码的一些基本信息进行配置,比如字体,验证码位数等等

附上一些kaptha的基本配置信息

| **Constant**                    | **描述**                                                    | **默认值**                                            |

| -------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------- |

| kaptcha.border                  | 图片边框,合法值:yes , no                                  | yes                                                  |

| kaptcha.border.color            | 边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue. | black                                                |

| kaptcha.border.thickness        | 边框厚度,合法值:>0                                        | 1                                                    |

| kaptcha.image.width              | 图片宽                                                      | 200                                                  |

| kaptcha.image.height            | 图片高                                                      | 50                                                    |

| kaptcha.producer.impl            | 图片实现类                                                  | com.google.code.kaptcha.impl.DefaultKaptcha          |

| kaptcha.textproducer.impl        | 文本实现类                                                  | com.google.code.kaptcha.text.impl.DefaultTextCreator  |

| kaptcha.textproducer.char.string | 文本集合,验证码值从此集合中获取                            | abcde2345678gfynmnpwx                                |

| kaptcha.textproducer.char.length | 验证码长度                                                  | 5                                                    |

| kaptcha.textproducer.font.names  | 字体                                                        | Arial, Courier                                        |

| kaptcha.textproducer.font.size  | 字体大小                                                    | 40px.                                                |

| kaptcha.textproducer.font.color  | 字体颜色,合法值: r,g,b  或者 white,black,blue.            | black                                                |

| kaptcha.textproducer.char.space  | 文字间隔                                                    | 2                                                    |

| kaptcha.noise.impl              | 干扰实现类                                                  | com.google.code.kaptcha.impl.DefaultNoise            |

| kaptcha.noise.color              | 干扰 颜色,合法值: r,g,b 或者 white,black,blue.            | black                                                |

| kaptcha.obscurificator.impl      | 图片样式:  水纹com.google.code.kaptcha.impl.WaterRipple  鱼眼com.google.code.kaptcha.impl.FishEyeGimpy  阴影com.google.code.kaptcha.impl.ShadowGimpy | com.google.code.kaptcha.impl.WaterRipple              |

| kaptcha.background.impl          | 背景实现类                                                  | com.google.code.kaptcha.impl.DefaultBackground        |

| kaptcha.background.clear.from    | 背景颜色渐变,开始颜色                                      | light grey                                            |

| kaptcha.background.clear.to      | 背景颜色渐变, 结束颜色                                      | white                                                |

| kaptcha.word.impl                | 文字渲染器                                                  | com.google.code.kaptcha.text.impl.DefaultWordRenderer |

| kaptcha.session.key              | session key                                                  | KAPTCHA_SESSION_KEY                                  |

| kaptcha.session.date            | session date                                                | KAPTCHA_SESSION_DATE                                  |

###### CorsConfig

主要是为跨域做一些基本的配置,学习阶段后端必备

代码展示,防止以后忘记

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Configuration

public class CorsConfig {

    @Bean

    public FilterRegistrationBean<CorsFilter> corsFilter() {

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        CorsConfiguration config = new CorsConfiguration();

        config.setAllowCredentials(true);

        //放行所有跨域的客户端domain

        config.addAllowedOrigin("*");

        //允许的请求方法列表

        String[] requestMethods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};

        List<String> allowedRequestMethods = Arrays.asList(requestMethods);

        config.setAllowedMethods(allowedRequestMethods);

        //允许的客户端请求头列表

        String[] requestHeaders = {"x-requested-with", "Content-Type", "Authorization"};

        List<String> allowedHeaders = Arrays.asList(requestHeaders);

        config.setAllowedHeaders(allowedHeaders);

        //允许的响应头列表

        String[] responseHeaders = {"Access-Control-Expose-Headers", "Authorization"};

        List<String> allowedExposedHeaders = Arrays.asList(responseHeaders);

        config.setExposedHeaders(allowedExposedHeaders);

        source.registerCorsConfiguration("/**", config);

        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));

        // 这个顺序很重要,设置在最前

        bean.setOrder(0);

        return bean;

    }

}

```

###### MybatisPlusConfig

主要是为mybatisplus做一些基本配置(如果要有分页查询数据,则这个配置文件必须要有)

```

/**

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@EnableTransactionManagement

@Configuration

@MapperScan("com.baomidou.cloud.service.*.mapper*")

public class MybatisPlusConfig {

    @Bean

    public PaginationInterceptor paginationInterceptor() {

        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();

        //设置请求的页面大于最大页操作,true调回到首页,false继续请求,默认为false

        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));

        return paginationInterceptor;

    }

}

```

###### MySqlGenerator

代码生成器,功能强大无比,根据数据表,一键生成各种层级,各种实体

```

/**

* @ClassName MySqlGenerator

* @Description 代码生成器

* @Author Johnny

* @Date 2020/4/16

* @Version 1.0

*/

@Slf4j

public class MySqlGenerator {

    public static void main(String[] args) {

        //全局策略配置

        GlobalConfig config = new GlobalConfig();

        //获得当前项目根路径d:/dev/SpringBoot/spring-boot-learning

        String projectPath = System.getProperty("user.dir");

        config.setActiveRecord(true)

                //作者注释

                .setAuthor("Johnny")

                //代码生成输出路径

                .setOutputDir(projectPath + "/src/main/java")

                //覆盖已有文件,默认false

                .setFileOverride(true)

                //是否打开输出目录窗口。默认true

                .setOpen(false)

                //开启swagger2模式

                //.setSwagger2(true)

                //开启ActiveRecord模式

                .setActiveRecord(true)

                //mapper添加restMap

                .setBaseResultMap(true)

                //mapper添加Base_Column_List

                .setBaseColumnList(true)

                //时间类型对应策略,默认time_pack

                //.setDateType(DateType.TIME_PACK)

                //相关包中的接口和类名后缀

                .setMapperName("%sMapper")

                .setServiceName("%sService")

                .setServiceImplName("%sServiceImpl");

        //数据库表配置,通过该配置,可指定需要生成哪些表或者排除哪些表

        StrategyConfig strategyConfig = new StrategyConfig();

        //是否大写命名

        strategyConfig.setCapitalMode(true)

                //是否跳过视图

                .setSkipView(true)

                //数据库表映射到实体的命名策略为驼峰式

                .setNaming(NamingStrategy.underline_to_camel)

                //生成表,可以写多个,如果不加参数,默认为所有表

                .setInclude()

                .setEntityBuilderModel(true)

                .setEntityLombokModel(true)

                .setRestControllerStyle(true)

                .setEntityTableFieldAnnotationEnable(true);

        //包名配置

        PackageConfig packageConfig = new PackageConfig();

        //父包名

        packageConfig.setParent("com.soft1851.music.admin")

                .setMapper("mapper")

                .setService("service")

                .setController("controller")

                .setXml("xml")

                .setEntity("entity");

        //数据源配置

        DataSourceConfig dataSourceConfig = new DataSourceConfig();

        dataSourceConfig.setDbType(DbType.MYSQL)

                .setDriverName("com.mysql.jdbc.Driver")

                .setUrl("jdbc:mysql://rm-m5ee476bu350735gjeo.mysql.rds.aliyuncs.com:3306/cloud_music?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true")

                .setUsername("root")

                .setPassword("XuNiit_#");

        AutoGenerator autoGenerator = new AutoGenerator();

        autoGenerator.setGlobalConfig(config)

                .setStrategy(strategyConfig)

                .setDataSource(dataSourceConfig)

                .setTemplateEngine(new FreemarkerTemplateEngine())

                .setPackageInfo(packageConfig);

        autoGenerator.execute();

        log.info("=============代码生成成功================");

    }

}

```

###### RedisConfig

主要对RedisConfig缓存数据库进行一些基本配置

```

/**

* @author Johnny

* @Date: 2020/4/21 20:45

* @Description:

*/

@Configuration

@EnableCaching

public class RedisConfig {

    @Bean

    CacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        //初始化RedisCacheWrite

        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();

        //设置默认过期时间为1天

        defaultCacheConfig.entryTtl(Duration.ofDays(1));

        //初始化RedisCacheManger

        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);

    }

    @Bean

    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

        RedisTemplate<Object,Object> template = new RedisTemplate<>();

        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)

        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);

        ObjectMapper mapper = new ObjectMapper();

        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        //使用stringRedisSerializer来序列化和反序列化redis的value值

        template.setKeySerializer(new StringRedisSerializer());

        template.afterPropertiesSet();

        return template;

    }

    @Bean

    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {

        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();

        stringRedisTemplate.setConnectionFactory(factory);

        return stringRedisTemplate;

    }

}

```

###### WebConfig

这个类中主要配置了拦截器,可以对部分页面进行统一拦截,当然这个只是统一的拦截,具体的拦截所要进行的操作,还在下文

```

/**

* @author Johnny

* @Date: 2020/4/21 20:58

* @Description:

*/

@Configuration

public class WebConfig implements WebMvcConfigurer {

    @Resource

    private LoginInterceptor loginInterceptor;

    @Resource

    private JwtInterceptor jwtInterceptor;

    /**

    * 添加拦截器配置

    */

    @Override

    public void addInterceptors(InterceptorRegistry registry) {

        //拦截路径可自行配置多个 可用 ,分隔开

        registry.addInterceptor(loginInterceptor).addPathPatterns("/sysAdmin/login").excludePathPatterns("/**").excludePathPatterns("/static/**");

//        registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns("/sysAdmin/login", "/captcha","/songList/page").excludePathPatterns("/static/**");

    }

}

```

#### controller层

controller层暂时带过,后面有时间的话把controller层常用注解汇总整理下

#### dto层

目前还没有将entity、vo、dto统一放到domain里,dto里主要是用户请求接口时需要传的参数,vo主要时后端返回前端展示的参数,而entity就是我们和数据库表所吻合的类。举例:我们数据库中的用户表的字段肯定不只时账号和密码,会有一些其他字段,比如加密盐、用户基本信息等等,但是用户请求登录接口时,只需要传账号和密码就可以了,此时此刻,为了防止资源的浪费,我们就定义一个dto来接收前端传来的参数。当用户进入主界面时,查看个人信息,后端并不需要将用户id、加密盐等私密信息展示到前端,此时,同样为了节省资源,我们定义vo视图对象,将前端需要的参数放进去,再传入前端。

#### entity层

entity层主要是将数据库中的表一对一的写好,此处是直接用代码生成器生成

#### exception

![image-20200430222108856](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430222108856.png)

###### CustomException

定义全局异常处理,使用时直接throws CustomException就好

```

/**

* @author Johnny

* @Date: 2020/4/22 10:48

* @Description:

*/

public class CustomException extends  RuntimeException {

    protected ResultCode resultCode;

    public CustomException(String msg, ResultCode resultCode) {

        super(msg);

        this.resultCode = resultCode;

    }

    public ResultCode getResultCode() {

        return resultCode;

    }

}

```

###### GlobalException

也是全局异常处理,但和上面略有区别,非手动throw,spring在遇到异常时,会自动来这个类里查找,如果已经有配置过的,就会按照配置要求输出内容

```

/**

* @author Johnny

* @Date: 2020/4/30 18:45

* @Description:

*/

@ControllerAdvice

public class GlobalException {

    @ExceptionHandler(ConstraintViolationException.class)

    ResponseResult handleConstraintViolationException(ConstraintViolationException e) {

        return ResponseResult.failure(ResultCode.DATA_IS_WRONG);

    }

}

```

###### JwtException

同CustomException类格式一样,但这个侧重于处理Jwt模块的异常

#### filter

![UTOOLS1588256707418.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256707418.png)

###### ChannelFilter

```

/**

* @author Johnny

* @Date: 2020/4/21 20:19

* @Description:

*/

@WebFilter(urlPatterns = "/*",filterName = "channelFilter")

public class ChannelFilter implements Filter {

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        ServletRequest requestWrapper = null;

        if (servletRequest instanceof HttpServletRequest) {

            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);

        }

        if (requestWrapper == null) {

            filterChain.doFilter(servletRequest,servletResponse);

        }else{

            filterChain.doFilter(requestWrapper, servletResponse);

        }

    }

    @Override

    public void destroy() {

    }

}

```

###### LoginFilter

这个类主要针对用户登录进行过滤

```

/**

* @author Johnny

* @Date: 2020/4/26 10:46

* @Description:

*/

@Slf4j

@WebFilter(urlPatterns = "/login",filterName = "LoginFilter")

public class LoginFilter implements Filter {

    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        ServletRequest requestWrapper = null;

        if (servletRequest instanceof HttpServletRequest) {

            String url = ((HttpServletRequest) servletRequest).getRequestURI();

            //判断接口是否为导入接口

            if ("/resource/guide".equals(url)) {

                Part file = ((HttpServletRequest) servletRequest).getPart("file");

                log.info("文件名:" + file);

            }

            requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);

        }

        if (requestWrapper == null) {

            filterChain.doFilter(servletRequest, servletResponse);

        }else{

            filterChain.doFilter(requestWrapper, servletResponse);

        }

    }

    @Override

    public void destroy() {

    }

}

```

#### handle层

![UTOOLS1588256874063.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588256874063.png)

###### GlobalExceptionHandle

定义全局异常

```

/**

* @author Johnny

* @Date: 2020/4/22 10:40

* @Description:

*/

@RestControllerAdvice(annotations = {RestController.class, Controller.class})

@Slf4j

public class GlobalExceptionHandle {

    //自定义异常

    /**

    * JwtException

    * @param exception

    * @return

    */

    @ExceptionHandler(value = {JwtException.class})

    @ResponseBody

    public ResponseResult sendError(JwtException exception) {

        log.error(exception.getMessage());

        return ResponseResult.failure(exception.getResultCode());

    }

    /**

    * CustomException

    * @param exception

    * @return

    */

    @ExceptionHandler(value = {CustomException.class})

    @ResponseBody

    public ResponseResult sendError(CustomException exception) {

        log.error(exception.getMessage());

        return ResponseResult.failure(exception.getResultCode());

    }

    //系统级异常

    /**

    * InvalidClassException

    * @param exception

    * @return

    */

    @ExceptionHandler(value = {InvalidClassException.class})

    @ResponseBody

    public ResponseResult sendError(InvalidClaimException exception) {

        log.error(exception.getMessage());

        //返回token已经过期

        return ResponseResult.failure(ResultCode.USER_TOKEN_EXPIRES);

    }

    /**

    * NullPointerException

    * @param exception

    * @return

    */

    @ExceptionHandler(value = {NullPointerException.class})

    @ResponseBody

    public ResponseResult sendError(NullPointerException exception) {

        log.error(exception.getMessage());

        //空指针提示数据未找到

        return ResponseResult.failure(ResultCode.RESULT_CODE_DATA_NONE);

    }

    /**

    * IOException

    * @param exception

    * @return

    */

    @ExceptionHandler(value = {IOException.class})

    @ResponseBody

    public ResponseResult sendError(IOException exception) {

        log.error(exception.getMessage());

        //io异常提示验证码出现问题

        return ResponseResult.failure(ResultCode.CAPTCHA_ERROR);

    }

```

###### GlobalResponseHandle

处理全局的响应头(当接口返回类型非ResponseResult类型时,可以自动转换)

```

/**

* @author Johnny

* @Date: 2020/4/22 10:55

* @Description:

*/

@ControllerAdvice

public class GlobalResponseHandle implements ResponseBodyAdvice {

    /**

    * 处理响应的具体方法

    * @param returnType

    * @param converterType

    * @return

    */

    @Override

    public boolean supports(MethodParameter returnType, Class converterType) {

        return true;

    }

    @Override

    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        if (body instanceof ResponseResult) {

            return body;

        }else{

            return ResponseResult.success(body);

        }

    }

}

```

#### interceptor

![image-20200430223215350](C:\Users\Jack\AppData\Roaming\Typora\typora-user-images\image-20200430223215350.png)

###### JwtInterceptor

```

/**

* @author Johnny

* @Date: 2020/4/23 19:41

* @Description:

*/

@Slf4j

@Component

public class JwtInterceptor  implements HandlerInterceptor {

    @Resource

    SysRoleService roleService;

    @Resource

    private RedisService redisService;

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("Authorization");

        log.info("token———————————"+token);

        //认证

        if (token == null) {

            log.info("用户未登录");

            return false;

        }else{

            //登录之后

            //从请求头当中取出用户id

            String adminId = request.getHeader("id");

            log.info("用户id" + adminId);

            //在redis中检查是否存在adminId为key的数据

            if (!redisService.existsKey(adminId)) {

                log.info("redis中不存在此键");

            }else{

                String secrect = redisService.getValue(adminId, String.class);

                //从token中解析出roles字符串

                String roles = JwtTokenUtil.getRoles(token,secrect);

                //反序列化成List

                List<SysRole> sysRoles = JSONArray.parseArray(roles, SysRole.class);

                //从request中取得客户端传输的roleId

                String roleId = request.getParameter("roleId");

                log.info("roleId:"+roleId);

                boolean flag = roleService.checkRole(sysRoles,Integer.parseInt(roleId));

                if(flag){

                    return true;

                }else{

                  log.info("用户不具备此角色");

                }

            }

            return false;

        }

    }

}

```

###### LoginInterceptor

```

/**

* @author Johnny

* @Date: 2020/4/21 21:00

* @Description:

*/

@Slf4j

@Component

public class LoginInterceptor implements HandlerInterceptor {

    @Resource

    private RedisService redisService;

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handle) {

        //将request包装成HttpServletWrapper类型

        RequestWrapper requestWrapper = new RequestWrapper(request);

        //取得请求的json对象

        String body = requestWrapper.getBody();

        log.info(body);

        //从redis中取得指定用户名的验证码

        JSONObject jsonObject = JSONObject.parseObject(body);

        String name = jsonObject.getString("name");

        System.out.println(name);

        String password = jsonObject.getString("password");

        System.out.println(password);

        String verifyCode = jsonObject.getString("verifyCode");

        System.out.println(verifyCode);

        LoginDto loginDto = LoginDto.builder().name(name).password(password).verifyCode(verifyCode).build();

        return true;

//        if (redisService.existsKey(loginDto.getName())) {

//            //获取redis中的验证码

//            String correctCode = redisService.getValue(name, String.class);

//            //忽略大小写,成功则放行到controller调用登录接口

//            if (correctCode.equalsIgnoreCase(verifyCode)) {

//                return true;

//            }else{

//                log.info("验证码错误");

//                return false;

//            }

//        }else{

//            log.info("验证码失效");

//            return false;

//        }

    }

}

```

#### mapper层

这个目前不做过多赘述,我会尽快整理下mybatisplus的常用语法发出来

#### util层

![UTOOLS1588257313481.png](https://johnnycc.oss-cn-beijing.aliyuncs.com/UTOOLS1588257313481.png)

###### ExcelConsumer

```

/**

* @author Johnny

* @Date: 2020/4/28 21:53

* @Description:

*/

@Slf4j

public class ExcelConsumer<T>  implements Runnable {

    /**

    * 一张工作表可以容纳的最大行数

    */

    private static Integer SHEET_SIZE = 100000;

    /**

    * 导出的Vo数据类型

    */

    private Class<T> clazz;

    /**

    * 工作簿

    */

    private SXSSFWorkbook wb;

    /**

    * 工作表名称

    */

    private String sheetName;

    /**

    * 数据缓冲区对象

    */

    private ExportDataAdapter<T> exportDataAdapter;

    /**

    * 线程同步

    */

    private CountDownLatch latch;

    /**

    * 构造方法

    *

    * @param clazz

    */

    public ExcelConsumer(Class<T> clazz, ExportDataAdapter<T> exportDataAdapter, SXSSFWorkbook wb, CountDownLatch latch, String sheetName) {

        if (clazz == null || wb == null || exportDataAdapter == null || latch == null) {

            log.error("ExcelConsumer::初始化对象参数不能为空");

            return;

        }

        this.clazz = clazz;

        this.exportDataAdapter = exportDataAdapter;

        this.wb = wb;

        this.latch = latch;

        this.sheetName = sheetName == null ? "UnNamedSheet" : sheetName;

    }

    @Override

    public void run() {

        //初始化excel导出工具类

        ExcelUtil<T> excelUtil = new ExcelUtil<>(this.clazz);

        Sheet sheet = null;

        int sheetNo = 0;

        int rowNum = 1;

        T vo;

        //生产者还在生产数据

        while (latch.getCount() > 1) {

            //生成sheetName

            if (rowNum == 1) {

                sheetNo++;

                sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));

                excelUtil.setColumnTitle(sheet);

            }

            //获取数据

            vo = exportDataAdapter.getData();

            //往excel添加一行数据

            excelUtil.addRowData(vo, wb, sheet, rowNum);

            rowNum++;

            //准备生成下一个sheetName

            if (rowNum == SHEET_SIZE + 1) {

                rowNum = 1;

            }

        }

        //生产者不再生产数据,取剩余数据,并将数据写入excel

        Integer reminderDataSize = exportDataAdapter.getDataSize();

        T reminderData;

        if (reminderDataSize > 0) {

            for (int i = 0; i < reminderDataSize; i++) {

                reminderData = exportDataAdapter.getData();

                if (rowNum == 1) {

                    sheetNo++;

                    sheet = excelUtil.createSheet(wb, sheetName.concat(Integer.toString(sheetNo)));

                    excelUtil.setColumnTitle(sheet);

                }

                excelUtil.addRowData(reminderData, wb, sheet, rowNum);

                rowNum++;

                if (rowNum == SHEET_SIZE + 1) {

                    rowNum = 1;

                }

            }

        }

        log.info("数据导出完成");

        latch.countDown();

    }

}

```

###### ExcelUtil

```

/**

* @author Johnny

* @Date: 2020/4/28 21:47

* @Description:

*/

public class ExcelUtil<T> {

    Class<T> clazz;

    /**

    * 表头字段列表

    */

    private List<Field> fields;

    /**

    * 数字单元格对象

    */

    private CellStyle decimalCellStyle = null;

    /**

    * 日期时间单元格对象

    */

    private CellStyle dateTimeCellStyle = null;

    /**

    * 构造方法

    *

    * @param clazz

    */

    public ExcelUtil(Class<T> clazz) {

        this.clazz = clazz;

    }

    /**

    * 添加一条数据

    *

    * @param vo:需要导出的vo对象

    * @param wb:工作簿

    * @param sheet:工作表

    * @param rowNum:当前行号

    */

    public void addRowData(T vo, SXSSFWorkbook wb, Sheet sheet, int rowNum) {

        //创建一行

        Row row = sheet.createRow(rowNum);

        Field field;

        Cell cell;

        ExcelVoAttribute attr;

        int fieldSize = fields.size();

        // 遍历入参对象的所有属性

        for (int j = 0; j < fieldSize; j++) {

            // 通过反射获得需要导出的入参对象的所有属性

            field = fields.get(j);

            // 设置实体类私有属性可访问

            field.setAccessible(true);

            // 获取所有添加了注解的属性

            attr = field.getAnnotation(ExcelVoAttribute.class);

            // 给每个属性创建一个单元格

            cell = row.createCell(j);

            try {

                this.setCellValue(attr, field.get(vo), wb, cell);

            } catch (IllegalAccessException e) {

                e.printStackTrace();

            }

        }

    }

    /**

    * 根据注解类判断字段类型设置excel单元格数据格式方法

    *

    * @param attr

    * @param valueObject

    * @param workbook

    * @param cell

    */

    private void setCellValue(ExcelVoAttribute attr, Object valueObject, SXSSFWorkbook workbook, Cell cell) {

        String returnValue;

        if (attr.isNumber()) {

            cell.setCellStyle(getCellStyle(attr, workbook));

            returnValue = valueObject == null || "".equals(valueObject) ? "0" : valueObject.toString();

            BigDecimal num = new BigDecimal(returnValue);

            cell.setCellValue(num.doubleValue());

        } else if (attr.isDateTime()) {

            cell.setCellStyle(getCellStyle(attr, workbook));

            DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

            returnValue = df.format((TemporalAccessor) valueObject);

            cell.setCellValue(returnValue);

        } else {

            returnValue = valueObject == null ? "" : valueObject.toString();

            cell.setCellValue(returnValue);

        }

    }

    /**

    * 根据注解类判断字段类型,返回excel单元格数据格式方法

    *

    * @param attr

    * @param workbook

    * @return CellStyle

    */

    private CellStyle getCellStyle(ExcelVoAttribute attr, SXSSFWorkbook workbook) {

        if (attr.isNumber()) {

            if (decimalCellStyle == null) {

                decimalCellStyle = workbook.createCellStyle();

                //此处设置数字单元格格式

                DataFormat df = workbook.createDataFormat();

                //千分位,保留1位小数

                decimalCellStyle.setDataFormat(df.getFormat("#,##0.0"));

            }

            return decimalCellStyle;

        }

        if (attr.isDateTime()) {

            if (dateTimeCellStyle == null) {

                dateTimeCellStyle = workbook.createCellStyle();

                //此处设置日期时间单元格格式

                DataFormat df = workbook.createDataFormat();

                dateTimeCellStyle.setDataFormat(df.getFormat("yyyy-MM-dd HH:mm:ss"));

            }

            return dateTimeCellStyle;

        }

        return null;

    }

    /**

    * 创建工作页Sheet

    *

    * @param wb

    * @param sheetName

    * @return Sheet

    */

    public Sheet createSheet(SXSSFWorkbook wb, String sheetName) {

        return wb.createSheet(sheetName);

    }

    /**

    * 设置excel列头及格式

    *

    * @param sheet

    */

    public void setColumnTitle(Sheet sheet) {

        if (fields == null) {

            this.fields = this.getSortFields();

        }

        Row row;

        Cell cell;

        ExcelVoAttribute attr;

        Field field;

        int fieldSize = fields.size();

        row = sheet.createRow(0);

        for (int i = 0; i < fieldSize; i++) {

            field = fields.get(i);

            attr = field.getAnnotation(ExcelVoAttribute.class);

            cell = CellUtil.createCell(row, i, attr.name());

            // 设置列宽,根据相应的字段名的长度等比

            sheet.setColumnWidth(i, attr.name().getBytes().length * 400);

        }

    }

    /**

    * 获取输出对象字段列表,并根据注解进行字段排序

    *

    * @return

    */

    private List<Field> getSortFields() {

        List<Field> fields = Arrays.stream(clazz.getDeclaredFields()).filter(x -> x.isAnnotationPresent(ExcelVoAttribute.class)).collect(Collectors.toList());

        List<Field> sortList = new ArrayList<>(fields);

        //排序

        for (Field field : fields) {

            ExcelVoAttribute excelVoAttribute = field.getAnnotation(ExcelVoAttribute.class);

            int sortNo = excelVoAttribute.column();

            sortList.set(sortNo, field);

        }

        return sortList;

    }

}

```

###### ExcelUtils

```

/**

* @author Johnny

* @Date: 2020/4/25 22:56

* @Description:

*/

@Slf4j

public class ExcelUtils {

    /**

    * 导出歌曲

    * @param response

    * @param list

    * @param map

    * @param title

    */

    public static void exportExcel(HttpServletResponse response, List list, Map<String, String> map, String title) {

        //通过工具类创建writer

        ExcelWriter writer = ExcelUtil.getWriter(true);

        //自定义标题别名

        Set<Map.Entry<String, String>> entries = map.entrySet();

        //迭代器遍历数据

        Iterator<Map.Entry<String, String>> iterator = entries.iterator();

        while (iterator.hasNext()) {

            Map.Entry<String, String> next = iterator.next();

            //自定义表头

            writer.addHeaderAlias(next.getKey(), next.getValue());

        }

        //合并单元格后的标题行,使用默认标题样式

        writer.merge(map.size() - 1, title);

        //一次性写出内容,使用默认样式,强制输出标题

        writer.write(list, true);

        //out为outputStream,需要写出到的目标流

        try {

            writer.flush(response.getOutputStream(), true);

        } catch (IOException e) {

            log.info("歌单导出异常");

            e.printStackTrace();

        }

        writer.close();

    }

    /**

    * 导入歌曲

    * @param file

    * @return

    */

    public static List<Song> importExcel(File file) {

        List<Song> songs = new ArrayList<>();

        InputStream inputStream = null;

        try {

            inputStream = new FileInputStream(file);

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        }

        //通过getReader方法确定读取那个sheet中的数据

        ExcelReader reader = ExcelUtil.getReader(inputStream, "sheet1");

        //回去总行数

        List<List<Object>> read = reader.read(1, reader.getRowCount());

        for (List<Object> objects : read) {

            //对每行数据取出构建一个song对象

            Song song = Song.builder()

                    .songName(objects.get(0).toString())

                    .songId(UUID.randomUUID().toString().replace("-", ""))

                    .sortId("0")

                    .singer(objects.get(1).toString())

                    .duration(objects.get(2).toString())

                    .thumbnail(objects.get(3).toString())

                    .url(objects.get(4).toString())

                    .lyric(objects.get(5).toString())

                    .commentCount(0)

                    .playCount(0)

                    .deleteFlag("0")

                    .updateTime(LocalDateTime.now())

                    .createTime(LocalDateTime.now())

                    .build();

            songs.add(song);

        }

        return songs;

    }

}

```

###### ExportDataAdapter

```

/**

* @author Johnny

* @Date: 2020/4/28 21:53

* @Description:

*/

public class ExportDataAdapter<T>{

    /**

    * 默认队列大小

    */

    private static Integer DEFAULT_SIZE = 1000;

    private BlockingQueue<T> resourceQueue = null;

    public ExportDataAdapter() {

        this.resourceQueue = new LinkedBlockingQueue<T>(DEFAULT_SIZE);

    }

    /**

    * 添加数据

    *

    * @param data

    */

    public void addData(T data) {

        try {

            resourceQueue.put(data);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    /**

    * 获取剩余数据数量

    *

    * @return

    */

    public Integer getDataSize() {

        return resourceQueue.size();

    }

    /**

    * 从队列中获取数据

    *

    * @return

    */

    public T getData() {

        try {

            return resourceQueue.take();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        return null;

    }

}

```

###### FileUtil

```

/**

* @author Johnny

* @Date: 2020/4/26 10:41

* @Description:

*/

public class FileUtil {

    public static File fileConversion(MultipartFile file) {

        int n;

        File file1 = new File(file.getOriginalFilename());

        try {

            InputStream in = file.getInputStream();

            OutputStream os = new FileOutputStream(file1);

            byte[] bytes = new byte[4096];

            while ((n=in.read(bytes,0,4096))!=-1){

                os.write(bytes,0,n);

                File file2 = new File(file1.toURI());

                file2.delete();

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

        return file1;

    }

}

```

###### JwtTokenUtil

```

/**

* @Author Johnny

* @Date 2020/4/15

* @Version 1.0

*/

@Slf4j

public class JwtTokenUtil {

    /**

    * 加密

    *

    * @param userId

    * @param expiresAt

    * @return String

    */

    public static String getToken(final String userId, String userRole,final String secrect,Date expiresAt) {

        String token = null;

        try {

            token = JWT.create()

                    .withIssuer("auth0")

                    .withClaim("userId", userId)

                    .withClaim("userRole", userRole)

                    .withExpiresAt(expiresAt)

                    // 使用了HMAC256加密算法, mySecret是用来加密数字签名的密钥

                    .sign(Algorithm.HMAC256(secrect));

        } catch (UnsupportedEncodingException e) {

            log.error("不支持的编码格式");

        }

        return token;

    }

    /**

    * 解密

    * @param token

    * @return DecodedJWT

    */

    public static DecodedJWT deToken(final String token,final String secrect) {

        DecodedJWT jwt;

        JWTVerifier verifier = null;

        try {

            verifier = JWT.require(Algorithm.HMAC256(secrect))

                    .withIssuer("auth0")

                    .build();

        } catch (UnsupportedEncodingException e) {

            log.error("不支持的编码格式");

        }

        assert verifier != null;

        jwt = verifier.verify(token);

        return jwt;

    }

    /**

    * 获取userId

    *

    * @param token

    * @return String

    */

    public static String getUserId(String token,String secrect) {

        return deToken(token,secrect).getClaim("userId").asString();

    }

    /**

    * 获取role

    *

    * @param token

    * @return String

    */

    public static String getRoles(String token,String secrect) {

        return deToken(token,secrect).getClaim("userRole").asString();

    }

    /**

    * 验证是否过期

    *

    * @param token

    * @return boolean

    */

    public static boolean isExpiration(String token,String secrect) {

        return deToken(token,secrect).getExpiresAt().before(new Date());

    }

    public static void main(String[] args) {

//        String token = getToken("2000100193", new Date(System.currentTimeMillis() + 10L * 1000L));

//        System.out.println(token);

//        while (true) {

//            boolean flag = isExpiration(token);

//            System.out.println(flag);

//            if (flag) {

//                System.out.println("token已失效");

//                break;

//            }

//            try {

//                Thread.sleep(1000);

//            } catch (InterruptedException e) {

//                e.printStackTrace();

//            }

//        }

//        String token = "eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsImV4cCI6MTU4NzY3MTQ3OCwidXNlcklkIjoiREUzNUQ3Q0MwNUFGOTZBMjFEN0FERkM4NjUxRTY2MTQifQ.uT2W3QcE3744WNN3inEKT8lUVJs6xAC7TodDCaWkcyM";

//        System.out.println(deToken(token).getClaim("userId").asString());

    }

}

```

###### Md5Util

```

/**

* @author Johnny

*/

public class Md5Util {

    /**

    * @param pwd    需要加密的字符串

    * @param isUpper 字母大小写(false为默认小写,true为大写)

    * @param bit    加密的位数(16,32,64)

    * @return String

    */

    public static String getMd5(String pwd, boolean isUpper, Integer bit) {

        String md5 = "";

        try {

            // 创建加密对象

            MessageDigest md = MessageDigest.getInstance("md5");

            if (bit == 64) {

                Base64.Encoder encoder = Base64.getEncoder();

                md5 = encoder.encodeToString(md.digest(pwd.getBytes(StandardCharsets.UTF_8)));

            } else {

                // 计算MD5函数

                md.update(pwd.getBytes());

                byte b[] = md.digest();

                int i;

                StringBuilder sb = new StringBuilder();

                for (byte value : b) {

                    i = value;

                    if (i < 0) {

                        i += 256;

                    }

                    if (i < 16) {

                        sb.append("0");

                    }

                    sb.append(Integer.toHexString(i));

                }

                md5 = sb.toString();

                if (bit == 16) {

                    //截取32位md5为16位

                    md5 = md5.substring(8, 24).toString();

                    if (isUpper) {

                        md5 = md5.toUpperCase();

                    }

                    return md5;

                }

            }

            //转换成大写

            if (isUpper) {

                md5 = md5.toUpperCase();

            }

        } catch (Exception e) {

            e.printStackTrace();

            System.out.println("md5加密抛出异常!");

        }

        return md5;

    }

    public static void main(String[] args) {

        String a = "123456";

        String md5a = getMd5(a, true, 32);

        System.out.println(md5a);

        System.out.println(md5a.length());

    }

```

###### ThreadPool

```

/**

* @author Johnny

* @Date: 2020/4/28 21:55

* @Description:

*/

public class ThreadPool {

    /**

    * 异步线程

    */

    private final static ThreadPoolExecutor executor =

            new ThreadPoolExecutor(20, 100, 10, TimeUnit.MINUTES,

                    new ArrayBlockingQueue<>(2000),

                    r -> new Thread(r, "excelExportThread"),

                    new ThreadPoolExecutor.AbortPolicy());

    public static ThreadPoolExecutor getExecutor() {

        return executor;

    }

}

```

###### TreeBuilder

```

/**

* @author Johnny

* @Date: 2020/4/23 18:46

* @Description:

*/

public class TreeBuilder {

    /**

    * 两层循环实现建树

    *

    * @param treeNodes 传入的树节点列表

    * @return

    */

    public static List<TreeNode> buildTreeByLoop(List<TreeNode> treeNodes) {

        List<TreeNode> trees = new ArrayList<>();

        for (TreeNode treeNode : treeNodes) {

            if (treeNode.getParentId() == 0) {

                trees.add(treeNode);

            }

            for (TreeNode it : treeNodes) {

                if (it.getParentId().equals(treeNode.getId())) {

                    if (treeNode.getSubMenus() == null) {

                        treeNode.setSubMenus(new ArrayList<>());

                    }

                    treeNode.getSubMenus().add(it);

                }

            }

        }

        return trees;

    }

    /**

    * 使用递归方法建树

    *

    * @param treeNodes

    * @return

    */

    public static List<TreeNode> buildTreeByRecursive(List<TreeNode> treeNodes) {

        List<TreeNode> trees = new ArrayList<>();

        for (TreeNode treeNode : treeNodes) {

            if (treeNode.getParentId() == 0) {

                trees.add(findChildren(treeNode, treeNodes));

            }

        }

        return trees;

    }

    /**

    * 递归查找子节点

    *

    * @param treeNodes

    * @return

    */

    public static TreeNode findChildren(TreeNode treeNode, List<TreeNode> treeNodes) {

        for (TreeNode it : treeNodes) {

            if (treeNode.getId().equals(it.getParentId())) {

                if (treeNode.getSubMenus() == null) {

                    treeNode.setSubMenus(new ArrayList<>());

                }

                treeNode.getSubMenus().add(findChildren(it, treeNodes));

            }

        }

        return treeNode;

    }

}

```

###### TreeNode

```

/**

* @author Johnny

* @Date: 2020/4/23 18:46

* @Description:

*/

@Data

@NoArgsConstructor

@AllArgsConstructor

@Builder

public class TreeNode {

    private Integer id;

    private Integer parentId;

    private Integer type;

    private String title;

    private String icon;

    private String path;

    private Integer sort;

    private List<TreeNode> subMenus;

    public TreeNode(Integer id, Integer parentId, Integer type,String title, String icon, String path, Integer sort) {

        this.id = id;

        this.parentId = parentId;

        this.type = type;

        this.title = title;

        this.icon = icon;

        this.path = path;

        this.sort = sort;

    }

}

```、

更多内容请关注Johnny博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345