feat: 全量提交

This commit is contained in:
horizons
2022-10-24 07:50:54 +08:00
commit de9157143a
128 changed files with 7493 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
package com.youlai.system;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SystemApplication {
public static void main(String[] args) {
SpringApplication.run(SystemApplication.class, args);
}
}

View File

@@ -0,0 +1,25 @@
package com.youlai.system.common.base;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableField(fill = FieldFill.INSERT)
@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonInclude(value = JsonInclude.Include.NON_NULL)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,22 @@
package com.youlai.system.common.base;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 基础分页请求对象
*
* @author haoxr
* @date 2021/2/28
*/
@Data
@ApiModel
public class BasePageQuery {
@ApiModelProperty(value = "页码", example = "1")
private int pageNum = 1;
@ApiModelProperty(value = "每页记录数", example = "10")
private int pageSize = 10;
}

View File

@@ -0,0 +1,19 @@
package com.youlai.system.common.base;
import lombok.Data;
import lombok.ToString;
import java.io.Serializable;
/**
* 视图对象基类
*
* @author haoxr
* @date 2022/10/22
*/
@Data
@ToString
public class BaseVO implements Serializable {
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,88 @@
package com.youlai.system.common.base;
import cn.hutool.core.util.ObjectUtil;
import java.util.EnumSet;
import java.util.Objects;
/**
* 枚举通用接口
*
* @author haoxr
* @date 2022/3/27 12:06
*/
public interface IBaseEnum<T> {
T getValue();
String getLabel();
/**
* 根据值获取枚举
*
* @param value
* @param clazz
* @param <E> 枚举
* @return
*/
static <E extends Enum<E> & IBaseEnum> E getEnumByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
return matchEnum;
}
/**
* 根据文本标签获取值
*
* @param value
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> String getLabelByValue(Object value, Class<E> clazz) {
Objects.requireNonNull(value);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getValue(), value))
.findFirst()
.orElse(null);
String label = null;
if (matchEnum != null) {
label = matchEnum.getLabel();
}
return label;
}
/**
* 根据文本标签获取值
*
* @param label
* @param clazz
* @param <E>
* @return
*/
static <E extends Enum<E> & IBaseEnum> Object getValueByLabel(String label, Class<E> clazz) {
Objects.requireNonNull(label);
EnumSet<E> allEnums = EnumSet.allOf(clazz); // 获取类型下的所有枚举
String finalLabel = label;
E matchEnum = allEnums.stream()
.filter(e -> ObjectUtil.equal(e.getLabel(), finalLabel))
.findFirst()
.orElse(null);
Object value = null;
if (matchEnum != null) {
value = matchEnum.getValue();
}
return value;
}
}

View File

@@ -0,0 +1,18 @@
package com.youlai.system.common.constant;
/**
* Security常量
*
* @author haoxr
* @date 2022/10/22
*/
public interface SecurityConstants {
/**
* 授权角色的前缀
* <p>
* 区分角色与权限标识
*/
String ROLE_PREFIX = "ROLE_";
}

View File

@@ -0,0 +1,26 @@
package com.youlai.system.common.constant;
/**
* 系统常量
*
* @author haoxr
* @date 2022/10/22
*/
public interface SystemConstants {
/**
* 根节点ID
*/
Long ROOT_NODE_ID = 0l;
/**
* 系统默认密码
*/
String DEFAULT_USER_PASSWORD = "123456";
/**
* 超级管理员角色编码
*/
String ROOT_ROLE_CODE = "ROOT";
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.common.enums;
import com.youlai.system.common.base.IBaseEnum;
import lombok.Getter;
/**
* 性别枚举
*
* @author haoxr
* @date 2022/10/14
*/
public enum GenderEnum implements IBaseEnum<Integer> {
MALE(1, ""),
FEMALE (2, "");
@Getter
private Integer value;
@Getter
private String label;
GenderEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,35 @@
package com.youlai.system.common.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.youlai.system.common.base.IBaseEnum;
import lombok.Getter;
/**
* 菜单类型枚举
*
* @author haoxr
* @date 2022/4/23 9:36
*/
public enum MenuTypeEnum implements IBaseEnum<Integer> {
NULL(0, null),
MENU(1, "菜单"),
CATALOG(2, "目录"),
EXTLINK(3, "外链"),
BUTTON(4, "按钮");
@Getter
@EnumValue // Mybatis-Plus 提供注解表示插入数据库时插入该值
private Integer value;
@Getter
// @JsonValue // 表示对枚举序列化时返回此字段
private String label;
MenuTypeEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.common.enums;
import com.youlai.system.common.base.IBaseEnum;
import lombok.Getter;
/**
* 状态枚举
*
* @author haoxr
* @date 2022/10/14
*/
public enum StatusEnum implements IBaseEnum<Integer> {
ENABLE(1, "启用"),
DISABLE (0, "禁用");
@Getter
private Integer value;
@Getter
private String label;
StatusEnum(Integer value, String label) {
this.value = value;
this.label = label;
}
}

View File

@@ -0,0 +1,35 @@
package com.youlai.system.common.exception;
import com.youlai.system.common.result.IResultCode;
import lombok.Getter;
/**
* 自定义业务异常
*
* @author haoxr
* @date 2022/7/31
*/
@Getter
public class BusinessException extends RuntimeException {
public IResultCode resultCode;
public BusinessException(IResultCode errorCode) {
super(errorCode.getMsg());
this.resultCode = errorCode;
}
public BusinessException(String message){
super(message);
}
public BusinessException(String message, Throwable cause){
super(message, cause);
}
public BusinessException(Throwable cause){
super(cause);
}
}

View File

@@ -0,0 +1,208 @@
package com.youlai.system.common.exception;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.youlai.system.common.result.Result;
import com.youlai.system.common.result.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.ServletException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLSyntaxErrorException;
import java.util.concurrent.CompletionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 全局系统异常处理
* 调整异常处理的HTTP状态码丰富异常处理类型
*
* @author hxrui
* @author Gadfly
* @date 2020-02-25 13:54
* <p>
**/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public <T> Result<T> processException(BindException e) {
log.error("BindException:{}", e.getMessage());
String msg = e.getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(""));
return Result.failed(ResultCode.PARAM_ERROR, msg);
}
/**
* RequestParam参数的校验
*
* @param e
* @param <T>
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public <T> Result<T> processException(ConstraintViolationException e) {
log.error("ConstraintViolationException:{}", e.getMessage());
String msg = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(""));
return Result.failed(ResultCode.PARAM_ERROR, msg);
}
/**
* RequestBody参数的校验
*
* @param e
* @param <T>
* @return
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public <T> Result<T> processException(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException:{}", e.getMessage());
String msg = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining(""));
return Result.failed(ResultCode.PARAM_ERROR, msg);
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(NoHandlerFoundException.class)
public <T> Result<T> processException(NoHandlerFoundException e) {
log.error(e.getMessage(), e);
return Result.failed(ResultCode.RESOURCE_NOT_FOUND);
}
/**
* MissingServletRequestParameterException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public <T> Result<T> processException(MissingServletRequestParameterException e) {
log.error(e.getMessage(), e);
return Result.failed(ResultCode.PARAM_IS_NULL);
}
/**
* MethodArgumentTypeMismatchException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public <T> Result<T> processException(MethodArgumentTypeMismatchException e) {
log.error(e.getMessage(), e);
return Result.failed(ResultCode.PARAM_ERROR, "类型错误");
}
/**
* ServletException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ServletException.class)
public <T> Result<T> processException(ServletException e) {
log.error(e.getMessage(), e);
return Result.failed(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public <T> Result<T> handleIllegalArgumentException(IllegalArgumentException e) {
log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
return Result.failed(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(JsonProcessingException.class)
public <T> Result<T> handleJsonProcessingException(JsonProcessingException e) {
log.error("Json转换异常异常原因{}", e.getMessage(), e);
return Result.failed(e.getMessage());
}
/**
* HttpMessageNotReadableException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public <T> Result<T> processException(HttpMessageNotReadableException e) {
log.error(e.getMessage(), e);
String errorMessage = "请求体不可为空";
Throwable cause = e.getCause();
if (cause != null) {
errorMessage = convertMessage(cause);
}
return Result.failed(errorMessage);
}
/**
* TypeMismatchException
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(TypeMismatchException.class)
public <T> Result<T> processException(TypeMismatchException e) {
log.error(e.getMessage(), e);
return Result.failed(e.getMessage());
}
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(SQLSyntaxErrorException.class)
public <T> Result<T> processSQLSyntaxErrorException(SQLSyntaxErrorException e) {
log.error(e.getMessage(), e);
String errorMsg = e.getMessage();
if (StrUtil.isNotBlank(errorMsg) && errorMsg.contains("denied to user")) {
return Result.failed("数据库用户无操作权限,建议本地搭建数据库环境");
} else {
return Result.failed(e.getMessage());
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BusinessException.class)
public <T> Result<T> handleBizException(BusinessException e) {
log.error("业务异常,异常原因:{}", e.getMessage(), e);
if (e.getResultCode() != null) {
return Result.failed(e.getResultCode());
}
return Result.failed(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(Exception.class)
public <T> Result<T> handleException(Exception e) {
return Result.failed(e.getLocalizedMessage());
}
/**
* 传参类型错误时,用于消息转换
*
* @param throwable 异常
* @return 错误信息
*/
private String convertMessage(Throwable throwable) {
String error = throwable.toString();
String regulation = "\\[\"(.*?)\"]+";
Pattern pattern = Pattern.compile(regulation);
Matcher matcher = pattern.matcher(error);
String group = "";
if (matcher.find()) {
String matchString = matcher.group();
matchString = matchString.replace("[", "").replace("]", "");
matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
group += matchString;
}
return group;
}
}

View File

@@ -0,0 +1,42 @@
package com.youlai.system.common.model;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 下拉选项对象
*
* @author haoxr
* @date 2022/1/22
*/
@ApiModel("下拉选项对象")
@Data
@NoArgsConstructor
public class Option<T> {
public Option(T value, String label) {
this.value = value;
this.label = label;
}
public Option(T value, String label, List<Option> children) {
this.value = value;
this.label = label;
this.children= children;
}
@ApiModelProperty("选项的值")
private T value;
@ApiModelProperty("选项的标签")
private String label;
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private List<Option> children;
}

View File

@@ -0,0 +1,12 @@
package com.youlai.system.common.result;
/**
* @author haoxr
**/
public interface IResultCode {
String getCode();
String getMsg();
}

View File

@@ -0,0 +1,46 @@
package com.youlai.system.common.result;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 分页响应结构体
*
* @author haoxr
* @date 2022/2/18 23:29
*/
@Data
public class PageResult<T> implements Serializable {
private String code;
private Data data;
private String msg;
public static <T> PageResult<T> success(IPage<T> page) {
PageResult<T> result = new PageResult<>();
result.setCode(ResultCode.SUCCESS.getCode());
Data data = new Data<T>();
data.setList(page.getRecords());
data.setTotal(page.getTotal());
result.setData(data);
result.setMsg(ResultCode.SUCCESS.getMsg());
return result;
}
@lombok.Data
public static class Data<T> {
private List<T> list;
private long total;
}
}

View File

@@ -0,0 +1,73 @@
package com.youlai.system.common.result;
import lombok.Data;
import java.io.Serializable;
/**
* 统一响应结构体
*
* @author haoxr
* @date 2022/1/30
**/
@Data
public class Result<T> implements Serializable {
private String code;
private T data;
private String msg;
public static <T> Result<T> success() {
return success(null);
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMsg(ResultCode.SUCCESS.getMsg());
result.setData(data);
return result;
}
public static <T> Result<T> failed() {
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), ResultCode.SYSTEM_EXECUTION_ERROR.getMsg(), null);
}
public static <T> Result<T> failed(String msg) {
return result(ResultCode.SYSTEM_EXECUTION_ERROR.getCode(), msg, null);
}
public static <T> Result<T> judge(boolean status) {
if (status) {
return success();
} else {
return failed();
}
}
public static <T> Result<T> failed(IResultCode resultCode) {
return result(resultCode.getCode(), resultCode.getMsg(), null);
}
public static <T> Result<T> failed(IResultCode resultCode, String msg) {
return result(resultCode.getCode(), msg, null);
}
private static <T> Result<T> result(IResultCode resultCode, T data) {
return result(resultCode.getCode(), resultCode.getMsg(), data);
}
private static <T> Result<T> result(String code, String msg, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setData(data);
result.setMsg(msg);
return result;
}
public static boolean isSuccess(Result<?> result) {
return result != null && ResultCode.SUCCESS.getCode().equals(result.getCode());
}
}

View File

@@ -0,0 +1,106 @@
package com.youlai.system.common.result;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author haoxr
* @date 2020-06-23
**/
@AllArgsConstructor
@NoArgsConstructor
public enum ResultCode implements IResultCode, Serializable {
SUCCESS("00000", "一切ok"),
USER_ERROR("A0001", "用户端错误"),
USER_LOGIN_ERROR("A0200", "用户登录异常"),
USER_NOT_EXIST("A0201", "用户不存在"),
USER_ACCOUNT_LOCKED("A0202", "用户账户被冻结"),
USER_ACCOUNT_INVALID("A0203", "用户账户已作废"),
USERNAME_OR_PASSWORD_ERROR("A0210", "用户名或密码错误"),
PASSWORD_ENTER_EXCEED_LIMIT("A0211", "用户输入密码次数超限"),
CLIENT_AUTHENTICATION_FAILED("A0212", "客户端认证失败"),
TOKEN_INVALID_OR_EXPIRED("A0230", "token无效或已过期"),
TOKEN_ACCESS_FORBIDDEN("A0231", "token已被禁止访问"),
AUTHORIZED_ERROR("A0300", "访问权限异常"),
ACCESS_UNAUTHORIZED("A0301", "访问未授权"),
FORBIDDEN_OPERATION("A0302", "演示环境禁止修改、删除重要数据,请本地部署后测试"),
PARAM_ERROR("A0400", "用户请求参数错误"),
RESOURCE_NOT_FOUND("A0401", "请求资源不存在"),
PARAM_IS_NULL("A0410", "请求必填参数为空"),
USER_UPLOAD_FILE_ERROR("A0700", "用户上传文件异常"),
USER_UPLOAD_FILE_TYPE_NOT_MATCH("A0701", "用户上传文件类型不匹配"),
USER_UPLOAD_FILE_SIZE_EXCEEDS("A0702", "用户上传文件太大"),
USER_UPLOAD_IMAGE_SIZE_EXCEEDS("A0703", "用户上传图片太大"),
SYSTEM_EXECUTION_ERROR("B0001", "系统执行出错"),
SYSTEM_EXECUTION_TIMEOUT("B0100", "系统执行超时"),
SYSTEM_ORDER_PROCESSING_TIMEOUT("B0100", "系统订单处理超时"),
SYSTEM_DISASTER_RECOVERY_TRIGGER("B0200", "系统容灾功能被出发"),
FLOW_LIMITING("B0210", "系统限流"),
DEGRADATION("B0220", "系统功能降级"),
SYSTEM_RESOURCE_ERROR("B0300", "系统资源异常"),
SYSTEM_RESOURCE_EXHAUSTION("B0310", "系统资源耗尽"),
SYSTEM_RESOURCE_ACCESS_ERROR("B0320", "系统资源访问异常"),
SYSTEM_READ_DISK_FILE_ERROR("B0321", "系统读取磁盘文件失败"),
CALL_THIRD_PARTY_SERVICE_ERROR("C0001", "调用第三方服务出错"),
MIDDLEWARE_SERVICE_ERROR("C0100", "中间件服务出错"),
INTERFACE_NOT_EXIST("C0113", "接口不存在"),
MESSAGE_SERVICE_ERROR("C0120", "消息服务出错"),
MESSAGE_DELIVERY_ERROR("C0121", "消息投递出错"),
MESSAGE_CONSUMPTION_ERROR("C0122", "消息消费出错"),
MESSAGE_SUBSCRIPTION_ERROR("C0123", "消息订阅出错"),
MESSAGE_GROUP_NOT_FOUND("C0124", "消息分组未查到"),
DATABASE_ERROR("C0300", "数据库服务出错"),
DATABASE_TABLE_NOT_EXIST("C0311", "表不存在"),
DATABASE_COLUMN_NOT_EXIST("C0312", "列不存在"),
DATABASE_DUPLICATE_COLUMN_NAME("C0321", "多表关联中存在多个相同名称的列"),
DATABASE_DEADLOCK("C0331", "数据库死锁"),
DATABASE_PRIMARY_KEY_CONFLICT("C0341", "主键冲突");
@Override
public String getCode() {
return code;
}
@Override
public String getMsg() {
return msg;
}
private String code;
private String msg;
@Override
public String toString() {
return "{" +
"\"code\":\"" + code + '\"' +
", \"msg\":\"" + msg + '\"' +
'}';
}
public static ResultCode getValue(String code){
for (ResultCode value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return SYSTEM_EXECUTION_ERROR; // 默认系统执行错误
}
}

View File

@@ -0,0 +1,61 @@
package com.youlai.system.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.youlai.system.handler.IntegerArrayJsonTypeHandler;
import com.youlai.system.handler.LongArrayJsonTypeHandler;
import com.youlai.system.handler.MyMetaObjectHandler;
import com.youlai.system.handler.StringArrayJsonTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* MP配置类
*
* @author haoxr
* @date 2022/7/2
*/
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig {
/**
* 分页插件和数据权限插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> {
// 全局注册自定义TypeHandler
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
typeHandlerRegistry.register(String[].class, JdbcType.OTHER, StringArrayJsonTypeHandler.class);
typeHandlerRegistry.register(Long[].class, JdbcType.OTHER, LongArrayJsonTypeHandler.class);
typeHandlerRegistry.register(Integer[].class, JdbcType.OTHER, IntegerArrayJsonTypeHandler.class);
};
}
/**
* 自动填充数据库创建人、创建时间、更新人、更新时间
*/
@Bean
public GlobalConfig globalConfig() {
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
return globalConfig;
}
}

View File

@@ -0,0 +1,82 @@
package com.youlai.system.config;
import cn.hutool.core.collection.CollectionUtil;
import io.swagger.annotations.Api;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.groupName("权限服务")
//是否开启 (true 开启 false隐藏。生产环境建议隐藏)
//.enable(false)
.select()
//扫描的路径包,设置basePackage会将包下的所有被@Api标记类的所有方法作为api
.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
//指定路径处理PathSelectors.any()代表所有的路径
.paths(PathSelectors.any())
.build().securityContexts(CollectionUtil.newArrayList(securityContext()))
.securitySchemes(CollectionUtil.newArrayList(apiKey()));
}
/**
* 配置基本信息
*
* @return
*/
@Bean
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
//设置文档标题(API名称)
.title("SpringBoot单体应用开发文档")
//文档描述
.description("快速开发文档-接口说明")
//版本号
.version("1.0.0")
//联系人
.contact(new Contact("", "http://localhost", ""))
.build();
}
private List<SecurityScheme> securitySchemes() {
List<SecurityScheme> apiKeyList= new ArrayList<>();
apiKeyList.add(HttpAuthenticationScheme.JWT_BEARER_BUILDER.name("Authorization").build());
return apiKeyList;
}
private ApiKey apiKey() {
return new ApiKey("Authorization", "Authorization", "header");
}
List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return CollectionUtil.newArrayList(new SecurityReference("Authorization", authorizationScopes));
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
//.forPaths(PathSelectors.regex(".*?208.*$"))
.build();
}
}

View File

@@ -0,0 +1,58 @@
package com.youlai.system.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.TimeZone;
@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper();
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
// 后台Long值传递给前端精度丢失问题JS最大精度整数是Math.pow(2,53)
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(0, jackson2HttpMessageConverter);
}
@Bean
public Validator validator(final AutowireCapableBeanFactory autowireCapableBeanFactory) {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true) // failFast=true 不校验所有参数,只要出现校验失败情况直接返回,不再进行后续参数校验
.constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}

View File

@@ -0,0 +1,47 @@
package com.youlai.system.controller;
import com.youlai.system.common.result.Result;
import com.youlai.system.security.jwt.JwtTokenManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@Api(tags = "认证管理")
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenManager jwtTokenManager;
@ApiOperation(value = "登录",notes = "生成token")
@PostMapping("/login")
public Result login(
@RequestParam String username,
@RequestParam String password
) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username,
password);
Authentication authentication = authenticationManager.authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成token
String token = jwtTokenManager.createToken(authentication);
return Result.success("Bearer " + token);
}
@ApiOperation(value = "注销")
@DeleteMapping("/logout")
public Result login() {
SecurityContextHolder.clearContext();
return Result.success("注销成功");
}
}

View File

@@ -0,0 +1,83 @@
package com.youlai.system.controller;
import com.youlai.system.common.model.Option;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.form.DeptForm;
import com.youlai.system.pojo.query.DeptQuery;
import com.youlai.system.pojo.vo.dept.DeptVO;
import com.youlai.system.service.SysDeptService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
/**
* 部门控制器
*
* @author haoxr
* @date 2020/11/6
*/
@Api(tags = "部门接口")
@RestController
@RequestMapping("/api/v1/dept")
@RequiredArgsConstructor
public class SysDeptController {
private final SysDeptService deptService;
@ApiOperation(value = "获取部门列表")
@GetMapping
public Result<List<DeptVO>> listDepartments(DeptQuery queryParams) {
List<DeptVO> list = deptService.listDepartments(queryParams);
return Result.success(list);
}
@ApiOperation(value = "获取部门下拉选项")
@GetMapping("/options")
public Result<List<Option>> listDeptOptions() {
List<Option> list = deptService.listDeptOptions();
return Result.success(list);
}
@ApiOperation(value = "获取部门详情")
@GetMapping("/{deptId}/form")
public Result<DeptForm> getDeptForm(
@ApiParam("部门ID") @PathVariable Long deptId
) {
DeptForm deptForm = deptService.getDeptForm(deptId);
return Result.success(deptForm);
}
@ApiOperation(value = "新增部门")
@PostMapping
public Result saveDept(
@Valid @RequestBody DeptForm formData
) {
Long id = deptService.saveDept(formData);
return Result.success(id);
}
@ApiOperation(value = "修改部门")
@PutMapping(value = "/{deptId}")
public Result updateDept(
@PathVariable Long deptId,
@Valid @RequestBody DeptForm formData
) {
deptId = deptService.updateDept(deptId, formData);
return Result.success(deptId);
}
@ApiOperation(value = "删除部门")
@DeleteMapping("/{ids}")
public Result deleteDepartments(
@ApiParam("部门ID多个以英文逗号(,)分割") @PathVariable("ids") String ids
) {
boolean result = deptService.deleteByIds(ids);
return Result.judge(result);
}
}

View File

@@ -0,0 +1,70 @@
package com.youlai.system.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.common.result.PageResult;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.form.DictItemForm;
import com.youlai.system.pojo.query.DictItemPageQuery;
import com.youlai.system.pojo.vo.dict.DictItemPageVO;
import com.youlai.system.service.SysDictItemService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Api(tags = "字典数据接口")
@RestController
@RequestMapping("/api/v1/dict/items")
@RequiredArgsConstructor
public class SysDictItemController {
private final SysDictItemService dictItemService;
@ApiOperation(value = "字典数据分页列表")
@GetMapping("/pages")
public PageResult<DictItemPageVO> listDictItemPages(
DictItemPageQuery queryParams
) {
Page<DictItemPageVO> result = dictItemService.listDictItemPages(queryParams);
return PageResult.success(result);
}
@ApiOperation(value = "字典数据表单数据")
@GetMapping("/{id}/form")
public Result<DictItemForm> getDictItemForm(
@ApiParam("字典ID") @PathVariable Long id
) {
DictItemForm formData = dictItemService.getDictItemForm(id);
return Result.success(formData);
}
@ApiOperation(value = "新增字典数据")
@PostMapping
public Result saveDictItem(
@RequestBody DictItemForm DictItemForm
) {
boolean result = dictItemService.saveDictItem(DictItemForm);
return Result.judge(result);
}
@ApiOperation(value = "修改字典数据")
@PutMapping("/{id}")
public Result updateDictItem(
@PathVariable Long id,
@RequestBody DictItemForm DictItemForm
) {
boolean status = dictItemService.updateDictItem(id, DictItemForm);
return Result.judge(status);
}
@ApiOperation(value = "删除字典")
@DeleteMapping("/{ids}")
public Result deleteDictItems(
@ApiParam("字典ID多个以英文逗号(,)拼接") @PathVariable String ids
) {
boolean result = dictItemService.deleteDictItems(ids);
return Result.judge(result);
}
}

View File

@@ -0,0 +1,74 @@
package com.youlai.system.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.common.model.Option;
import com.youlai.system.common.result.PageResult;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.form.DictTypeForm;
import com.youlai.system.pojo.query.DictTypePageQuery;
import com.youlai.system.pojo.vo.dict.DictTypePageVO;
import com.youlai.system.service.SysDictTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "字典类型接口")
@RestController
@RequestMapping("/api/v1/dict/types")
@RequiredArgsConstructor
public class SysDictTypeController {
private final SysDictTypeService dictTypeService;
@ApiOperation(value = "字典类型分页列表")
@GetMapping("/pages")
public PageResult<DictTypePageVO> listDictTypePages(DictTypePageQuery queryParams) {
Page<DictTypePageVO> result = dictTypeService.listDictTypePages(queryParams);
return PageResult.success(result);
}
@ApiOperation(value = "字典类型表单详情")
@GetMapping("/{id}/form")
public Result<DictTypeForm> getDictTypeFormData(
@ApiParam("字典ID") @PathVariable Long id
) {
DictTypeForm dictTypeForm = dictTypeService.getDictTypeFormData(id);
return Result.success(dictTypeForm);
}
@ApiOperation(value = "新增字典类型")
@PostMapping
public Result saveDictType(@RequestBody DictTypeForm dictTypeForm) {
boolean result = dictTypeService.saveDictType(dictTypeForm);
return Result.judge(result);
}
@ApiOperation(value = "修改字典类型")
@PutMapping("/{id}")
public Result updateDict(@PathVariable Long id, @RequestBody DictTypeForm dictTypeForm) {
boolean status = dictTypeService.updateDictType(id, dictTypeForm);
return Result.judge(status);
}
@ApiOperation(value = "删除字典类型")
@DeleteMapping("/{ids}")
public Result deleteDictTypes(
@ApiParam("字典类型ID多个以英文逗号(,)分割") @PathVariable String ids
) {
boolean result = dictTypeService.deleteDictTypes(ids);
return Result.judge(result);
}
@ApiOperation(value = "获取字典类型的数据项")
@GetMapping("/{typeCode}/items")
public Result<List<Option>> listDictItemsByTypeCode(
@ApiParam("字典类型编码") @PathVariable String typeCode
) {
List<Option> list = dictTypeService.listDictItemsByTypeCode(typeCode);
return Result.success(list);
}
}

View File

@@ -0,0 +1,116 @@
package com.youlai.system.controller;
import com.youlai.system.common.model.Option;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.entity.SysMenu;
import com.youlai.system.pojo.vo.menu.MenuVO;
import com.youlai.system.pojo.vo.menu.ResourceVO;
import com.youlai.system.pojo.vo.menu.RouteVO;
import com.youlai.system.service.SysMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;
/**
* 菜单控制器
*
* @author haoxr
* @date 2020/11/06
*/
@Api(tags = "菜单接口")
@RestController
@RequestMapping("/api/v1/menus")
@RequiredArgsConstructor
@Slf4j
public class SysMenuController {
private final SysMenuService menuService;
@ApiOperation(value = "资源(菜单+权限)列表")
@GetMapping("/resources")
public Result<List<ResourceVO>> listResources() {
List<ResourceVO> resources = menuService.listResources();
return Result.success(resources);
}
@ApiOperation(value = "菜单列表")
@GetMapping
public Result listMenus(
@ApiParam("菜单名称") String name
) {
List<MenuVO> menuList = menuService.listMenus(name);
return Result.success(menuList);
}
@ApiOperation(value = "菜单下拉列表")
@GetMapping("/options")
public Result listMenuOptions() {
List<Option> menus = menuService.listMenuOptions();
return Result.success(menus);
}
@ApiOperation(value = "路由列表")
@GetMapping("/routes")
public Result listRoutes() {
List<RouteVO> routeList = menuService.listRoutes();
return Result.success(routeList);
}
@ApiOperation(value = "菜单详情")
@GetMapping("/{id}")
public Result detail(
@ApiParam(value = "菜单ID") @PathVariable Long id
) {
SysMenu menu = menuService.getById(id);
return Result.success(menu);
}
@ApiOperation(value = "新增菜单")
@PostMapping
@CacheEvict(cacheNames = "system", key = "'routes'")
public Result addMenu(@RequestBody SysMenu menu) {
boolean result = menuService.saveMenu(menu);
return Result.judge(result);
}
@ApiOperation(value = "修改菜单")
@PutMapping(value = "/{id}")
@CacheEvict(cacheNames = "system", key = "'routes'")
public Result updateMenu(
@RequestBody SysMenu menu
) {
boolean result = menuService.saveMenu(menu);
return Result.judge(result);
}
@ApiOperation(value = "删除菜单")
@DeleteMapping("/{ids}")
@CacheEvict(cacheNames = "system", key = "'routes'")
public Result deleteMenus(
@ApiParam("菜单ID多个以英文(,)分割") @PathVariable("ids") String ids
) {
boolean result = menuService.removeByIds(Arrays.asList(ids.split(",")));
return Result.judge(result);
}
@ApiOperation(value = "修改菜单显示状态")
@PatchMapping("/{menuId}")
public Result updateMenuVisible(
@ApiParam(value = "菜单ID") @PathVariable Long menuId,
@ApiParam(value = "显示状态(1:显示;0:隐藏)") Integer visible
) {
boolean result =menuService.updateMenuVisible(menuId, visible);
return Result.judge(result);
}
}

View File

@@ -0,0 +1,104 @@
package com.youlai.system.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.common.model.Option;
import com.youlai.system.common.result.PageResult;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.entity.SysRole;
import com.youlai.system.pojo.form.RoleForm;
import com.youlai.system.pojo.form.RoleResourceForm;
import com.youlai.system.pojo.query.RolePageQuery;
import com.youlai.system.pojo.vo.role.RolePageVO;
import com.youlai.system.service.SysRoleService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@Api(tags = "角色接口")
@RestController
@RequestMapping("/api/v1/roles")
@RequiredArgsConstructor
public class SysRoleController {
private final SysRoleService sysRoleService;
@ApiOperation(value = "角色分页列表")
@GetMapping("/pages")
public PageResult<RolePageVO> listRolePages(RolePageQuery queryParams) {
Page<RolePageVO> result = sysRoleService.listRolePages(queryParams);
return PageResult.success(result);
}
@ApiOperation(value = "角色下拉列表")
@GetMapping("/options")
public Result<List<Option>> listRoleOptions() {
List<Option> list = sysRoleService.listRoleOptions();
return Result.success(list);
}
@ApiOperation(value = "角色详情")
@GetMapping("/{roleId}")
public Result getRoleDetail(
@ApiParam("角色ID") @PathVariable Long roleId
) {
SysRole role = sysRoleService.getById(roleId);
return Result.success(role);
}
@ApiOperation(value = "新增角色")
@PostMapping
public Result addRole(@Valid @RequestBody RoleForm roleForm) {
boolean result = sysRoleService.saveRole(roleForm);
return Result.judge(result);
}
@ApiOperation(value = "修改角色")
@PutMapping(value = "/{id}")
public Result updateRole(@Valid @RequestBody RoleForm roleForm) {
boolean result = sysRoleService.saveRole(roleForm);
return Result.judge(result);
}
@ApiOperation(value = "删除角色")
@DeleteMapping("/{ids}")
public Result deleteRoles(
@ApiParam("删除角色,多个以英文逗号(,)分割") @PathVariable String ids
) {
boolean result = sysRoleService.deleteRoles(ids);
return Result.judge(result);
}
@ApiOperation(value = "修改角色状态")
@PutMapping(value = "/{roleId}/status")
public Result updateRoleStatus(
@ApiParam("角色ID") @PathVariable Long roleId,
@ApiParam("角色状态:1-正常0-禁用") @RequestParam Integer status
) {
boolean result = sysRoleService.updateRoleStatus(roleId, status);
return Result.judge(result);
}
@ApiOperation(value = "获取角色的资源ID集合", notes = "资源包括菜单和权限ID")
@GetMapping("/{roleId}/resources")
public Result<RoleResourceForm> getRoleResources(
@ApiParam("角色ID") @PathVariable Long roleId
) {
RoleResourceForm resourceIds = sysRoleService.getRoleResources(roleId);
return Result.success(resourceIds);
}
@ApiOperation(value = "分配角色的资源权限")
@PutMapping("/{roleId}/resources")
public Result updateRoleResource(
@PathVariable Long roleId,
@RequestBody RoleResourceForm roleResourceForm
) {
boolean result = sysRoleService.updateRoleResource(roleId,roleResourceForm);
return Result.judge(result);
}
}

View File

@@ -0,0 +1,153 @@
package com.youlai.system.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.youlai.system.common.result.PageResult;
import com.youlai.system.common.result.Result;
import com.youlai.system.pojo.dto.UserImportDTO;
import com.youlai.system.pojo.form.UserForm;
import com.youlai.system.pojo.entity.SysUser;
import com.youlai.system.pojo.query.UserPageQuery;
import com.youlai.system.pojo.vo.user.UserExportVO;
import com.youlai.system.pojo.vo.user.UserLoginVO;
import com.youlai.system.pojo.vo.user.UserVO;
import com.youlai.system.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
/**
* 用户控制器
*
* @author haoxr
* @date 2022/10/16
*/
@Api(tags = "用户接口")
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class SysUserController {
private final SysUserService userService;
@ApiOperation(value = "用户分页列表")
@GetMapping("/pages")
public PageResult<UserVO> listUserPages(UserPageQuery queryParams) {
IPage<UserVO> result = userService.listUserPages(queryParams);
return PageResult.success(result);
}
@ApiOperation(value = "用户表单数据")
@GetMapping("/{userId}/form")
public Result<UserForm> getUserDetail(
@ApiParam(value = "用户ID") @PathVariable Long userId
) {
UserForm formData = userService.getUserFormData(userId);
return Result.success(formData);
}
@ApiOperation(value = "新增用户")
@PostMapping
public Result saveUser(
@RequestBody @Valid UserForm userForm
) {
boolean result = userService.saveUser(userForm);
return Result.judge(result);
}
@ApiOperation(value = "修改用户")
@PutMapping(value = "/{userId}")
public Result updateUser(
@ApiParam("用户ID") @PathVariable Long userId,
@RequestBody @Validated UserForm userForm) {
boolean result = userService.updateUser(userId, userForm);
return Result.judge(result);
}
@ApiOperation(value = "删除用户")
@DeleteMapping("/{ids}")
public Result deleteUsers(
@ApiParam("用户ID多个以英文逗号(,)分割") @PathVariable String ids
) {
boolean result = userService.deleteUsers(ids);
return Result.judge(result);
}
@ApiOperation(value = "修改用户密码")
@PatchMapping(value = "/{userId}/password")
public Result updatePassword(
@ApiParam("用户ID") @PathVariable Long userId,
@RequestParam String password
) {
boolean result = userService.updatePassword(userId, password);
return Result.judge(result);
}
@ApiOperation(value = "修改用户状态")
@PatchMapping(value = "/{userId}/status")
public Result updatePassword(
@ApiParam("用户ID") @PathVariable Long userId,
@ApiParam("用户状态(1:启用;0:禁用)") @RequestParam Integer status
) {
boolean result = userService.update(new LambdaUpdateWrapper<SysUser>()
.eq(SysUser::getId, userId)
.set(SysUser::getStatus, status)
);
return Result.judge(result);
}
@ApiOperation(value = "获取登录用户信息")
@GetMapping("/me")
public Result<UserLoginVO> getUserLoginInfo() {
UserLoginVO userLoginVO = userService.getUserLoginInfo();
return Result.success(userLoginVO);
}
@ApiOperation("用户导入模板下载")
@GetMapping("/template")
public void downloadTemplate(HttpServletResponse response) throws IOException {
String fileName = "用户导入模板.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
String fileClassPath = "excel-templates" + File.separator + fileName;
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(fileClassPath);
ServletOutputStream outputStream = response.getOutputStream();
ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).build();
excelWriter.finish();
}
@ApiOperation("导入用户")
@PostMapping("/_import")
public Result importUsers(UserImportDTO userImportDTO) throws IOException {
String msg = userService.importUsers(userImportDTO);
return Result.success(msg);
}
@ApiOperation("导出用户")
@GetMapping("/_export")
public void exportUsers(UserPageQuery queryParams, HttpServletResponse response) throws IOException {
String fileName = "用户列表.xlsx";
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
List<UserExportVO> exportUserList = userService.listExportUsers(queryParams);
EasyExcel.write(response.getOutputStream(), UserExportVO.class).sheet("用户列表").doWrite(exportUserList);
}
}

View File

@@ -0,0 +1,22 @@
package com.youlai.system.converter;
import com.youlai.system.pojo.entity.SysDept;
import com.youlai.system.pojo.form.DeptForm;
import com.youlai.system.pojo.vo.dept.DeptVO;
import org.mapstruct.Mapper;
/**
* 部门对象转换器
*
* @author haoxr
* @date 2022/7/29
*/
@Mapper(componentModel = "spring")
public interface DeptConverter {
DeptForm entity2Form(SysDept entity);
DeptVO entity2Vo(SysDept entity);
SysDept form2Entity(DeptForm deptForm);
}

View File

@@ -0,0 +1,25 @@
package com.youlai.system.converter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.pojo.entity.SysDictItem;
import com.youlai.system.pojo.form.DictItemForm;
import com.youlai.system.pojo.vo.dict.DictItemPageVO;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
/**
* 字典数据项对象转换器
*
* @author haoxr
* @date 2022/6/8
*/
@Mapper(componentModel = "spring")
public interface DictItemConverter {
Page<DictItemPageVO> entity2Page(Page<SysDictItem> page);
DictItemForm entity2Form(SysDictItem entity);
@InheritInverseConfiguration(name="entity2Form")
SysDictItem form2Entity(DictItemForm entity);
}

View File

@@ -0,0 +1,23 @@
package com.youlai.system.converter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.pojo.entity.SysDictType;
import com.youlai.system.pojo.form.DictTypeForm;
import com.youlai.system.pojo.vo.dict.DictTypePageVO;
import org.mapstruct.Mapper;
/**
* 字典类型对象转换器
*
* @author haoxr
* @date 2022/6/8
*/
@Mapper(componentModel = "spring")
public interface DictTypeConverter {
Page<DictTypePageVO> entity2Page(Page<SysDictType> page);
DictTypeForm entity2Form(SysDictType entity);
SysDictType form2Entity(DictTypeForm entity);
}

View File

@@ -0,0 +1,23 @@
package com.youlai.system.converter;
import com.youlai.system.pojo.entity.SysMenu;
import com.youlai.system.pojo.po.RoutePO;
import com.youlai.system.pojo.vo.menu.MenuVO;
import com.youlai.system.pojo.vo.menu.RouteVO;
import org.mapstruct.Mapper;
import java.util.List;
/**
* 菜单对象转换器
*
* @author haoxr
* @date 2022/7/29
*/
@Mapper(componentModel = "spring")
public interface MenuConverter {
MenuVO entity2VO(SysMenu entity);
}

View File

@@ -0,0 +1,35 @@
package com.youlai.system.converter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.common.model.Option;
import com.youlai.system.pojo.entity.SysRole;
import com.youlai.system.pojo.form.RoleForm;
import com.youlai.system.pojo.vo.role.RolePageVO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import java.util.List;
/**
* 角色对象转换器
*
* @author haoxr
* @date 2022/5/29
*/
@Mapper(componentModel = "spring")
public interface RoleConverter {
Page<RolePageVO> entity2Page(Page<SysRole> page);
@Mappings({
@Mapping(target = "value", source = "id"),
@Mapping(target = "label", source = "name")
})
Option role2Option(SysRole role);
List<Option> roles2Options(List<SysRole> roles);
SysRole form2Entity(RoleForm roleForm);
}

View File

@@ -0,0 +1,44 @@
package com.youlai.system.converter;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.pojo.entity.SysUser;
import com.youlai.system.pojo.form.UserForm;
import com.youlai.system.pojo.po.UserFormPO;
import com.youlai.system.pojo.po.UserPO;
import com.youlai.system.pojo.vo.user.UserLoginVO;
import com.youlai.system.pojo.vo.user.UserVO;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
/**
* 用户对象转换器
*
* @author haoxr
* @date 2022/6/8
*/
@Mapper(componentModel = "spring")
public interface UserConverter {
@Mappings({
@Mapping(target = "genderLabel", expression = "java(com.youlai.system.common.base.IBaseEnum.getLabelByValue(po.getGender(), com.youlai.system.common.enums.GenderEnum.class))")
})
UserVO po2Vo(UserPO po);
Page<UserVO> po2Vo(Page<UserPO> po);
UserForm po2Form(UserFormPO po);
UserForm entity2Form(SysUser entity);
@InheritInverseConfiguration(name = "entity2Form")
SysUser form2Entity(UserForm entity);
@Mappings({
@Mapping(target = "userId", source = "id")
})
UserLoginVO entity2LoginUser(SysUser entity);
}

View File

@@ -0,0 +1,120 @@
package com.youlai.system.handler;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.springframework.util.StringUtils;
import java.lang.reflect.Array;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Objects;
/**
* 数组类型转换 json
* <p>
* 主要是用于对象数据 基础类型包装对象不建议用
* <a href="https://www.jianshu.com/p/ab832f3fe81c">https://www.jianshu.com/p/ab832f3fe81c</a>
*
* @author Gadfly
* @since 2021-06-30 15:20
*/
@Slf4j
@MappedJdbcTypes(value = {JdbcType.OTHER}, includeNullJdbcType = true)
public class ArrayObjectJsonTypeHandler<E> extends BaseTypeHandler<E[]> {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String STRING_JSON_ARRAY_EMPTY = "[]";
static {
// 未知字段忽略
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 不使用科学计数
MAPPER.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
// null 值不输出(节省内存)
MAPPER.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
}
private final Class<E[]> type;
public ArrayObjectJsonTypeHandler(Class<E[]> type) {
Objects.requireNonNull(type);
this.type = type;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E[] parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, toJson(parameter));
}
@Override
public E[] getNullableResult(ResultSet rs, String columnName) throws SQLException {
return toObject(rs.getString(columnName), type);
}
@Override
public E[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return toObject(rs.getString(columnIndex), type);
}
@Override
public E[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return toObject(cs.getString(columnIndex), type);
}
/**
* object 转 json
*
* @param obj 对象
* @return String json字符串
*/
private String toJson(E[] obj) {
if (ArrayUtils.isEmpty(obj)) {
return STRING_JSON_ARRAY_EMPTY;
}
try {
return MAPPER.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new RuntimeException("mybatis column to json error,obj:" + Arrays.toString(obj), e);
}
}
/**
* 转换对象
*
* @param json json数据
* @param clazz 类
* @return E
*/
private E[] toObject(String json, Class<E[]> clazz) {
if (json == null) {
return null;
}
if (!StringUtils.hasText(json)) {
return newArray(clazz);
}
try {
return MAPPER.readValue(json, clazz);
} catch (JsonProcessingException e) {
log.error("mybatis column json to object error,json:{}", json, e);
return newArray(clazz);
}
}
@SuppressWarnings("unchecked")
private E[] newArray(Class<E[]> clazz) {
return (E[]) Array.newInstance(clazz.getComponentType(), 0);
}
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;
/**
* Integer 数组类型转换 json
* <a href="https://www.jianshu.com/p/ab832f3fe81c">https://www.jianshu.com/p/ab832f3fe81c</a>
*
* @author haoxr
* @since 2022/10/14 15:19
*/
@Slf4j
@Component
@MappedTypes(value = {Integer[].class})
@MappedJdbcTypes(value = {JdbcType.VARCHAR}, includeNullJdbcType = true)
public class IntegerArrayJsonTypeHandler extends ArrayObjectJsonTypeHandler<Integer> {
public IntegerArrayJsonTypeHandler() {
super(Integer[].class);
}
}

View File

@@ -0,0 +1,23 @@
package com.youlai.system.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;
/**
* Long 数组类型转换 json
*
* @author haoxr
* @since 2022/10/14
*/
@Slf4j
@Component
@MappedTypes(value = {Long[].class})
@MappedJdbcTypes(value = {JdbcType.OTHER}, includeNullJdbcType = true)
public class LongArrayJsonTypeHandler extends ArrayObjectJsonTypeHandler<Long> {
public LongArrayJsonTypeHandler() {
super(Long[].class);
}
}

View File

@@ -0,0 +1,40 @@
package com.youlai.system.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* mybatis-plus 字段自动填充
*
* @author haoxr
* @date 2022/10/14
* @link https://mp.baomidou.com/guide/auto-fill-metainfo.html
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 新增填充创建时间
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class);
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class);
}
/**
* 更新填充更新时间
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class);
}
}

View File

@@ -0,0 +1,23 @@
package com.youlai.system.handler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;
/**
* String 数组类型转换 json
*
* @author haoxr
* @since 2022/10/14
*/
@Slf4j
@Component
@MappedTypes(value = {String[].class})
@MappedJdbcTypes(value = {JdbcType.OTHER}, includeNullJdbcType = true)
public class StringArrayJsonTypeHandler extends ArrayObjectJsonTypeHandler<String> {
public StringArrayJsonTypeHandler() {
super(String[].class);
}
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.listener;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.youlai.system.pojo.dto.UserImportDTO;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
/**
*
* @author haoxr
* @date 2022/4/10 20:49
*/
@Component
@Scope("prototype")
public class UserImportListener extends AnalysisEventListener<UserImportDTO.UserItem> {
@Override
public void invoke(UserImportDTO.UserItem userItem, AnalysisContext analysisContext) {
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}

View File

@@ -0,0 +1,19 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.youlai.system.pojo.entity.SysDept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface SysDeptMapper extends BaseMapper<SysDept> {
// @DataPermission
@Override
List<SysDept> selectList(@Param(Constants.WRAPPER) Wrapper<SysDept> queryWrapper);
}

View File

@@ -0,0 +1,14 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysDictItem;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysDictItemMapper extends BaseMapper<SysDictItem> {
}

View File

@@ -0,0 +1,14 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysDictType;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysDictTypeMapper extends BaseMapper<SysDictType> {
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.mapper;
/**
* 菜单持久接口层
*
* @author haoxr
* @date 2022/1/24
*/
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysMenu;
import com.youlai.system.pojo.po.RoutePO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Set;
@Mapper
public interface SysMenuMapper extends BaseMapper<SysMenu> {
List<RoutePO> listRoutes();
/**
* 获取角色权限集合
*
* @param roles
* @return
*/
Set<String> listRolePerms(Set<String> roles);
}

View File

@@ -0,0 +1,11 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysRole;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SysRoleMapper extends BaseMapper<SysRole> {
}

View File

@@ -0,0 +1,25 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysRoleMenu;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 角色菜单持久层
*
* @author haoxr
* @date 2022/6/4
*/
@Mapper
public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenu> {
/**
* 获取角色拥有的菜单ID集合
*
* @param roleId
* @return
*/
List<Long> listMenuIdsByRoleId(Long roleId);
}

View File

@@ -0,0 +1,56 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youlai.system.pojo.entity.SysUser;
import com.youlai.system.pojo.po.UserAuthInfo;
import com.youlai.system.pojo.po.UserFormPO;
import com.youlai.system.pojo.po.UserPO;
import com.youlai.system.pojo.query.UserPageQuery;
import com.youlai.system.pojo.vo.user.UserExportVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 用户持久层
*
* @author haoxr
* @date 2022/1/14
*/
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
/**
* 获取用户分页列表
*
* @param page
* @param queryParams 查询参数
* @return
*/
Page<UserPO> listUserPages(Page<UserPO> page, UserPageQuery queryParams);
/**
* 获取用户表单详情
*
* @param userId 用户ID
* @return
*/
UserFormPO getUserDetail(Long userId);
/**
* 根据用户名获取认证信息
*
* @param username
* @return
*/
UserAuthInfo getUserAuthInfo(String username);
/**
* 获取导出用户列表
*
* @param queryParams
* @return
*/
List<UserExportVO> listExportUsers(UserPageQuery queryParams);
}

View File

@@ -0,0 +1,16 @@
package com.youlai.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.youlai.system.pojo.entity.SysUserRole;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户角色持久层
*
* @author haoxr
* @date 2022/1/15
*/
@Mapper
public interface SysUserRoleMapper extends BaseMapper<SysUserRole> {
}

View File

@@ -0,0 +1,58 @@
package com.youlai.system.pojo.dto;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 用户导入对象
*
* @author haoxr
* @date 2022/4/10
*/
@Data
public class UserImportDTO {
/**
* 部门ID
*/
private Long deptId;
/**
* 角色ID
*/
private String roleIds;
private MultipartFile file;
/**
* 导入的用户列表
*/
private List<UserItem> userList;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class UserItem {
@ExcelProperty(value = "用户名")
private String username;
@ExcelProperty(value = "用户昵称")
private String nickname;
@ExcelProperty(value = "性别")
private String gender;
@ExcelProperty(value = "手机号码")
private String mobile;
@ExcelProperty(value = "邮箱")
private String email;
}
}

View File

@@ -0,0 +1,66 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 部门表
* @TableName sys_dept
*/
@TableName(value ="sys_dept")
@Data
public class SysDept implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 部门名称
*/
private String name;
/**
* 父节点id
*/
private Long parentId;
/**
* 父节点id路径
*/
private String treePath;
/**
* 显示顺序
*/
private Integer sort;
/**
* 状态(1:正常;0:禁用)
*/
private Integer status;
/**
* 逻辑删除标识(1:已删除;0:未删除)
*/
private Integer deleted;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,71 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 字典数据表
* @TableName sys_dict_item
*/
@TableName(value ="sys_dict_item")
@Data
public class SysDictItem implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 字典类型编码
*/
private String typeCode;
/**
* 字典项名称
*/
private String name;
/**
* 字典项值
*/
private String value;
/**
* 排序
*/
private Integer sort;
/**
* 状态(1:正常;0:禁用)
*/
private Integer status;
/**
* 是否默认(1:是;0:否)
*/
private Integer defaulted;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,56 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 字典类型表
* @TableName sys_dict_type
*/
@TableName(value ="sys_dict_type")
@Data
public class SysDictType implements Serializable {
/**
* 主键
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 类型名称
*/
private String name;
/**
* 类型编码
*/
private String code;
/**
* 状态(0:正常;1:禁用)
*/
private Integer status;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,76 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import com.youlai.system.common.base.BaseEntity;
import com.youlai.system.common.enums.MenuTypeEnum;
import lombok.Data;
/**
* 菜单表实体
*/
@TableName(value ="sys_menu")
@Data
public class SysMenu extends BaseEntity {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 菜单名称
*/
private String name;
/**
* 菜单类型(1-菜单2-目录3-外链4-按钮权限)
*/
private MenuTypeEnum type;
/**
* 路由路径(浏览器地址栏路径)
*/
private String path;
/**
* 组件路径(vue页面完整路径省略.vue后缀)
*/
private String component;
/**
* 权限标识
*/
private String perm;
/**
* 显示状态(1:显示;0:隐藏)
*/
private Integer visible;
/**
* 排序
*/
private Integer sort;
/**
* 菜单图标
*/
private String icon;
/**
* 外链路径
*/
private String redirectUrl;
}

View File

@@ -0,0 +1,61 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 角色表
* @TableName sys_role
*/
@TableName(value ="sys_role")
@Data
public class SysRole implements Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 角色名称
*/
private String name;
/**
* 角色编码
*/
private String code;
/**
* 显示顺序
*/
private Integer sort;
/**
* 角色状态(1-正常0-停用)
*/
private Integer status;
/**
* 逻辑删除标识(0-未删除1-已删除)
*/
private Integer deleted;
/**
* 更新时间
*/
private Date createTime;
/**
* 创建时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* 角色和菜单关联表
* @TableName sys_role_menu
*/
@TableName(value ="sys_role_menu")
@Data
@AllArgsConstructor
public class SysRoleMenu implements Serializable {
/**
* 角色ID
*/
private Long roleId;
/**
* 菜单ID
*/
private Long menuId;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,86 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 用户信息表
* @TableName sys_user
*/
@TableName(value ="sys_user")
@Data
public class SysUser implements Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 性别((1:男;2:女))
*/
private Integer gender;
/**
* 密码
*/
private String password;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户头像
*/
private String avatar;
/**
* 联系方式
*/
private String mobile;
/**
* 用户状态((1:正常;0:禁用))
*/
private Integer status;
/**
* 用户邮箱
*/
private String email;
/**
* 逻辑删除标识(0:未删除;1:已删除)
*/
private Integer deleted;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* 用户和角色关联表
* @TableName sys_user_role
*/
@TableName(value ="sys_user_role")
@Data
@AllArgsConstructor
public class SysUserRole implements Serializable {
/**
* 用户ID
*/
private Long userId;
/**
* 角色ID
*/
private Long roleId;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.pojo.form;
import com.youlai.system.common.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@ApiModel("部门表单对象")
@Data
public class DeptForm extends BaseEntity {
@ApiModelProperty("部门名称")
private String name;
@ApiModelProperty("父部门ID")
@NotNull(message = "父部门ID不能为空")
private Long parentId;
@ApiModelProperty("状态")
private Integer status;
@ApiModelProperty("排序")
private Integer sort;
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.pojo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典数据项")
@Data
public class DictItemForm {
@ApiModelProperty("数据项ID")
private Long id;
@ApiModelProperty("类型编码")
private String typeCode;
@ApiModelProperty("数据项名称")
private String name;
@ApiModelProperty("")
private String value;
@ApiModelProperty("状态1->启用;0->禁用")
private Integer status;
@ApiModelProperty("排序")
private Integer sort;
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.pojo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典类型")
@Data
public class DictTypeForm {
@ApiModelProperty("字典类型ID")
private Long id;
@ApiModelProperty("类型名称")
private String name;
@ApiModelProperty("类型编码")
private String code;
@ApiModelProperty("类型状态1->启用;0->禁用")
private Integer status;
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.pojo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@ApiModel("角色表单对象")
@Data
public class RoleForm {
@ApiModelProperty("角色ID")
private Long id;
@ApiModelProperty("角色名称")
@NotBlank(message = "角色名称不能为空")
private String name;
@ApiModelProperty("角色编码")
@NotBlank(message = "角色编码不能为空")
private String code;
@ApiModelProperty("排序")
private Integer sort;
@ApiModelProperty("角色状态(1-正常0-停用)")
private Integer status;
}

View File

@@ -0,0 +1,31 @@
package com.youlai.system.pojo.form;
import lombok.Data;
import java.util.List;
/**
* 角色权限传输层对象
*
* @author haoxr
* @date 2021/12/19 11:46
*/
@Data
public class RolePermsForm {
/**
* 菜单ID
*/
private Long menuId;
/**
* 角色ID
*/
private Long roleId;
/**
* 权限ID集合
*/
private List<Long> permIds;
}

View File

@@ -0,0 +1,20 @@
package com.youlai.system.pojo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("菜单资源表单")
@Data
public class RoleResourceForm {
@ApiModelProperty("菜单ID集合")
private List<Long> menuIds;
@ApiModelProperty("权限ID集合")
private List<Long> permIds;
}

View File

@@ -0,0 +1,55 @@
package com.youlai.system.pojo.form;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import java.util.List;
/**
* 用户表单对象
*
* @author haoxr
* @date 2022/4/12 11:04
*/
@ApiModel
@Data
public class UserForm {
@ApiModelProperty("用户ID")
private Long id;
@ApiModelProperty("用户名")
@NotBlank(message = "用户名不能为空")
private String username;
@ApiModelProperty("昵称")
@NotBlank(message = "昵称不能为空")
private String nickname;
@Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "{phone.valid}")
private String mobile;
@ApiModelProperty("性别")
private Integer gender;
@ApiModelProperty("用户头像")
private String avatar;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("用户状态(1:正常;0:禁用)")
private Integer status;
@ApiModelProperty("部门ID")
private Long deptId;
@ApiModelProperty("角色ID集合")
@NotEmpty(message = "用户角色不能为空")
private List<Long> roleIds;
}

View File

@@ -0,0 +1,78 @@
package com.youlai.system.pojo.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.youlai.system.common.base.BaseEntity;
import com.youlai.system.common.enums.MenuTypeEnum;
import lombok.Data;
import java.util.List;
/**
* 路由
*/
@Data
public class RoutePO {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 菜单名称
*/
private String name;
/**
* 菜单类型(1-菜单2-目录3-外链4-按钮权限)
*/
private MenuTypeEnum type;
/**
* 路由路径(浏览器地址栏路径)
*/
private String path;
/**
* 组件路径(vue页面完整路径省略.vue后缀)
*/
private String component;
/**
* 权限标识
*/
private String perm;
/**
* 显示状态(1:显示;0:隐藏)
*/
private Integer visible;
/**
* 排序
*/
private Integer sort;
/**
* 菜单图标
*/
private String icon;
/**
* 外链路径
*/
private String redirectUrl;
/**
* 拥有路由的权限
*/
private List<String> roles;
}

View File

@@ -0,0 +1,33 @@
package com.youlai.system.pojo.po;
import lombok.Data;
import java.util.Set;
/**
* 用户认证信息
*
* @author haoxr
* @date 2022/10/22
*
*/
@Data
public class UserAuthInfo {
private Long userId;
private String username;
private String nickname;
private Long deptId;
private String password;
private Integer status;
private Set<String> roles;
private Set<String> perms;
}

View File

@@ -0,0 +1,66 @@
package com.youlai.system.pojo.po;
import lombok.Data;
import java.util.List;
/**
* user表单持久化对象
*
* @author haoxr
* @date 2022/6/10
*/
@Data
public class UserFormPO {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 手机号
*/
private String mobile;
/**
* 性别(1:男;2:女)
*/
private Integer gender;
/**
* 用户头像
*/
private String avatar;
/**
* 用户邮箱
*/
private String email;
/**
* 状态(1:启用;0:禁用)
*/
private Integer status;
/**
* 部门ID
*/
private Long deptId;
/**
* 角色ID集合
*/
private List<Long> roleIds;
}

View File

@@ -0,0 +1,72 @@
package com.youlai.system.pojo.po;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
* 用户持久化对象
*
* @author haoxr
* @date 2022/6/10
*/
@Data
public class UserPO {
/**
* 用户ID
*/
private Long id;
/**
* 账户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 手机号
*/
private String mobile;
/**
* 性别(1->男2->女)
*/
private Integer gender;
/**
* 头像URL
*/
private String avatar;
/**
* 邮箱
*/
private String email;
/**
* 状态: 1->启用;0->禁用
*/
private Integer status;
/**
* 部门名称
*/
private String deptName;
/**
* 角色名称,多个使用英文逗号(,)分割
*/
private String roleNames;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createTime;
}

View File

@@ -0,0 +1,23 @@
package com.youlai.system.pojo.query;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 部门分页查询对象
*
* @author haoxr
* @date 2022/6/11
*/
@ApiModel("部门分页查询对象")
@Data
public class DeptQuery {
@ApiModelProperty("关键字(部门名称)")
private String keywords;
@ApiModelProperty("状态(1->正常0->禁用)")
private Integer status;
}

View File

@@ -0,0 +1,18 @@
package com.youlai.system.pojo.query;
import com.youlai.system.common.base.BasePageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典数据项分页查询对象")
@Data
public class DictItemPageQuery extends BasePageQuery {
@ApiModelProperty("关键字(字典项名称)")
private String keywords;
@ApiModelProperty("字典类型编码")
private String typeCode;
}

View File

@@ -0,0 +1,16 @@
package com.youlai.system.pojo.query;
import com.youlai.system.common.base.BasePageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典类型分页查询对象")
@Data
public class DictTypePageQuery extends BasePageQuery {
@ApiModelProperty("关键字(类型名称/类型编码)")
private String keywords;
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.pojo.query;
import com.youlai.system.common.base.BasePageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 权限分页查询对象
*
* @author haoxr
* @date 2022/1/14 22:22
*/
@Data
@ApiModel
public class PermPageQuery extends BasePageQuery {
@ApiModelProperty("权限名称")
private String name;
@ApiModelProperty("菜单ID")
private Long menuId;
}

View File

@@ -0,0 +1,19 @@
package com.youlai.system.pojo.query;
import com.youlai.system.common.base.BasePageQuery;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 角色分页查询实体
*
* @author haoxr
* @date 2022/6/3
*
*/
@Data
public class RolePageQuery extends BasePageQuery {
@ApiModelProperty("关键字(角色名称/角色编码)")
private String keywords;
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.pojo.query;
import com.youlai.system.common.base.BasePageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 用户分页查询对象
*
* @author haoxr
* @date 2022/1/14
*/
@ApiModel
@Data
public class UserPageQuery extends BasePageQuery {
@ApiModelProperty("关键字(用户名/昵称/手机号)")
private String keywords;
@ApiModelProperty("用户状态")
private Integer status;
@ApiModelProperty("部门ID")
private Long deptId;
}

View File

@@ -0,0 +1,27 @@
package com.youlai.system.pojo.vo.dept;
import com.youlai.system.common.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("部门详情对象")
@Data
public class DeptDetailVO extends BaseEntity {
@ApiModelProperty("部门ID(编辑必填)")
private Long id;
@ApiModelProperty("部门名称")
private String name;
@ApiModelProperty("父部门ID")
private Long parentId;
@ApiModelProperty("状态")
private Integer status;
@ApiModelProperty("排序")
private Integer sort;
}

View File

@@ -0,0 +1,29 @@
package com.youlai.system.pojo.vo.dept;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class DeptVO {
private Long id;
private Long parentId;
private String name;
private Integer sort;
private Integer status;
private List<DeptVO> children;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.pojo.vo.dict;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典数据项分页对象")
@Data
public class DictItemPageVO {
@ApiModelProperty("数据项ID")
private Long id;
@ApiModelProperty("数据项名称")
private String name;
@ApiModelProperty("")
private String value;
@ApiModelProperty("类型状态1->启用;0->禁用")
private Integer status;
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.pojo.vo.dict;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("字典类型")
@Data
public class DictTypePageVO {
@ApiModelProperty("字典类型ID")
private Long id;
@ApiModelProperty("类型名称")
private String name;
@ApiModelProperty("类型编码")
private String code;
@ApiModelProperty("类型状态1->启用;0->禁用")
private Integer status;
}

View File

@@ -0,0 +1,30 @@
package com.youlai.system.pojo.vo.menu;
import lombok.Data;
@Data
public class MenuDetailVO {
private Long id;
private Long parentId;
private String name;
private String icon;
private String routeName;
private String routePath;
private String component;
private Integer sort;
private Integer visible;
private String redirect;
private Integer type;
}

View File

@@ -0,0 +1,41 @@
package com.youlai.system.pojo.vo.menu;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.youlai.system.common.enums.MenuTypeEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("菜单视图对象")
@Data
public class MenuVO {
private Long id;
private Long parentId;
private String name;
private String icon;
private String routeName;
private String routePath;
private String component;
private Integer sort;
private Integer visible;
private String redirect;
@ApiModelProperty("菜单类型")
private MenuTypeEnum type;
@JsonInclude(value = JsonInclude.Include.NON_NULL)
private List<MenuVO> children;
}

View File

@@ -0,0 +1,26 @@
package com.youlai.system.pojo.vo.menu;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@ApiModel("资源(菜单+权限)视图对象")
@Data
public class ResourceVO {
@ApiModelProperty("选项的值")
private Long value;
@ApiModelProperty("选项的标签")
private String label;
@ApiModelProperty("子菜单")
@JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private List<ResourceVO> children;
}

View File

@@ -0,0 +1,51 @@
package com.youlai.system.pojo.vo.menu;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import java.util.List;
/**
* 菜单路由视图对象
*
* @author haoxr
* @date 2020/11/28
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class RouteVO {
private String path;
private String component;
private String redirect;
private String name;
private Meta meta;
@Data
public static class Meta {
private String title;
private String icon;
private Boolean hidden;
/**
* 如果设置为 true目录没有子节点也会显示
*/
private Boolean alwaysShow;
private List<String> roles;
/**
* 页面缓存开启状态
*/
private Boolean keepAlive;
}
private List<RouteVO> children;
}

View File

@@ -0,0 +1,35 @@
package com.youlai.system.pojo.vo.perm;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 权限视图对象
*
* @author haoxr
* @date 2021/10/30 10:54
*/
@ApiModel("权限视图对象")
@Data
public class PermPageVO {
@ApiModelProperty("权限ID")
private Long id;
@ApiModelProperty("权限名称")
private String name;
@ApiModelProperty("URL权限标识-服务名称")
private String serviceName;
@ApiModelProperty("URL权限标识-请求标识")
private String requestMethod;
@ApiModelProperty("URL权限标识-请求方式")
private String requestPath;
@ApiModelProperty("按钮权限标识")
private String btnPerm;
}

View File

@@ -0,0 +1,34 @@
package com.youlai.system.pojo.vo.role;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@ApiModel("角色分页视图对象")
@Data
public class RolePageVO {
@ApiModelProperty("角色ID")
private Long id;
@ApiModelProperty("角色名称")
private String name;
@ApiModelProperty("角色编码")
private String code;
@ApiModelProperty("角色状态")
private Integer status;
@ApiModelProperty("排序")
private Integer sort;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,49 @@
package com.youlai.system.pojo.vo.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 用户表详情视图对象
*
* @author haoxr
* @date 2022/8/25
*/
@ApiModel
@Data
public class UserDetailVO {
@ApiModelProperty("用户ID")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("")
private String mobile;
@ApiModelProperty("性别")
private Integer gender;
@ApiModelProperty("用户头像")
private String avatar;
@ApiModelProperty("邮箱")
private String email;
@ApiModelProperty("用户状态(1:正常;0:禁用)")
private Integer status;
@ApiModelProperty("部门ID")
private Long deptId;
@ApiModelProperty("角色ID集合")
private List<Long> roleIds;
}

View File

@@ -0,0 +1,44 @@
package com.youlai.system.pojo.vo.user;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户导出视图对象
*
* @author haoxr
* @date 2022/4/11 8:46
*/
@Data
@ColumnWidth(20)
public class UserExportVO {
@ExcelProperty(value = "用户名")
private String username;
@ExcelProperty(value = "用户昵称")
private String nickname;
@ExcelProperty(value = "部门")
private String deptName;
@ExcelProperty(value = "性别")
private String gender;
@ExcelProperty(value = "手机号码")
private String mobile;
@ExcelProperty(value = "邮箱")
private String email;
@ExcelProperty(value = "创建时间")
@DateTimeFormat("yyyy/MM/dd HH:mm:ss")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,35 @@
package com.youlai.system.pojo.vo.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
import java.util.Set;
/**
* 用户登录视图对象
*
* @author haoxr
* @date 2022/1/14
*/
@ApiModel("当前登录用户视图对象")
@Data
public class UserLoginVO {
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("用户昵称")
private String nickname;
@ApiModelProperty("头像地址")
private String avatar;
@ApiModelProperty("用户角色编码集合")
private Set<String> roles;
@ApiModelProperty("用户权限标识集合")
private Set<String> perms;
}

View File

@@ -0,0 +1,54 @@
package com.youlai.system.pojo.vo.user;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
/**
* 用户分页视图对象
*
* @author haoxr
* @date 2022/1/15 9:41
*/
@ApiModel("用户分页视图对象")
@Data
public class UserVO {
@ApiModelProperty("用户ID")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("用户昵称")
private String nickname;
@ApiModelProperty("手机号")
private String mobile;
@ApiModelProperty("性别")
private String genderLabel;
@ApiModelProperty("用户头像地址")
private String avatar;
@ApiModelProperty("用户邮箱")
private String email;
@ApiModelProperty("用户状态(1:启用;0:禁用)")
private Integer status;
@ApiModelProperty("部门名称")
private String deptName;
@ApiModelProperty("角色名称,多个使用英文逗号(,)分割")
private String roleNames;
@ApiModelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date createTime;
}

View File

@@ -0,0 +1,77 @@
package com.youlai.system.security;
import com.youlai.system.security.jwt.JwtAuthenticationFilter;
import com.youlai.system.security.jwt.JwtTokenManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author haoxr
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtTokenManager jwtTokenManager;
public SecurityConfig(
JwtTokenManager jwtTokenManager
) {
this.jwtTokenManager = jwtTokenManager;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests(auth -> auth.antMatchers("/**").permitAll()
.anyRequest().authenticated());
// disable cache
http.headers().cacheControl();
http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenManager), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring()
.antMatchers("/api/v1/auth/login","/webjars/**", "/doc.html", "/swagger-resources/**", "/v3/api-docs");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 无法直接注入 AuthenticationManager
*
* @param authenticationConfiguration
* @return
* @throws Exception
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}

View File

@@ -0,0 +1,22 @@
package com.youlai.system.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.util.ResponseUtils;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring Security访问异常处理器
*
* @author haoxr
* @date 2022/10/18
*/
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_ACCESS_FORBIDDEN);
}
}

View File

@@ -0,0 +1,24 @@
package com.youlai.system.security.exception;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.util.ResponseUtils;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 认证异常处理
*
* @author haoxr
* @date 2022/10/18
*/
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID_OR_EXPIRED);
}
}

View File

@@ -0,0 +1,57 @@
package com.youlai.system.security.jwt;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.common.result.ResultCode;
import com.youlai.system.util.ResponseUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* jwt auth token filter.
*
* @author haoxr
*/
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String TOKEN_PREFIX = "Bearer ";
private final JwtTokenManager tokenManager;
public JwtAuthenticationFilter(JwtTokenManager tokenManager) {
this.tokenManager = tokenManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
String jwt = resolveToken(request);
if (StrUtil.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
try {
this.tokenManager.validateToken(jwt);
Authentication authentication = this.tokenManager.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}catch (Exception e){
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID_OR_EXPIRED);
}
}else{
ResponseUtils.writeErrMsg(response, ResultCode.TOKEN_INVALID_OR_EXPIRED);
}
}
/**
* Get token from header.
*/
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
return bearerToken.substring(TOKEN_PREFIX.length());
}
return null;
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 1999-2021 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.youlai.system.security.jwt;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.security.userdetails.SysUserDetails;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.DecodingException;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* JWT token manager.
*
* @author haoxr
* @date 2022/10/22
*/
@Component
public class JwtTokenManager {
/**
* secret key.
*/
@Value("${auth.token.secret_key}")
private String secretKey;
/**
* Token validity time(seconds).
*/
@Value("${auth.token.token_validity}")
private long tokenValidity;
/**
* secret key byte array.
*/
private byte[] secretKeyBytes;
private JwtParser jwtParser;
@Resource
private RedisTemplate redisTemplate;
/**
* Create token.
*
* @param authentication auth info
* @return token
*/
public String createToken(Authentication authentication) {
long now = System.currentTimeMillis();
Date validity;
validity = new Date(now + tokenValidity * 1000L);
Claims claims = Jwts.claims().setSubject(authentication.getName());
SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal();
claims.put("userId", userDetails.getUserId());
claims.put("username", claims.getSubject());
Set<String> roles = userDetails.getAuthorities().stream()
.map(item -> item.getAuthority()).collect(Collectors.toSet());
Set<String> authorities = userDetails.getPerms();
authorities.addAll(roles);
redisTemplate.opsForValue().set("USER_PERMS:" + userDetails.getUserId(), authorities);
return Jwts.builder().setClaims(claims).setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, Keys.hmacShaKeyFor(this.getSecretKeyBytes())).compact();
}
/**
* Create token.
*
* @param userName auth info
* @return token
*/
public String createToken(String userName) {
long now = System.currentTimeMillis();
Date validity;
validity = new Date(now + tokenValidity * 1000L);
Claims claims = Jwts.claims().setSubject(userName);
return Jwts.builder().setClaims(claims).setExpiration(validity)
.signWith(SignatureAlgorithm.HS256, Keys.hmacShaKeyFor(this.getSecretKeyBytes())).compact();
}
/**
* Get auth Info.
*
* @param token token
* @return auth info
*/
public Authentication getAuthentication(String token) {
if (jwtParser == null) {
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build();
}
Claims claims = jwtParser.parseClaimsJws(token).getBody();
List<GrantedAuthority> authorities = AuthorityUtils
.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
SysUserDetails principal = new SysUserDetails();
principal.setUserId(Convert.toLong(claims.get("userId")));
principal.setUsername(Convert.toStr(claims.get("username")));
// 权限数据过多放置在redis
Set<String> perms = (Set<String>) redisTemplate.opsForValue().get("USER_PERMS:" + claims.get("userId"));
if (CollectionUtil.isNotEmpty(perms)) {
List<GrantedAuthority> permAuthorities = perms.stream()
.map(perm -> new SimpleGrantedAuthority(perm))
.collect(Collectors.toList());
authorities.addAll(permAuthorities);
}
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
/**
* validate token.
*
* @param token token
*/
public void validateToken(String token) {
if (jwtParser == null) {
jwtParser = Jwts.parserBuilder().setSigningKey(this.getSecretKeyBytes()).build();
}
jwtParser.parseClaimsJws(token);
}
public byte[] getSecretKeyBytes() {
if (secretKeyBytes == null) {
try {
secretKeyBytes = Decoders.BASE64.decode(secretKey);
} catch (DecodingException e) {
secretKeyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
}
}
return secretKeyBytes;
}
}

View File

@@ -0,0 +1,99 @@
package com.youlai.system.security.userdetails;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.system.pojo.po.UserAuthInfo;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Spring Security
* @author haoxr
*/
@Data
public class SysUserDetails implements UserDetails {
private Long userId;
private String username;
private String password;
private Boolean enabled;
private Collection<SimpleGrantedAuthority> authorities;
private String authorityStr;
private Set<String> perms;
public SysUserDetails() {
}
public SysUserDetails(UserAuthInfo user) {
this.userId = user.getUserId();
Set<String> roles = user.getRoles();
Set<SimpleGrantedAuthority> authorities;
if (CollectionUtil.isNotEmpty(roles)) {
authorities = roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role)) // 标识角色
.collect(Collectors.toSet());
} else {
authorities = Collections.EMPTY_SET;
}
this.authorities = authorities;
this.username = user.getUsername();
this.password = user.getPassword();
this.enabled = ObjectUtil.equal(user.getStatus(), 1);
this.perms=user.getPerms();
}
public Long getUserId() {
return this.userId;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}

View File

@@ -0,0 +1,29 @@
package com.youlai.system.security.userdetails;
import com.youlai.system.pojo.po.UserAuthInfo;
import com.youlai.system.service.SysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author haoxr
*/
@Service
@RequiredArgsConstructor
public class SysUserDetailsServiceImpl implements UserDetailsService {
private final SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserAuthInfo userAuthInfo = sysUserService.getUserAuthInfo(username);
if(userAuthInfo==null){
throw new UsernameNotFoundException(username);
}
return new SysUserDetails(userAuthInfo);
}
}

View File

@@ -0,0 +1,65 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.common.model.Option;
import com.youlai.system.pojo.entity.SysDept;
import com.youlai.system.pojo.form.DeptForm;
import com.youlai.system.pojo.query.DeptQuery;
import com.youlai.system.pojo.vo.dept.DeptVO;
import java.util.List;
/**
* 部门业务接口
*
* @author haoxr
* @date 2021/8/22
*/
public interface SysDeptService extends IService<SysDept> {
/**
* 部门列表
*
* @return
*/
List<DeptVO> listDepartments(DeptQuery queryParams);
/**
* 部门树形下拉选项
*
* @return
*/
List<Option> listDeptOptions();
/**
* 新增部门
*
* @param formData
* @return
*/
Long saveDept(DeptForm formData);
/**
* 修改部门
*
* @param deptId
* @param formData
* @return
*/
Long updateDept(Long deptId, DeptForm formData);
/**
* 删除部门
*
* @param ids 部门ID多个以英文逗号,拼接字符串
* @return
*/
boolean deleteByIds(String ids);
/**
* 获取部门详情
*
* @param deptId
* @return
*/
DeptForm getDeptForm(Long deptId);
}

View File

@@ -0,0 +1,54 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.pojo.entity.SysDictItem;
import com.youlai.system.pojo.form.DictItemForm;
import com.youlai.system.pojo.query.DictItemPageQuery;
import com.youlai.system.pojo.vo.dict.DictItemPageVO;
/**
*
*/
public interface SysDictItemService extends IService<SysDictItem> {
/**
* 字典数据项分页列表
*
* @param queryParams
* @return
*/
Page<DictItemPageVO> listDictItemPages(DictItemPageQuery queryParams);
/**
* 字典数据项表单详情
*
* @param id 字典数据项ID
* @return
*/
DictItemForm getDictItemForm(Long id);
/**
* 新增字典数据项
*
* @param dictItemForm 字典数据项表单
* @return
*/
boolean saveDictItem(DictItemForm dictItemForm);
/**
* 修改字典数据项
*
* @param id 字典数据项ID
* @param dictItemForm 字典数据项表单
* @return
*/
boolean updateDictItem(Long id, DictItemForm dictItemForm);
/**
* 删除字典数据项
*
* @param idsStr 字典数据项ID多个以英文逗号(,)分割
* @return
*/
boolean deleteDictItems(String idsStr);
}

View File

@@ -0,0 +1,73 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.common.model.Option;
import com.youlai.system.pojo.entity.SysDictType;
import com.youlai.system.pojo.form.DictTypeForm;
import com.youlai.system.pojo.query.DictTypePageQuery;
import com.youlai.system.pojo.vo.dict.DictTypePageVO;
import java.util.List;
/**
* 数据字典类型业务接口
*
* @author haoxr
* @date 2022/10/12
*/
public interface SysDictTypeService extends IService<SysDictType> {
/**
* 字典类型分页列表
*
* @param queryParams 分页查询对象
* @return
*/
Page<DictTypePageVO> listDictTypePages(DictTypePageQuery queryParams);
/**
* 获取字典类型表单详情
*
* @param id 字典类型ID
* @return
*/
DictTypeForm getDictTypeFormData(Long id);
/**
* 新增字典类型
*
* @param dictTypeForm 字典类型表单
* @return
*/
boolean saveDictType(DictTypeForm dictTypeForm);
/**
* 修改字典类型
*
* @param id
* @param dictTypeForm 字典类型表单
* @return
*/
boolean updateDictType(Long id, DictTypeForm dictTypeForm);
/**
* 删除字典类型
*
* @param idsStr 字典类型ID多个以英文逗号(,)分割
* @return
*/
boolean deleteDictTypes(String idsStr);
/**
* 获取字典类型的数据项
*
* @param typeCode
* @return
*/
List<Option> listDictItemsByTypeCode(String typeCode);
}

View File

@@ -0,0 +1,80 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.common.model.Option;
import com.youlai.system.pojo.entity.SysMenu;
import com.youlai.system.pojo.vo.menu.MenuVO;
import com.youlai.system.pojo.vo.menu.ResourceVO;
import com.youlai.system.pojo.vo.menu.RouteVO;
import java.util.List;
import java.util.Set;
/**
* 菜单业务接口
*
* @author haoxr
* @date 2020/11/06
*/
public interface SysMenuService extends IService<SysMenu> {
/**
* 获取菜单表格列表
*
* @param name 菜单名称
* @return
*/
List<MenuVO> listMenus(String name);
/**
* 获取菜单下拉列表
*
* @return
*/
List<Option> listMenuOptions();
/**
* 新增菜单
*
* @param menu
* @return
*/
boolean saveMenu(SysMenu menu);
/**
* 清理路由缓存
*/
void cleanCache();
/**
* 获取路由列表
*
* @return
*/
List<RouteVO> listRoutes();
/**
* 资源(菜单+权限)树形列表
*
* @return
*/
List<ResourceVO> listResources();
/**
* 修改菜单显示状态
*
* @param menuId 菜单ID
* @param visible 是否显示(1->显示2->隐藏)
* @return
*/
boolean updateMenuVisible(Long menuId, Integer visible);
/**
* 获取角色权限集合
*
* @param roles
* @return
*/
Set<String> listRolePerms(Set<String> roles);
}

View File

@@ -0,0 +1,19 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.pojo.entity.SysRoleMenu;
import java.util.List;
public interface SysRoleMenuService extends IService<SysRoleMenu> {
/**
* 获取角色拥有的菜单ID集合
*
* @param roleId
* @return
*/
List<Long> listMenuIdsByRoleId(Long roleId);
}

View File

@@ -0,0 +1,81 @@
package com.youlai.system.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.youlai.system.common.model.Option;
import com.youlai.system.pojo.entity.SysRole;
import com.youlai.system.pojo.form.RoleForm;
import com.youlai.system.pojo.form.RoleResourceForm;
import com.youlai.system.pojo.query.RolePageQuery;
import com.youlai.system.pojo.vo.role.RolePageVO;
import java.util.List;
/**
* 角色业务接口层
*
* @author haoxr
* @date 2022/6/3
*/
public interface SysRoleService extends IService<SysRole> {
/**
* 角色分页列表
*
* @param queryParams
* @return
*/
Page<RolePageVO> listRolePages(RolePageQuery queryParams);
/**
* 角色下拉列表
*
* @return
*/
List<Option> listRoleOptions();
/**
*
* @param roleForm
* @return
*/
boolean saveRole(RoleForm roleForm);
/**
* 修改角色状态
*
* @param roleId
* @param status
* @return
*/
boolean updateRoleStatus(Long roleId, Integer status);
/**
* 批量删除角色
*
* @param ids
* @return
*/
boolean deleteRoles(String ids);
/**
* 获取角色的资源ID集合,资源包括菜单和权限
*
* @param roleId
* @return
*/
RoleResourceForm getRoleResources(Long roleId);
/**
* 修改角色的资源权限
*
* @param roleId
* @param roleResourceForm
* @return
*/
boolean updateRoleResource(Long roleId, RoleResourceForm roleResourceForm);
}

Some files were not shown because too many files have changed in this diff Show More