Merge remote-tracking branch 'remotes/origin/develop'

# Conflicts:
#	README.md
This commit is contained in:
Zhaoyang 2020-03-24 10:19:58 +08:00
commit a92d365917
192 changed files with 7075 additions and 1410 deletions

View File

@ -1,4 +1,4 @@
#### 助力抗击疫情diboot 团队紧急上线公益“轻统计工具”无需注册Excel一键生成在线填报表单、快速收集统计数据。[<点我使用>](http://s.dibo.ltd/#/g "数据统计工具")
> 助力抗击疫情diboot 团队紧急上线公益“轻统计工具”无需注册Excel一键生成在线填报表单、快速收集统计数据。[<点我使用>](http://s.dibo.ltd/#/g "注解自动绑定多表关联")
# diboot
<p align="center">
@ -12,8 +12,8 @@
> [设计目标](https://segmentfault.com/a/1190000020906742):面向开发人员的低代码开发平台,将重复性的工作自动化,提高质量、效率、可维护性。
diboot v2版本实现: diboot-core全新内核 + diboot-devtools代码生成平台 + IAM身份认证等基础功能组件。
![diboot平台组成结构图](diboot-docs/.vuepress/public/structure.png)
diboot v2版本目前实现: diboot-core全新内核 + diboot-devtools开发助理 + IAM身份认证、file文件处理等基础组件 + diboot-*-admin基础后台。
## 一、 diboot-core: 精简优化内核
全新精简内核,(基于diboot-core 2.x版本的CRUD和简单关联的常规功能实现代码量比1.x版本减少70%+),主要实现:
@ -25,7 +25,7 @@ diboot v2版本实现: diboot-core全新内核 + diboot-devtools代码生成
> 通过@BindDict注解实现数据字典(枚举)的存储值value与显示值name的转换。
#### 4. Entity/DTO自动转换为QueryWrapper
> @BindQuery注解绑定字段参数对应的查询条件类型Controller中直接绑定转换为QueryWrapper无需再手动构建QueryWrapper查询条件
#### 5. 其他常用工具类的最佳实践封装
#### 5. 其他常用Service接口、工具类的最佳实践封装
> 字符串处理、常用校验、BeanUtils、DateUtils等
更多介绍请查看: [diboot-core README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core "注解自动绑定多表关联").
@ -33,24 +33,36 @@ diboot v2版本实现: diboot-core全新内核 + diboot-devtools代码生成
## 二、 diboot-devtools 自动化开发助理
#### 1. 支持多数据库MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
#### 2. 使用很简单UI界面操作引入依赖jar配置参数后即可随SpringBoot启动运行)
#### 3. 功能很强大(数据结构变更与代码联动同步,一键生成/更新代码自动记录变更SQL、维护索引
#### 4. 配置很灵活(可按需配置生成代码是否启用`Lombok`、`Swagger`、`Shiro`等)
#### 5. SQL与代码很标准devtools标准化了数据结构定义与代码实现降低维护成本
* 支持多数据库MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
* 使用很简单UI界面操作引入依赖配置参数后即可随SpringBoot本地项目启动运行)
* 功能很强大(数据结构变更与代码联动同步,一键生成&非覆盖式更新代码自动记录变更SQL、维护索引
* 配置很灵活(可按需配置生成代码是否启用`Lombok`、`Swagger`等)
* SQL与代码很标准devtools标准化了数据结构定义与代码实现降低维护成本
> [我要试试](https://www.diboot.com/guide/diboot-devtools/%E4%BB%8B%E7%BB%8D.html)
## 三、iam-base 身份认证基础组件
## 三、iam-base 身份认证基础组件 及 配套VUE前端框架diboot-antd-admin、diboot-element-admin
#### 1. RBAC角色权限模型 + JWT的认证授权 实现
#### 2. BindPermission注解, 支持两级权限控制 + 自动鉴权
#### 3. BindPermission注解, 支持自动收集权限码并更新数据库
#### 4. 支持灵活的扩展能力(扩展多种登录方式、灵活替换用户实体类、自定义缓存等)
* RBAC角色权限模型 + JWT的认证授权 实现支持刷新token
* 简化的BindPermission注解支持兼容shiro的简化权限绑定与自动鉴权
* 自动提取需要验证的后端接口, 借助前端功能方便绑定前后端菜单按钮权限
* 支持灵活的扩展能力(扩展多种登录方式、灵活替换用户实体类、自定义缓存等)
* Starter启动自动安装依赖的数据表
* 启用devtools自动生成初始controller代码到本地
更多介绍请查看: [iam-base-starter README](https://github.com/dibo-software/diboot-v2/tree/master/iam-base-starter "身份认证管理组件").
## 四、diboot-file 文件相关处理组件
* EasyExcel轻量封装支持Java注解校验与@BindDict注解实现字典name-value转换提供完善的校验错误提示
* 封装常用的文件本地存储、上传下载、图片压缩水印等常用处理
* Starter启动自动安装依赖的数据表
* 启用devtools自动生成初始样例controller代码到本地
更多介绍请查看: [diboot-file-starter README](https://github.com/dibo-software/diboot-v2/tree/master/diboot-file-starter "文件组件").
> 其他组件逐步开发中 ...
## 四、技术交流群
## 、技术交流群
如果您有技术问题,欢迎加群交流:

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
</parent>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
<packaging>jar</packaging>
<description>diboot core starter project</description>
@ -25,7 +25,7 @@
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
</dependency>
<dependency>

View File

@ -3,6 +3,7 @@ package com.diboot.core.starter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.diboot.core.config.Cons;
import com.diboot.core.util.D;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
@ -17,6 +18,7 @@ import org.springframework.core.env.Environment;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@ -55,9 +57,9 @@ public class CoreAutoConfiguration{
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
//处理中文乱码问题
converter.setDefaultCharset(Charset.forName(Cons.CHARSET_UTF8));
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastMediaTypes.add(MediaType.APPLICATION_JSON);
converter.setSupportedMediaTypes(fastMediaTypes);
// 配置转换格式
FastJsonConfig fastJsonConfig = new FastJsonConfig();

View File

@ -1,24 +1,17 @@
package com.diboot.core.starter;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.V;
import org.apache.commons.io.IOUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -30,12 +23,11 @@ import java.util.Map;
* @date 2019/08/01
*/
public class SqlHandler {
private static final Logger logger = LoggerFactory.getLogger(SqlHandler.class);
private static final Logger log = LoggerFactory.getLogger(SqlHandler.class);
// 数据字典SQL
private static final String DICTIONARY_SQL = "SELECT id FROM ${SCHEMA}.dictionary WHERE id=0";
private static final String MYBATIS_PLUS_SCHEMA_CONFIG = "mybatis-plus.global-config.db-config.schema";
private static String dbType;
private static String CURRENT_SCHEMA = null;
private static Environment environment;
@ -45,8 +37,6 @@ public class SqlHandler {
*/
public static void init(Environment env) {
environment = env;
String jdbcUrl = environment.getProperty("spring.datasource.url");
dbType = SqlHandler.extractDatabaseType(jdbcUrl);
}
/***
@ -54,9 +44,8 @@ public class SqlHandler {
* @return
*/
public static void initBootstrapSql(Class inst, Environment environment, String module){
if(dbType == null){
init(environment);
}
init(environment);
String dbType = getDbType();
String sqlPath = "META-INF/sql/init-"+module+"-"+dbType+".sql";
extractAndExecuteSqls(inst, sqlPath);
}
@ -75,24 +64,8 @@ public class SqlHandler {
* @return
*/
public static boolean checkIsTableExists(String sqlStatement){
// 获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) ContextHelper.getBean(SqlSessionFactory.class);
if(sqlSessionFactory == null){
logger.warn("无法获取SqlSessionFactory实例安装SQL将无法执行请手动安装");
return false;
}
sqlStatement = buildPureSqlStatement(sqlStatement);
try(SqlSession session = sqlSessionFactory.openSession(); Connection conn = session.getConnection(); PreparedStatement stmt = conn.prepareStatement(sqlStatement)){
ResultSet rs = stmt.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
if(meta.getColumnCount() > 0){
rs.close();
}
return true;
}
catch(Exception e){
return false;
}
return SqlExecutor.validateQuery(sqlStatement);
}
/***
@ -148,10 +121,10 @@ public class SqlHandler {
sqlStatement = clearComments(sqlStatement);
// 替换sqlStatement中的变量{SCHEMA}
if(sqlStatement.contains("${SCHEMA}")){
if(dbType.equals(DbType.SQL_SERVER.getDb())){
if(getDbType().equals(DbType.SQL_SERVER.getDb())){
sqlStatement = S.replace(sqlStatement, "${SCHEMA}", getSqlServerCurrentSchema());
}
else if(dbType.equals(DbType.ORACLE.getDb())){
else if(getDbType().equals(DbType.ORACLE.getDb())){
sqlStatement = S.replace(sqlStatement, "${SCHEMA}", getOracleCurrentSchema());
}
else{
@ -174,11 +147,11 @@ public class SqlHandler {
try{
boolean success = SqlExecutor.executeUpdate(sqlStatement, null);
if(success){
logger.info("初始化SQL执行完成: "+ S.substring(sqlStatement, 0, 60) + "...");
log.info("初始化SQL执行完成: "+ S.substring(sqlStatement, 0, 60) + "...");
}
}
catch (Exception e){
logger.error("初始化SQL执行异常请检查或手动执行。SQL => "+sqlStatement, e);
log.error("初始化SQL执行异常请检查或手动执行。SQL => "+sqlStatement, e);
}
}
return true;
@ -196,10 +169,10 @@ public class SqlHandler {
lines = IOUtils.readLines(is, "UTF-8");
}
catch (FileNotFoundException fe){
logger.warn("暂未发现数据库SQL: "+sqlPath + " 请参考其他数据库定义DDL手动初始化。");
log.warn("暂未发现数据库SQL: "+sqlPath + " 请参考其他数据库定义DDL手动初始化。");
}
catch (Exception e){
logger.warn("读取SQL文件异常: "+sqlPath, e);
log.warn("读取SQL文件异常: "+sqlPath, e);
}
return lines;
}
@ -248,20 +221,6 @@ public class SqlHandler {
return inputSql;
}
/**
* 提取数据库类型
* @param jdbcUrl
* @return
*/
public static String extractDatabaseType(String jdbcUrl){
DbType dbType = JdbcUtils.getDbType(jdbcUrl);
String dbName = dbType.getDb();
if(dbName.startsWith(DbType.SQL_SERVER.getDb()) && !dbName.equals(DbType.SQL_SERVER.getDb())){
dbName = DbType.SQL_SERVER.getDb();
}
return dbName;
}
//SQL Server查询当前schema
public static final String SQL_DEFAULT_SCHEMA = "SELECT DISTINCT default_schema_name FROM sys.database_principals where default_schema_name is not null AND name!='guest'";
/**
@ -326,7 +285,7 @@ public class SqlHandler {
}
}
catch(Exception e){
logger.error("获取SqlServer默认Schema异常: {}", e.getMessage());
log.error("获取SqlServer默认Schema异常: {}", e.getMessage());
}
return null;
}
@ -336,6 +295,6 @@ public class SqlHandler {
* @return
*/
public static String getDbType(){
return dbType;
return ContextHelper.getDatabaseType();
}
}

View File

@ -11,7 +11,7 @@ create table dictionary (
is_deletable BOOLEAN not null default FALSE,
is_editable BOOLEAN not null default TRUE,
is_deleted BOOLEAN not null default FALSE,
create_time DATE not null default CURRENT_TIMESTAMP,
create_time timestamp not null default CURRENT_TIMESTAMP,
constraint PK_dictionary primary key (id)
);
-- 添加备注

View File

@ -48,14 +48,14 @@ private List<Role> roleList;
### 1. 引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-core-spring-boot-starter:2.0.4")
compile("com.diboot:diboot-core-spring-boot-starter:2.0.5")
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
</dependency>
~~~
> * 使用diboot-devtools会自动引入diboot-core无需配置此依赖。
@ -103,7 +103,7 @@ public JsonResult getVOList(UserDto userDto, HttpServletRequest request) throws
//调用super.buildQueryWrapper(entityOrDto, request) 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行转换
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto, request);
//... 查询list
return new JsonResult(Status.OK, list);
return JsonResult.OK(list);
}
~~~

View File

@ -7,11 +7,11 @@
<parent>
<groupId>com.diboot</groupId>
<artifactId>diboot-root</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
</parent>
<artifactId>diboot-core</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
<packaging>jar</packaging>
<description>diboot core project</description>

View File

@ -10,13 +10,14 @@ import com.diboot.core.binding.binder.EntityBinder;
import com.diboot.core.binding.binder.EntityListBinder;
import com.diboot.core.binding.binder.FieldBinder;
import com.diboot.core.binding.parser.BindAnnotationGroup;
import com.diboot.core.binding.parser.BindAnnotationGroupCache;
import com.diboot.core.binding.parser.ConditionManager;
import com.diboot.core.binding.parser.FieldAnnotation;
import com.diboot.core.binding.parser.ParserCache;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.service.DictionaryService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,9 +38,24 @@ public class RelationsBinder {
private static final Logger log = LoggerFactory.getLogger(RelationsBinder.class);
/**
* 自动转换和绑定VO中的注解关联
* @param entityList
* @param voClass
* 自动转换和绑定单个VO中的注解关联禁止循环调用多个对象请调用convertAndBind(voList, voClass)
* @param voClass 需要转换的VO class
* @param <E>
* @param <VO>
* @return
*/
public static <E, VO> VO convertAndBind(E entity, Class<VO> voClass){
// 转换为VO列表
VO vo = BeanUtils.convert(entity, voClass);
// 自动绑定关联对象
bind(vo);
return vo;
}
/**
* 自动转换和绑定多个VO中的注解关联
* @param entityList 需要转换的VO list
* @param voClass VO class
* @param <E>
* @param <VO>
* @return
@ -53,7 +69,20 @@ public class RelationsBinder {
}
/**
* 自动绑定关联对象
* 自动绑定单个VO的关联对象禁止循环调用多个对象请调用bind(voList)
* @param vo 需要注解绑定的对象
* @return
* @throws Exception
*/
public static <VO> void bind(VO vo){
List<VO> voList = new ArrayList<>(1);
voList.add(vo);
bind(voList);
}
/**
* 自动绑定多个VO集合的关联对象
* @param voList 需要注解绑定的对象集合
* @return
* @throws Exception
*/
@ -63,7 +92,7 @@ public class RelationsBinder {
}
// 获取VO类
Class voClass = voList.get(0).getClass();
BindAnnotationGroup bindAnnotationGroup = BindAnnotationGroupCache.getBindAnnotationGroup(voClass);
BindAnnotationGroup bindAnnotationGroup = ParserCache.getBindAnnotationGroup(voClass);
if(bindAnnotationGroup.isNotEmpty()){
// 绑定数据字典
List<FieldAnnotation> dictAnnoList = bindAnnotationGroup.getBindDictAnnotations();
@ -104,7 +133,11 @@ public class RelationsBinder {
DictionaryService dictionaryService = (DictionaryService) ContextHelper.getBean(DictionaryService.class);
if(dictionaryService != null){
BindDict annotation = (BindDict) fieldAnno.getAnnotation();
dictionaryService.bindItemLabel(voList, fieldAnno.getFieldName(), annotation.field(), annotation.type());
String dictValueField = annotation.field();
if(V.isEmpty(dictValueField)){
dictValueField = S.replace(fieldAnno.getFieldName(), "Label", "");
}
dictionaryService.bindItemLabel(voList, fieldAnno.getFieldName(), dictValueField, annotation.type());
}
}

View File

@ -23,5 +23,5 @@ public @interface BindDict {
* 数据字典项取值字段
* @return
*/
String field();
String field() default "";
}

View File

@ -12,6 +12,7 @@ import com.diboot.core.util.S;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -52,6 +53,8 @@ public abstract class BaseBinder<T> {
*/
protected MiddleTable middleTable;
protected Class<T> referencedEntityClass;
/**
* join连接条件指定当前VO的取值方法和关联entity的取值方法
* @param annoObjectFkGetter 当前VO的取值方法
@ -77,27 +80,27 @@ public abstract class BaseBinder<T> {
}
public BaseBinder<T> andEQ(String fieldName, Object value){
queryWrapper.eq(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.eq(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andNE(String fieldName, Object value){
queryWrapper.ne(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.ne(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andGT(String fieldName, Object value){
queryWrapper.gt(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.gt(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andGE(String fieldName, Object value){
queryWrapper.ge(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.ge(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andLT(String fieldName, Object value){
queryWrapper.lt(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.lt(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andLE(String fieldName, Object value){
queryWrapper.le(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.le(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andIsNotNull(String fieldName){
@ -109,11 +112,11 @@ public abstract class BaseBinder<T> {
return this;
}
public BaseBinder<T> andBetween(String fieldName, Object begin, Object end){
queryWrapper.between(S.toSnakeCase(fieldName), formatValue(begin), formatValue(end));
queryWrapper.between(S.toSnakeCase(fieldName), formatValue(fieldName, begin), formatValue(fieldName, end));
return this;
}
public BaseBinder<T> andLike(String fieldName, String value){
queryWrapper.like(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.like(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andIn(String fieldName, Collection valueList){
@ -125,11 +128,11 @@ public abstract class BaseBinder<T> {
return this;
}
public BaseBinder<T> andNotBetween(String fieldName, Object begin, Object end){
queryWrapper.notBetween(S.toSnakeCase(fieldName), formatValue(begin), formatValue(end));
queryWrapper.notBetween(S.toSnakeCase(fieldName), formatValue(fieldName, begin), formatValue(fieldName, end));
return this;
}
public BaseBinder<T> andNotLike(String fieldName, String value){
queryWrapper.notLike(S.toSnakeCase(fieldName), formatValue(value));
queryWrapper.notLike(S.toSnakeCase(fieldName), formatValue(fieldName, value));
return this;
}
public BaseBinder<T> andApply(String applySql){
@ -209,13 +212,23 @@ public abstract class BaseBinder<T> {
/**
* 格式化条件值
* @param value
* @param fieldName 属性名
* @param value
* @return
*/
private Object formatValue(Object value){
private Object formatValue(String fieldName, Object value){
if(value instanceof String && S.contains((String)value, "'")){
value = S.replace((String)value, "'", "");
return S.replace((String)value, "'", "");
}
// 转型
if(this.referencedEntityClass != null){
Field field = BeanUtils.extractField(this.referencedEntityClass, S.toLowerCaseCamel(fieldName));
if(field != null){
return BeanUtils.convertValueToFieldType(value, field);
}
}
return value;
}
}

View File

@ -42,6 +42,7 @@ public class EntityBinder<T> extends BaseBinder<T> {
this.referencedService = referencedService;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
}
/***

View File

@ -31,6 +31,7 @@ public class EntityListBinder<T> extends EntityBinder<T> {
this.referencedService = serviceInstance;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
}
@Override

View File

@ -35,6 +35,7 @@ public class FieldBinder<T> extends BaseBinder<T> {
this.referencedService = serviceInstance;
this.annoObjectList = voList;
this.queryWrapper = new QueryWrapper<T>();
this.referencedEntityClass = BeanUtils.getGenericityClass(referencedService, 1);
}
/***

View File

@ -12,7 +12,7 @@ import java.util.List;
/**
* VO绑定注解的归类分组用于缓存解析后的结果
* @author mazc@dibo.ltd<br>
* @version 1.0<br>
* @version 2.0<br>
* @date 2019/04/03 <br>
*/
public class BindAnnotationGroup {

View File

@ -10,6 +10,7 @@ import net.sf.jsqlparser.schema.Column;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -68,7 +69,10 @@ public class ConditionManager {
// 解析中间表关联
String tableName = extractMiddleTableName(expressionList);
if(tableName != null){
parseMiddleTable(binder, expressionList, tableName);
List<Expression> additionalExpress = parseMiddleTable(binder, expressionList, tableName);
if(V.notEmpty(additionalExpress)){
parseDirectRelation(binder, additionalExpress);
}
}
else{
parseDirectRelation(binder, expressionList);
@ -196,11 +200,13 @@ public class ConditionManager {
* @param expressionList
* @return
*/
private static <T> void parseMiddleTable(BaseBinder<T> binder, List<Expression> expressionList, String tableName) {
private static <T> List<Expression> parseMiddleTable(BaseBinder<T> binder, List<Expression> expressionList, String tableName) {
// 单一条件不是中间表条件
if(expressionList.size() <= 1){
return;
return expressionList;
}
// 非中间表的附加条件表达式
List<Expression> additionalExpressions = new ArrayList<>();
// 提取到表
MiddleTable middleTable = new MiddleTable(tableName);
// 中间表两边边连接字段
@ -253,8 +259,13 @@ public class ConditionManager {
}
else{ // equals附加条件暂只支持列在左侧 department.level=1
String leftExpression = express.getLeftExpression().toString();
if(leftExpression != null && leftExpression.startsWith(tableName+".")){
middleTable.addAdditionalCondition(removeLeftAlias(operator.toString()));
if(leftExpression != null){
if(leftExpression.startsWith(tableName+".")){
middleTable.addAdditionalCondition(removeLeftAlias(operator.toString()));
}
else{
additionalExpressions.add(express);
}
}
}
}
@ -296,12 +307,18 @@ public class ConditionManager {
LikeExpression express = (LikeExpression)operator;
leftExpression = express.getLeftExpression().toString();
}
if(leftExpression != null && leftExpression.startsWith(tableName+".")){
middleTable.addAdditionalCondition(removeLeftAlias(operator.toString()));
if(leftExpression != null){
if(leftExpression.startsWith(tableName+".")){
middleTable.addAdditionalCondition(removeLeftAlias(operator.toString()));
}
else{
additionalExpressions.add(operator);
}
}
}
}
binder.withMiddleTable(middleTable);
return additionalExpressions;
}
/**

View File

@ -6,7 +6,7 @@ import java.lang.annotation.Annotation;
* 字段名与注解的包装对象关系 <br>
*
* @author mazc@dibo.ltd<br>
* @version 1.0<br>
* @version 2.0<br>
* @date 2019/04/04 <br>
*/
public class FieldAnnotation{

View File

@ -1,5 +1,6 @@
package com.diboot.core.binding.parser;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.util.S;
import com.diboot.core.util.SqlExecutor;
@ -14,7 +15,7 @@ import java.util.Map;
/**
* 中间表
* @author mazc@dibo.ltd<br>
* @version 1.0<br>
* @version 2.0<br>
* @date 2019/04/01 <br>
*/
public class MiddleTable {
@ -128,9 +129,20 @@ public class MiddleTable {
.append(" WHERE ").append(this.equalsToAnnoObjectFKColumn).append(" IN (");
String params = S.repeat("?", ",", annoObjectForeignKeyList.size());
sb.append(params).append(")");
// 添加删除标记
boolean appendDeleteFlag = true;
if(this.additionalConditions != null){
for(String condition : this.additionalConditions){
sb.append(" AND (").append(condition).append(")");
if(S.containsIgnoreCase(condition, "is_" + Cons.FieldName.deleted.name())){
appendDeleteFlag = false;
}
}
}
// 如果需要删除
if(appendDeleteFlag){
if(ParserCache.hasDeletedColumn(this.table)){
sb.append(" AND is_deleted = ").append(BaseConfig.getActiveFlagValue());
}
}
return sb.toString();

View File

@ -1,6 +1,7 @@
package com.diboot.core.binding.parser;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.SqlExecutor;
import com.diboot.core.util.V;
import java.lang.annotation.Annotation;
@ -14,14 +15,18 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* VO对象中的绑定注解 缓存管理类
* @author mazc@dibo.ltd<br>
* @version 1.0<br>
* @version 2.0<br>
* @date 2019/04/03 <br>
*/
public class BindAnnotationGroupCache {
public class ParserCache {
/**
* VO类-绑定注解缓存
*/
private static Map<Class, BindAnnotationGroup> allVoBindAnnotationCacheMap = new ConcurrentHashMap<>();
/**
* 中间表是否包含is_deleted列 缓存
*/
private static Map<String, Boolean> middleTableHasDeletedCacheMap = new ConcurrentHashMap<>();
/**
* 获取指定class对应的Bind相关注解
@ -62,4 +67,16 @@ public class BindAnnotationGroupCache {
return group;
}
/**
* 是否有is_deleted列
* @return
*/
public static boolean hasDeletedColumn(String middleTable){
if(middleTableHasDeletedCacheMap.containsKey(middleTable)){
return middleTableHasDeletedCacheMap.get(middleTable);
}
boolean hasColumn = SqlExecutor.validateQuery("SELECT is_deleted FROM "+middleTable);
middleTableHasDeletedCacheMap.put(middleTable, hasColumn);
return hasColumn;
}
}

View File

@ -92,4 +92,16 @@ public class BaseConfig {
public static int getBatchSize() {
return 1000;
}
private static String ACTIVE_FLAG_VALUE = null;
/**
* 获取有效记录的标记值 0
* @return
*/
public static String getActiveFlagValue(){
if(ACTIVE_FLAG_VALUE == null){
ACTIVE_FLAG_VALUE = getProperty("mybatis-plus.global-config.db-config.logic-not-delete-value", "0");
}
return ACTIVE_FLAG_VALUE;
}
}

View File

@ -22,11 +22,6 @@ import java.util.*;
public class BaseController {
private static final Logger log = LoggerFactory.getLogger(BaseController.class);
/**
* ID参数名
*/
protected static final String PARAM_ID = Cons.FieldName.id.name();
/***
* 构建查询QueryWrapper (根据BindQuery注解构建相应的查询条件)
* @param entityOrDto Entity对象或者DTO对象 (属性若无BindQuery注解默认构建为为EQ相等条件)
@ -140,8 +135,10 @@ public class BaseController {
String paramName = (String) paramNames.nextElement();
String[] values = request.getParameterValues(paramName);
if(V.notEmpty(values)){
if(values.length == 1 && V.notEmpty(values[0])){
result.put(paramName, values[0]);
if(values.length == 1){
if(V.notEmpty(values[0])){
result.put(paramName, values[0]);
}
}
else{
// 多个值需传递到后台SQL的in语句

View File

@ -73,7 +73,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 查询当前页的数据
List<VO> voList = getService().getViewObjectList(queryWrapper, pagination, getVOClass());
// 返回结果
return new JsonResult(Status.OK, voList).bindPagination(pagination);
return JsonResult.OK(voList).bindPagination(pagination);
}
/**
@ -86,7 +86,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 查询当前页的数据
List entityList = getService().getEntityList(queryWrapper);
// 返回结果
return new JsonResult(Status.OK, entityList);
return JsonResult.OK(entityList);
}
/***
@ -101,7 +101,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 查询当前页的数据
List entityList = getService().getEntityList(queryWrapper, pagination);
// 返回结果
return new JsonResult(Status.OK, entityList).bindPagination(pagination);
return JsonResult.OK(entityList).bindPagination(pagination);
}
/***
@ -114,7 +114,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 执行创建资源前的操作
String validateResult = this.beforeCreate(entity);
if (validateResult != null) {
return new JsonResult(Status.FAIL_VALIDATION, validateResult);
return JsonResult.FAIL_VALIDATION(validateResult);
}
// 执行保存操作
boolean success = getService().createEntity(entity);
@ -123,7 +123,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
this.afterCreated(entity);
// 组装返回结果
Map<String, Object> data = buildPKDataMap(entity);
return new JsonResult(Status.OK, data);
return JsonResult.OK(data);
} else {
log.warn("创建操作未成功entity=" + entity.getClass().getSimpleName());
// 组装返回结果
@ -152,14 +152,14 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 执行更新资源前的操作
String validateResult = this.beforeUpdate(entity);
if (validateResult != null) {
return new JsonResult(Status.FAIL_VALIDATION, validateResult);
return JsonResult.FAIL_VALIDATION(validateResult);
}
// 执行保存操作
boolean success = getService().updateEntity(entity);
if (success) {
// 执行更新成功后的操作
this.afterUpdated(entity);
return new JsonResult(Status.OK);
return JsonResult.OK();
} else {
log.warn("更新操作失败,{}:{}", entity.getClass().getSimpleName(), entity.getId());
// 返回操作结果
@ -190,7 +190,7 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
// 执行更新成功后的操作
this.afterDeleted(entity);
log.info("删除操作成功,{}:{}", entity.getClass().getSimpleName(), id);
return new JsonResult(Status.OK);
return JsonResult.OK();
} else {
log.warn("删除操作未成功,{}:{}", entity.getClass().getSimpleName(), id);
return new JsonResult(Status.FAIL_OPERATION);
@ -243,53 +243,53 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
//============= 供子类继承重写的方法 =================
/***
* 创建前的相关处理
* @param entity
* @param entityOrDto
* @return
*/
protected String beforeCreate(E entity) throws Exception {
protected String beforeCreate(Object entityOrDto) throws Exception {
return null;
}
/***
* 创建成功后的相关处理
* @param entity
* @param entityOrDto
* @return
*/
protected void afterCreated(E entity) throws Exception {
protected void afterCreated(Object entityOrDto) throws Exception {
}
/***
* 更新前的相关处理
* @param entity
* @param entityOrDto
* @return
*/
protected String beforeUpdate(E entity) throws Exception {
protected String beforeUpdate(Object entityOrDto) throws Exception {
return null;
}
/***
* 更新成功后的相关处理
* @param entity
* @param entityOrDto
* @return
*/
protected void afterUpdated(E entity) throws Exception {
protected void afterUpdated(Object entityOrDto) throws Exception {
}
/***
* 是否有删除权限如不可删除返回错误提示信息 Status.FAIL_NO_PERMISSION.label()
* @param entity
* @param entityOrDto
* @return
*/
protected String beforeDelete(E entity) throws Exception{
protected String beforeDelete(Object entityOrDto) throws Exception{
return null;
}
/***
* 删除成功后的相关处理
* @param entity
* @param entityOrDto
* @return
*/
protected void afterDeleted(E entity) throws Exception {
protected void afterDeleted(Object entityOrDto) throws Exception {
}
/***
@ -333,6 +333,9 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
protected Class<E> getEntityClass(){
if(this.entityClass == null){
this.entityClass = BeanUtils.getGenericityClass(this, 0);
if(this.entityClass == null) {
log.warn("无法从 {} 类定义中获取泛型类entityClass", this.getClass().getName());
}
}
return this.entityClass;
}
@ -344,6 +347,9 @@ public class BaseCrudRestController<E extends BaseEntity, VO extends Serializabl
protected Class<VO> getVOClass(){
if(this.voClasss == null){
this.voClasss = BeanUtils.getGenericityClass(this, 1);
if(this.voClasss == null) {
log.warn("无法从 {} 类定义中获取泛型类voClasss", this.getClass().getName());
}
}
return this.voClasss;
}

View File

@ -2,6 +2,9 @@ package com.diboot.core.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.*;
import com.diboot.core.config.Cons;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.JSON;
import lombok.Getter;
import lombok.Setter;
@ -52,6 +55,19 @@ public abstract class BaseEntity implements Serializable {
return JSON.toMap(jsonStr);
}
/**
* 获取主键值
* @return
*/
@JSONField(serialize = false)
public Object getPrimaryKey(){
String pk = ContextHelper.getPrimaryKey(this.getClass());
if(Cons.FieldName.id.name().equals(pk)){
return getId();
}
return BeanUtils.getProperty(this, pk);
}
/**
* Entity对象转为String
* @return

View File

@ -6,7 +6,7 @@ import java.util.*;
/**
* 可校验的List包装类用于接收List并绑定校验
* @author : uu
* @version : v2.0.4
* @version : v2.0.5
* @Date 2020-01-10
*/
public class ValidList<T> implements List<T> {

View File

@ -55,6 +55,14 @@ public class BusinessException extends RuntimeException {
this.status = status;
}
/**
* 自定义内容提示
* @param msg
*/
public BusinessException(String msg) {
super( msg);
}
/**
* 自定义内容提示
* @param status

View File

@ -67,6 +67,10 @@ public class DefaultExceptionHandler {
BusinessException be = (BusinessException)e;
map = be.toMap();
}
else if(e.getCause() instanceof BusinessException){
BusinessException be = (BusinessException)e.getCause();
map = be.toMap();
}
else{
map = new HashMap<>();
map.put("code", status.value());
@ -108,9 +112,13 @@ public class DefaultExceptionHandler {
* @param request
* @return
*/
private boolean isJsonRequest(HttpServletRequest request){
return S.contains(request.getHeader("Accept"),"json")
|| S.contains(request.getHeader("content-type"), "json");
protected boolean isJsonRequest(HttpServletRequest request){
if("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))){
return true;
}
return S.containsIgnoreCase(request.getHeader("Accept"),"json")
|| S.containsIgnoreCase(request.getHeader("content-type"), "json")
|| S.containsIgnoreCase(request.getContentType(), "json");
}
/**

View File

@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.diboot.core.binding.binder.EntityBinder;
import com.diboot.core.binding.binder.EntityListBinder;
import com.diboot.core.binding.binder.FieldBinder;
import com.diboot.core.util.IGetter;
import com.diboot.core.util.ISetter;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Pagination;
@ -49,6 +51,15 @@ public interface BaseService<T> {
*/
boolean createEntities(Collection<T> entityList);
/**
* 添加entity 其关联子项entities
* @param entity 主表entity
* @param relatedEntities 关联表entities
* @param relatedEntitySetter 关联Entity类的setter
* @return
*/
<RE, R> boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
/**
* 更新Entity实体
* @param entity
@ -71,6 +82,13 @@ public interface BaseService<T> {
*/
boolean updateEntity(Wrapper updateWrapper);
/**
* 批量更新entity
* @param entityList
* @return
*/
boolean updateEntities(Collection<T> entityList);
/***
* 创建或更新entityentity.id存在则新建否则更新
* @param entity
@ -85,6 +103,24 @@ public interface BaseService<T> {
*/
boolean createOrUpdateEntities(Collection entityList);
/**
* 添加entity 其关联子项entities
* @param entity 主表entity
* @param relatedEntities 关联表entities
* @param relatedEntitySetter 关联Entity类的setter
* @return
*/
<RE,R> boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
/**
* 删除entity 其关联子项entities
* @param id 待删除entity的主键
* @param relatedEntityClass 待删除关联Entity类
* @param relatedEntitySetter 待删除类的setter方法
* @return
*/
<RE,R> boolean deleteEntityAndRelatedEntities(Serializable id, Class<RE> relatedEntityClass, ISetter<RE, R> relatedEntitySetter);
/**
* 根据主键删除实体
* @param id 主键
@ -154,6 +190,14 @@ public interface BaseService<T> {
*/
T getSingleEntity(Wrapper queryWrapper);
/**
* 是否存在符合条件的记录
* @param getterFn entity的getter方法
* @param value 需要检查的值
* @return
*/
boolean exists(IGetter<T> getterFn, Object value);
/**
* 是否存在符合条件的记录
* @param queryWrapper

View File

@ -1,5 +1,6 @@
package com.diboot.core.service.impl;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -13,10 +14,7 @@ import com.diboot.core.config.BaseConfig;
import com.diboot.core.config.Cons;
import com.diboot.core.mapper.BaseCrudMapper;
import com.diboot.core.service.BaseService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.util.*;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Pagination;
import org.slf4j.Logger;
@ -61,14 +59,48 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createEntities(Collection entityList){
if(V.isEmpty(entityList)){
warning("createEntities", "参数entityList为空!");
public <RE, R> boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter) {
boolean success = createEntity(entity);
if(!success){
log.warn("新建Entity失败: {}", entity.toString());
return false;
}
// 批量插入
return super.saveBatch(entityList, BaseConfig.getBatchSize());
if(V.isEmpty(relatedEntities)){
return success;
}
Class relatedEntityClass = BeanUtils.getTargetClass(relatedEntities.get(0));
// 获取关联对象对应的Service
BaseService relatedEntityService = ContextHelper.getBaseServiceByEntity(relatedEntityClass);
if(relatedEntityService == null){
log.error("未能识别到Entity: {} 的Service实现请检查", relatedEntityClass.getName());
return false;
}
// 获取主键
Object pkValue = getPrimaryKeyValue(entity);
String attributeName = BeanUtils.convertToFieldName(relatedEntitySetter);
// 填充关联关系
relatedEntities.stream().forEach(relatedEntity->{
BeanUtils.setProperty(relatedEntity, attributeName, pkValue);
});
return relatedEntityService.createEntities(relatedEntities);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createEntities(Collection<T> entityList){
if(V.isEmpty(entityList)){
return false;
}
if(DbType.SQL_SERVER.getDb().equalsIgnoreCase(ContextHelper.getDatabaseType())){
for(T entity : entityList){
createEntity(entity);
}
return true;
}
else{
// 批量插入
return super.saveBatch(entityList, BaseConfig.getBatchSize());
}
}
@Override
@ -89,6 +121,12 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return success;
}
@Override
public boolean updateEntities(Collection<T> entityList) {
boolean success = super.updateBatchById(entityList);
return success;
}
@Override
public boolean createOrUpdateEntity(T entity) {
boolean success = super.saveOrUpdate(entity);
@ -106,6 +144,95 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return super.saveOrUpdateBatch(entityList, BaseConfig.getBatchSize());
}
@Override
@Transactional(rollbackFor = Exception.class)
public <RE,R> boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE,R> relatedEntitySetter) {
boolean success = updateEntity(entity);
if(!success){
log.warn("更新Entity失败: {}", entity.toString());
return false;
}
// 获取关联entity的类
Class relatedEntityClass = null;
if(V.notEmpty(relatedEntities)){
relatedEntityClass = BeanUtils.getTargetClass(relatedEntities.get(0));
}
else{
try{
relatedEntityClass = Class.forName(BeanUtils.getSerializedLambda(relatedEntitySetter).getImplClass().replaceAll("/", "."));
}
catch (Exception e){
log.warn("无法识别关联Entity的Class", e.getMessage());
return false;
}
}
// 获取关联对象对应的Service
BaseService relatedEntityService = ContextHelper.getBaseServiceByEntity(relatedEntityClass);
if(relatedEntityService == null){
log.error("未能识别到Entity: {} 的Service实现请检查", relatedEntityClass.getName());
return false;
}
// 获取主键
Object pkValue = getPrimaryKeyValue(entity);
String attributeName = BeanUtils.convertToFieldName(relatedEntitySetter);
//获取原 关联entity list
QueryWrapper<RE> queryWrapper = new QueryWrapper();
queryWrapper.eq(S.toSnakeCase(attributeName), pkValue);
List<RE> oldRelatedEntities = relatedEntityService.getEntityList(queryWrapper);
// 遍历更新关联对象
Set relatedEntityIds = new HashSet();
if(V.notEmpty(relatedEntities)){
// 新建 修改 删除
List<RE> newRelatedEntities = new ArrayList<>();
for(RE relatedEntity : relatedEntities){
BeanUtils.setProperty(relatedEntity, attributeName, pkValue);
Object relPkValue = getPrimaryKeyValue(relatedEntity);
if(V.notEmpty(relPkValue)){
relatedEntityService.updateEntity(relatedEntity);
}
else{
newRelatedEntities.add(relatedEntity);
}
relatedEntityIds.add(relPkValue);
}
relatedEntityService.createEntities(newRelatedEntities);
}
// 遍历已有关联对象
if(V.notEmpty(oldRelatedEntities)){
List deleteRelatedEntityIds = new ArrayList();
for(RE relatedEntity : oldRelatedEntities){
Object relPkValue = getPrimaryKeyValue(relatedEntity);
if(!relatedEntityIds.contains(relPkValue)){
deleteRelatedEntityIds.add(relPkValue);
}
}
relatedEntityService.deleteEntities(deleteRelatedEntityIds);
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public <RE,R> boolean deleteEntityAndRelatedEntities(Serializable id, Class<RE> relatedEntityClass, ISetter<RE,R> relatedEntitySetter) {
boolean success = deleteEntity(id);
if(!success){
log.warn("删除Entity失败: {}",id);
return false;
}
// 获取关联对象对应的Service
BaseService relatedEntityService = ContextHelper.getBaseServiceByEntity(relatedEntityClass);
if(relatedEntityService == null){
log.error("未能识别到Entity: {} 的Service实现请检查", relatedEntityClass.getClass().getName());
return false;
}
// 获取主键的关联属性
String attributeName = BeanUtils.convertToFieldName(relatedEntitySetter);
QueryWrapper<RE> queryWrapper = new QueryWrapper<RE>().eq(S.toSnakeCase(attributeName), id);
// 删除关联子表数据
return relatedEntityService.deleteEntities(queryWrapper);
}
@Override
public boolean deleteEntity(Serializable id) {
return super.removeById(id);
@ -171,6 +298,13 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return null;
}
@Override
public boolean exists(IGetter<T> getterFn, Object value) {
QueryWrapper<T> queryWrapper = new QueryWrapper();
queryWrapper.eq(BeanUtils.convertToFieldName(getterFn), value);
return exists(queryWrapper);
}
@Override
public boolean exists(Wrapper queryWrapper) {
List<T> entityList = getEntityListLimit(queryWrapper, 1);
@ -231,10 +365,22 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
String[] keyValueArray = sqlSelect.split(Cons.SEPARATOR_COMMA);
List<KeyValue> keyValueList = new ArrayList<>(mapList.size());
for(Map<String, Object> map : mapList){
if(map.get(keyValueArray[0]) != null){
KeyValue kv = new KeyValue(S.valueOf(map.get(keyValueArray[0])), map.get(keyValueArray[1]));
String key = keyValueArray[0], value = keyValueArray[1], ext = null;
// 兼容oracle大写
if(map.containsKey(key) == false && map.containsKey(key.toUpperCase())){
key = key.toUpperCase();
}
if(map.containsKey(value) == false && map.containsKey(value.toUpperCase())){
value = value.toUpperCase();
}
if(map.containsKey(key)){
KeyValue kv = new KeyValue(S.valueOf(map.get(key)), map.get(value));
if(keyValueArray.length > 2){
kv.setExt(map.get(keyValueArray[2]));
ext = keyValueArray[2];
if(map.containsKey(ext) == false && map.containsKey(ext.toUpperCase())){
ext = ext.toUpperCase();
}
kv.setExt(map.get(ext));
}
keyValueList.add(kv);
}
@ -315,6 +461,16 @@ public class BaseServiceImpl<M extends BaseCrudMapper<T>, T> extends ServiceImpl
return (Page<T>)pagination.toIPage();
}
/**
* 获取主键值
* @param entity
* @return
*/
private Object getPrimaryKeyValue(Object entity){
String pk = ContextHelper.getPrimaryKey(entity.getClass());
return BeanUtils.getProperty(entity, pk);
}
/***
* 打印警告信息
* @param method

View File

@ -87,23 +87,19 @@ public class DictionaryServiceImpl extends BaseServiceImpl<DictionaryMapper, Dic
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createDictAndChildren(DictionaryVO dictVO) {
Dictionary dictionary = (Dictionary)dictVO;
Dictionary dictionary = dictVO;
if(!super.createEntity(dictionary)){
log.warn("新建数据字典定义失败type="+dictVO.getType());
return false;
}
List<Dictionary> children = dictVO.getChildren();
if(V.notEmpty(children)){
boolean success = true;
for(Dictionary dict : children){
dict.setParentId(dictionary.getId());
dict.setType(dictionary.getType());
boolean insertOK = super.createEntity(dict);
if(!insertOK){
log.warn("dictionary插入数据字典失败请检查");
success = false;
}
}
// 批量保存
boolean success = super.createEntities(children);
if(!success){
String errorMsg = "新建数据字典子项失败type="+dictVO.getType();
log.warn(errorMsg);
@ -117,7 +113,7 @@ public class DictionaryServiceImpl extends BaseServiceImpl<DictionaryMapper, Dic
@Transactional(rollbackFor = Exception.class)
public boolean updateDictAndChildren(DictionaryVO dictVO) {
//将DictionaryVO转化为Dictionary
Dictionary dictionary = (Dictionary)dictVO;
Dictionary dictionary = dictVO;
if(!super.updateEntity(dictionary)){
log.warn("更新数据字典定义失败type="+dictVO.getType());
return false;
@ -151,7 +147,7 @@ public class DictionaryServiceImpl extends BaseServiceImpl<DictionaryMapper, Dic
if(!dictItemIds.contains(dict.getId())){
if(!super.deleteEntity(dict.getId())){
log.warn("删除子数据字典失败itemName="+dict.getItemName());
throw new RuntimeException();
throw new BusinessException(Status.FAIL_EXCEPTION, "删除字典子项异常");
}
}
}

View File

@ -3,7 +3,9 @@ package com.diboot.core.util;
import com.diboot.core.config.Cons;
import com.diboot.core.entity.BaseEntity;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
@ -12,13 +14,11 @@ import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.ReflectionUtils;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@ -104,7 +104,7 @@ public class BeanUtils {
}
}
catch (Exception e){
log.warn("对象转换异常, class="+clazz.getName());
log.warn("对象转换异常, class: {}, error: {}", clazz.getName(), e.getMessage());
}
return resultList;
}
@ -115,70 +115,22 @@ public class BeanUtils {
* @param propMap
*/
public static void bindProperties(Object model, Map<String, Object> propMap){
try{// 获取类属性
BeanInfo beanInfo = Introspector.getBeanInfo(model.getClass());
// JavaBean 对象的属性赋值
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor descriptor : propertyDescriptors) {
String propertyName = descriptor.getName();
if (!propMap.containsKey(propertyName)){
continue;
}
Object value = propMap.get(propertyName);
Class type = descriptor.getWriteMethod().getParameterTypes()[0];
Object[] args = new Object[1];
String fieldType = type.getName();
// 类型不一致需转型
if(value != null && !value.getClass().getTypeName().equals(fieldType)){
if(value instanceof String){
// String to Date
if(fieldType.equals(Date.class.getName())){
args[0] = D.fuzzyConvert((String)value);
}
// Map中的String型转换为其他型
else if(fieldType.equals(Boolean.class.getName())){
args[0] = V.isTrue((String)value);
}
else if (fieldType.equals(Integer.class.getName()) || "int".equals(fieldType)) {
args[0] = Integer.parseInt((String)value);
}
else if (fieldType.equals(Long.class.getName())) {
args[0] = Long.parseLong((String)value);
}
else if (fieldType.equals(Double.class.getName())) {
args[0] = Double.parseDouble((String)value);
}
else if (fieldType.equals(Float.class.getName())) {
args[0] = Float.parseFloat((String)value);
}
else{
args[0] = value;
log.warn("类型不一致,暂无法自动绑定,请手动转型一致后调用!字段类型: {} vs {} ", value.getClass().getTypeName(), fieldType);
}
}
// Integer 向上转型为 Long 绑定
else if(value.getClass().getTypeName().equals(Integer.class.getName()) && fieldType.equals(Long.class.getName())){
Integer intValue = (Integer)value;
args[0] = intValue.longValue();
}
// Float 向上转型为 Double 绑定
else if(value.getClass().getTypeName().equals(Float.class.getName()) && fieldType.equals(Double.class.getName())){
Float floatValue = (Float)value;
args[0] = floatValue.doubleValue();
}
else{
args[0] = value;
log.warn("类型不一致,暂无法自动绑定,请手动转型一致后调用! {} vs {} ", value.getClass().getTypeName(), fieldType);
}
}
else{
args[0] = value;
}
descriptor.getWriteMethod().invoke(model, args);
}
if(V.isEmpty(propMap)){
return;
}
catch (Exception e){
log.warn("复制Map属性到Model异常: " + e.getMessage(), e);
List<Field> fields = extractAllFields(model.getClass());
Map<String, Field> fieldNameMaps = convertToStringKeyObjectMap(fields, "name");
for(Map.Entry<String, Object> entry : propMap.entrySet()){
Field field = fieldNameMaps.get(entry.getKey());
if(field != null){
try{
Object value = convertValueToFieldType(entry.getValue(), field);
setProperty(model, entry.getKey(), value);
}
catch (Exception e){
log.warn("复制属性{}.{}异常: {}", model.getClass().getSimpleName(), entry.getKey(), e.getMessage());
}
}
}
}
@ -218,6 +170,41 @@ public class BeanUtils {
wrapper.setPropertyValue(field, value);
}
/**
* 转换为field对应的类型
* @param value
* @param field
* @return
*/
public static Object convertValueToFieldType(Object value, Field field){
String type = field.getGenericType().getTypeName();
if(value.getClass().getName().equals(type)){
return value;
}
if(Integer.class.getName().equals(type)){
return Integer.parseInt(S.valueOf(value));
}
else if(Long.class.getName().equals(type)){
return Long.parseLong(S.valueOf(value));
}
else if(Double.class.getName().equals(type)){
return Double.parseDouble(S.valueOf(value));
}
else if(BigDecimal.class.getName().equals(type)){
return new BigDecimal(S.valueOf(value));
}
else if(Float.class.getName().equals(type)){
return Float.parseFloat(S.valueOf(value));
}
else if(Boolean.class.getName().equals(type)){
return V.isTrue(S.valueOf(value));
}
else if(type.contains(Date.class.getSimpleName())){
return D.fuzzyConvert(S.valueOf(value));
}
return value;
}
/***
* Key-Object对象Map
* @param allLists
@ -334,10 +321,14 @@ public class BeanUtils {
}
// 提取所有的top level对象
List<T> topLevelModels = new ArrayList();
for(T model : allNodes){
Object parentId = getProperty(model, Cons.FieldName.parentId.name());
for(T node : allNodes){
Object parentId = getProperty(node, Cons.FieldName.parentId.name());
if(parentId == null || V.fuzzyEqual(parentId, rootNodeId)){
topLevelModels.add(model);
topLevelModels.add(node);
}
Object nodeId = getProperty(node, Cons.FieldName.id.name());
if(V.equals(nodeId, parentId)){
throw new BusinessException(Status.WARN_PERFORMANCE_ISSUE, "parentId关联自身请检查" + node.getClass().getSimpleName()+":"+nodeId);
}
}
if(V.isEmpty(topLevelModels)){
@ -631,6 +622,16 @@ public class BeanUtils {
return ReflectionUtils.findField(clazz, fieldName);
}
/**
* 获取目标类
* @param instance
* @return
*/
public static Class getTargetClass(Object instance){
Class targetClass = (instance instanceof Class)? (Class)instance : AopUtils.getTargetClass(instance);
return targetClass;
}
/**
* 从实例中获取目标对象的泛型定义类class
* @param instance 对象实例
@ -638,7 +639,7 @@ public class BeanUtils {
* @return
*/
public static Class getGenericityClass(Object instance, int index){
Class hostClass = (instance instanceof Class)? (Class)instance : AopUtils.getTargetClass(instance);
Class hostClass = getTargetClass(instance);
ResolvableType resolvableType = ResolvableType.forClass(hostClass).getSuperType();
ResolvableType[] types = resolvableType.getGenerics();
if(V.isEmpty(types) || index >= types.length){
@ -647,7 +648,7 @@ public class BeanUtils {
if(V.notEmpty(types) && types.length > index){
return types[index].resolve();
}
log.warn("无法从 {} 类定义中获取泛型类{}", hostClass.getName(), index);
log.debug("无法从 {} 类定义中获取泛型类{}", hostClass.getName(), index);
return null;
}
@ -690,7 +691,7 @@ public class BeanUtils {
* @param fn
* @return
*/
private static SerializedLambda getSerializedLambda(Serializable fn){
public static SerializedLambda getSerializedLambda(Serializable fn){
SerializedLambda lambda = null;
try{
Method method = fn.getClass().getDeclaredMethod("writeReplace");

View File

@ -1,16 +1,20 @@
package com.diboot.core.util;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
import com.diboot.core.config.Cons;
import com.diboot.core.service.BaseService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.web.context.ContextLoader;
@ -49,6 +53,10 @@ public class ContextHelper implements ApplicationContextAware {
* 存储主键字段非id的Entity
*/
private static Map<String, String> PK_NID_ENTITY_CACHE = new ConcurrentHashMap<>();
/**
* 数据库类型
*/
private static String DATABASE_TYPE = null;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@ -132,6 +140,7 @@ public class ContextHelper implements ApplicationContextAware {
* @param entity
* @return
*/
@Deprecated
public static IService getIServiceByEntity(Class entity){
if(ENTITY_SERVICE_CACHE == null){
ENTITY_SERVICE_CACHE = new ConcurrentHashMap<>();
@ -203,4 +212,47 @@ public class ContextHelper implements ApplicationContextAware {
}
return PK_NID_ENTITY_CACHE.get(entity.getName());
}
/***
* 获取JdbcUrl
* @return
*/
public static String getJdbcUrl() {
Environment environment = getApplicationContext().getEnvironment();
String jdbcUrl = environment.getProperty("spring.datasource.url");
if(jdbcUrl == null){
String master = environment.getProperty("spring.datasource.dynamic.primary");
jdbcUrl = environment.getProperty("spring.datasource.dynamic.datasource."+master+".url");
}
return jdbcUrl;
}
/**
* 获取数据库类型
* @return
*/
public static String getDatabaseType(){
if(DATABASE_TYPE != null){
return DATABASE_TYPE;
}
String jdbcUrl = getJdbcUrl();
if(jdbcUrl != null){
DbType dbType = JdbcUtils.getDbType(jdbcUrl);
DATABASE_TYPE = dbType.getDb();
if(DATABASE_TYPE.startsWith(DbType.SQL_SERVER.getDb())){
DATABASE_TYPE = DbType.SQL_SERVER.getDb();
}
}
else{
SqlSessionFactory sqlSessionFactory = getBean(SqlSessionFactory.class);
if(sqlSessionFactory != null){
DATABASE_TYPE = sqlSessionFactory.getConfiguration().getDatabaseId();
}
}
if(DATABASE_TYPE == null){
log.warn("无法识别数据库类型,请检查配置!");
}
return DATABASE_TYPE;
}
}

View File

@ -36,7 +36,7 @@ public class D extends DateUtils{
/***
* 星期
*/
protected static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
public static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
/***
* 当前的日期时间

View File

@ -130,7 +130,7 @@ public class Encryptor {
seed = seed + S.cut(KEY_FILL, 16-seed.length());
}
else if(seed.length() > 16){
seed = S.cut(KEY_FILL, 16);
seed = S.cut(seed, 16);
}
return seed.getBytes();
}

View File

@ -1,8 +1,6 @@
package com.diboot.core.util;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
/**

View File

@ -23,6 +23,30 @@ import java.util.Map;
public class SqlExecutor {
private static final Logger log = LoggerFactory.getLogger(SqlExecutor.class);
/**
* 检查SQL是否可以正常执行
* @param sqlStatement
* @return
*/
public static boolean validateQuery(String sqlStatement){
// 获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class);
if(sqlSessionFactory == null){
log.warn("无法获取SqlSessionFactory实例SQL将不被执行。");
return false;
}
try(SqlSession session = sqlSessionFactory.openSession(); Connection conn = session.getConnection(); PreparedStatement stmt = conn.prepareStatement(sqlStatement)){
ResultSet rs = stmt.executeQuery();
rs.close();
log.trace("执行验证SQL:{} 成功", sqlStatement);
return true;
}
catch(Exception e){
log.trace("执行验证SQL:{} 失败:{}", sqlStatement, e.getMessage());
return false;
}
}
/***
* 执行Select语句: SELECT user_id,role_id FROM user_role WHERE user_id IN (?,?,?,?)
* 查询结果如: [{"user_id":1001,"role_id":101},{"user_id":1001,"role_id":102},{"user_id":1003,"role_id":102},{"user_id":1003,"role_id":103}]
@ -34,7 +58,7 @@ public class SqlExecutor {
return null;
}
// 获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) ContextHelper.getBean(SqlSessionFactory.class);
SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class);
if(sqlSessionFactory == null){
log.warn("无法获取SqlSessionFactory实例SQL将不被执行。");
return null;
@ -95,9 +119,15 @@ public class SqlExecutor {
Map<String, Object> resultMap = new HashMap<>();
if(V.notEmpty(resultSetMapList)){
for(Map<String, E> row : resultSetMapList){
String key = String.valueOf(row.get(keyName));
Object value = row.get(valueName);
resultMap.put(key, value);
Object keyObj = row.get(keyName);
if(keyObj == null && row.get(keyName.toUpperCase()) != null){
keyObj = row.get(keyName.toUpperCase());
}
Object valueObj = row.get(valueName);
if(valueObj == null && row.get(valueName.toUpperCase()) != null){
valueObj = row.get(valueName.toUpperCase());
}
resultMap.put(S.valueOf(keyObj), valueObj);
}
}
return resultMap;
@ -121,13 +151,21 @@ public class SqlExecutor {
Map<String, List> resultMap = new HashMap<>();
if(V.notEmpty(resultSetMapList)){
for(Map<String, E> row : resultSetMapList){
String key = String.valueOf(row.get(keyName));
Object keyObj = row.get(keyName);
if(keyObj == null && row.get(keyName.toUpperCase()) != null){
keyObj = row.get(keyName.toUpperCase());
}
String key = S.valueOf(keyObj);
List valueList = resultMap.get(key);
if(valueList == null){
valueList = new ArrayList();
resultMap.put(key, valueList);
}
valueList.add(row.get(valueName));
Object valueObj = row.get(valueName);
if(valueObj == null && row.get(valueName.toUpperCase()) != null){
valueObj = row.get(valueName.toUpperCase());
}
valueList.add(valueObj);
}
}
return resultMap;
@ -145,7 +183,7 @@ public class SqlExecutor {
return false;
}
// 获取SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = (SqlSessionFactory) ContextHelper.getBean(SqlSessionFactory.class);
SqlSessionFactory sqlSessionFactory = ContextHelper.getBean(SqlSessionFactory.class);
if (sqlSessionFactory == null){
log.warn("无法获取SqlSessionFactory实例SQL将不被执行。");
return false;

View File

@ -1,10 +1,14 @@
package com.diboot.core.util;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.sql.Timestamp;
import java.util.*;
@ -17,6 +21,10 @@ import java.util.*;
*/
public class V {
private static final Logger log = LoggerFactory.getLogger(V.class);
/**
* hibernate注解验证
*/
private static Validator VALIDATOR = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator();
/***
* 对象是否为空
@ -234,6 +242,7 @@ public class V {
* @param validation
* @return
*/
@Deprecated
public static String validate(String value, String validation){
if(isEmpty(validation)){
return null;
@ -404,4 +413,21 @@ public class V {
return S.join(allErrors);
}
/**
* 基于Bean中的validator注解校验
* @param obj
*/
public static <T> String validateBean(T obj) {
// 校验
Set<ConstraintViolation<T>> errors = VALIDATOR.validate(obj);
if(errors == null || errors.size() == 0){
return null;
}
List<String> allErrors = new ArrayList<>(errors.size());
for(ConstraintViolation<T> err : errors){
allErrors.add(err.getMessage());
}
return S.join(allErrors);
}
}

View File

@ -2,22 +2,21 @@ package com.diboot.core.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import com.diboot.core.entity.Dictionary;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 数据字典的VO附带子项定义children
*/
@Getter
@Setter
@Accessors(chain = true)
public class DictionaryVO extends Dictionary {
@BindEntityList(entity= Dictionary.class, condition="this.type=type AND parent_id>0")
private List<Dictionary> children;
public List<Dictionary> getChildren() {
return children;
}
public void setChildren(List<Dictionary> children) {
this.children = children;
}
}

View File

@ -176,4 +176,77 @@ public class JsonResult implements Serializable {
}
}
/***
* 请求处理成功
*/
public static JsonResult OK(){
return new JsonResult(Status.OK);
}
/***
* 请求处理成功
*/
public static JsonResult OK(Object data){
return new JsonResult(Status.OK, data);
}
/***
* 部分成功一般用于批量处理场景只处理筛选后的合法数据
*/
public static JsonResult WARN_PARTIAL_SUCCESS(String msg){
return new JsonResult(Status.WARN_PARTIAL_SUCCESS).msg(msg);
}
/***
* 有潜在的性能问题
*/
public static JsonResult WARN_PERFORMANCE_ISSUE(String msg){
return new JsonResult(Status.WARN_PERFORMANCE_ISSUE).msg(msg);
}
/***
* 传入参数不对
*/
public static JsonResult FAIL_INVALID_PARAM(String msg){
return new JsonResult(Status.FAIL_INVALID_PARAM).msg(msg);
}
/***
* Token无效或已过期
*/
public static JsonResult FAIL_INVALID_TOKEN(String msg){
return new JsonResult(Status.FAIL_INVALID_TOKEN).msg(msg);
}
/***
* 没有权限执行该操作
*/
public static JsonResult FAIL_NO_PERMISSION(String msg){
return new JsonResult(Status.FAIL_NO_PERMISSION).msg(msg);
}
/***
* 请求资源不存在
*/
public static JsonResult FAIL_NOT_FOUND(String msg){
return new JsonResult(Status.FAIL_NOT_FOUND).msg(msg);
}
/***
* 数据校验不通过
*/
public static JsonResult FAIL_VALIDATION(String msg){
return new JsonResult(Status.FAIL_VALIDATION).msg(msg);
}
/***
* 操作执行失败
*/
public static JsonResult FAIL_OPERATION(String msg){
return new JsonResult(Status.FAIL_OPERATION).msg(msg);
}
/***
* 系统异常
*/
public static JsonResult FAIL_EXCEPTION(String msg){
return new JsonResult(Status.FAIL_EXCEPTION).msg(msg);
}
/***
* 缓存清空
*/
public static JsonResult MEMORY_EMPTY_LOST(String msg){
return new JsonResult(Status.MEMORY_EMPTY_LOST).msg(msg);
}
}

View File

@ -2,6 +2,7 @@ package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import diboot.core.test.StartupApplication;
@ -52,6 +53,16 @@ public class TestEntityBinder {
System.out.println(JSON.stringify(vo.getOrganizationVO()));
System.out.println(JSON.stringify(vo));
}
// 单个entity接口测试
EntityBinderVO singleVO = BeanUtils.convert(userList.get(0), EntityBinderVO.class);
RelationsBinder.bind(singleVO);
// 验证直接关联和通过中间表间接关联的绑定
Assert.assertEquals(singleVO.getDepartmentId(), singleVO.getDepartment().getId());
Assert.assertNotNull(singleVO.getDepartment().getOrgId());
// 测试绑定VO
Assert.assertNotNull(singleVO.getOrganizationVO());
System.out.println(JSON.stringify(singleVO.getOrganizationVO()));
System.out.println(JSON.stringify(singleVO));
}
}

View File

@ -2,8 +2,11 @@ package diboot.core.test.binder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.diboot.core.binding.RelationsBinder;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.service.DictionaryService;
import com.diboot.core.util.JSON;
import com.diboot.core.util.V;
import com.diboot.core.vo.DictionaryVO;
import diboot.core.test.StartupApplication;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.User;
@ -40,6 +43,9 @@ public class TestEntityListBinder {
@Autowired
DepartmentService departmentService;
@Autowired
DictionaryService dictionaryService;
/**
* 验证直接关联的绑定
*/
@ -86,4 +92,14 @@ public class TestEntityListBinder {
}
}
@Test
public void testDictionaryBinder(){
// 查询是否创建成功
LambdaQueryWrapper<Dictionary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dictionary::getType, "GENDER");
Dictionary dictionary = dictionaryService.getSingleEntity(queryWrapper);
DictionaryVO vo = RelationsBinder.convertAndBind(dictionary, DictionaryVO.class);
Assert.assertTrue(vo.getChildren().size() > 0);
}
}

View File

@ -2,13 +2,14 @@ package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Department;
import org.apache.ibatis.annotations.Mapper;
/**
* 部门Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
@Mapper
public interface DepartmentMapper extends BaseCrudMapper<Department> {
}

View File

@ -2,13 +2,14 @@ package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Organization;
import org.apache.ibatis.annotations.Mapper;
/**
* 单位Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
@Mapper
public interface OrganizationMapper extends BaseCrudMapper<Organization> {
}

View File

@ -2,13 +2,14 @@ package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.Role;
import org.apache.ibatis.annotations.Mapper;
/**
* 员工Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
@Mapper
public interface RoleMapper extends BaseCrudMapper<Role> {
}

View File

@ -2,13 +2,14 @@ package diboot.core.test.binder.mapper;
import com.diboot.core.mapper.BaseCrudMapper;
import diboot.core.test.binder.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* 员工Mapper
* @author mazc@dibo.ltd
* @version 2018/12/22
* Copyright © www.dibo.ltd
*/
@Mapper
public interface UserMapper extends BaseCrudMapper<User> {
}

View File

@ -10,7 +10,6 @@ import org.springframework.stereotype.Service;
* 单位相关Service实现
* @author mazc@dibo.ltd
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class OrganizationServiceImpl extends BaseServiceImpl<OrganizationMapper, Organization> implements OrganizationService {

View File

@ -10,7 +10,6 @@ import org.springframework.stereotype.Service;
* 员工相关Service
* @author mazc@dibo.ltd
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

View File

@ -10,7 +10,6 @@ import org.springframework.stereotype.Service;
* 员工相关Service
* @author mazc@dibo.ltd
* @version 2018/12/23
* Copyright © www.dibo.ltd
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

View File

@ -1,6 +1,9 @@
package diboot.core.test.binder.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* 定时任务
@ -8,6 +11,9 @@ import com.baomidou.mybatisplus.annotation.TableField;
* @version v2.0
* @date 2018/12/27
*/
@Getter
@Setter
@Accessors(chain = true)
public class DepartmentVO {
private static final long serialVersionUID = -4849732665419794547L;
@ -17,19 +23,4 @@ public class DepartmentVO {
@TableField(exist = false)
private String name;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -4,12 +4,18 @@ import com.diboot.core.binding.annotation.BindEntity;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* @author mazc@dibo.ltd
* @version v2.0
* @date 2019/1/30
*/
@Getter
@Setter
@Accessors(chain = true)
public class EntityBinderVO extends User {
private static final long serialVersionUID = 3526115343377985725L;
@ -18,21 +24,7 @@ public class EntityBinderVO extends User {
private Department department;
// 通过中间表关联Entity
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id") // AND ...
@BindEntity(entity = Organization.class, condition = "this.department_id=department.id AND department.org_id=id AND parent_id=0") // AND ...
private OrganizationVO organizationVO;
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public OrganizationVO getOrganizationVO() {
return organizationVO;
}
public void setOrganizationVO(OrganizationVO organizationVO) {
this.organizationVO = organizationVO;
}
}

View File

@ -3,6 +3,9 @@ package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import diboot.core.test.binder.entity.Role;
import diboot.core.test.binder.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
@ -13,27 +16,15 @@ import java.util.List;
* @version v2.0
* @date 2019/06/22
*/
@Getter
@Setter
@Accessors(chain = true)
public class EntityListComplexBinderVO extends User {
private String userType = "OrgUser";
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
// 支持通过中间表的多-多Entity实体关联
@BindEntityList(entity = Role.class, condition="this.id=user_role.user_id AND user_role.role_id=id")
private List<Role> roleList;
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
}

View File

@ -2,6 +2,9 @@ package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindEntityList;
import diboot.core.test.binder.entity.Department;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.List;
@ -10,6 +13,9 @@ import java.util.List;
* @version v2.0
* @date 2019/1/5
*/
@Getter
@Setter
@Accessors(chain = true)
public class EntityListSimpleBinderVO extends Department {
private static final long serialVersionUID = -362116388664907913L;
@ -17,11 +23,4 @@ public class EntityListSimpleBinderVO extends Department {
@BindEntityList(entity = Department.class, condition = "this.id=parent_id")
private List<DepartmentVO> children;
public List<DepartmentVO> getChildren() {
return children;
}
public void setChildren(List<DepartmentVO> children) {
this.children = children;
}
}

View File

@ -5,6 +5,9 @@ import com.diboot.core.binding.annotation.BindField;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.Organization;
import diboot.core.test.binder.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <Description>
@ -13,6 +16,9 @@ import diboot.core.test.binder.entity.User;
* @version v2.0
* @date 2019/06/22
*/
@Getter
@Setter
@Accessors(chain = true)
public class FieldBinderVO extends User{
private static final long serialVersionUID = 3526115343377985725L;
@ -30,30 +36,4 @@ public class FieldBinderVO extends User{
@BindDict(type="GENDER", field = "gender")
private String genderLabel;
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getOrgName() {
return orgName;
}
public void setOrgName(String orgName) {
this.orgName = orgName;
}
public String getOrgTelphone() {
return orgTelphone;
}
public void setOrgTelphone(String orgTelphone) {
this.orgTelphone = orgTelphone;
}
public String getGenderLabel() {
return genderLabel;
}
public void setGenderLabel(String genderLabel) {
this.genderLabel = genderLabel;
}
}

View File

@ -1,5 +1,9 @@
package diboot.core.test.binder.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <Description>
*
@ -7,26 +11,13 @@ package diboot.core.test.binder.vo;
* @version v2.0
* @date 2019/12/06
*/
@Getter
@Setter
@Accessors(chain = true)
public class OrganizationVO {
private Long parentId;
private String name;
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -3,6 +3,9 @@ package diboot.core.test.binder.vo;
import com.diboot.core.binding.annotation.BindField;
import diboot.core.test.binder.entity.Department;
import diboot.core.test.binder.entity.User;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
/**
* <Description>
@ -11,6 +14,9 @@ import diboot.core.test.binder.entity.User;
* @version v2.0
* @date 2019/06/22
*/
@Getter
@Setter
@Accessors(chain = true)
public class UserVO extends User{
private static final long serialVersionUID = 3526115343377985709L;
@ -18,10 +24,4 @@ public class UserVO extends User{
@BindField(entity= Department.class, field="name", condition="this.department_id=id AND parent_id IS NOT NULL AND name = '研发组'")
private String deptName;
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}

View File

@ -8,10 +8,9 @@ import com.diboot.core.config.BaseConfig;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.service.impl.DictionaryServiceImpl;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.V;
import com.diboot.core.vo.KeyValue;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.PagingJsonResult;
import com.diboot.core.vo.*;
import diboot.core.test.StartupApplication;
import diboot.core.test.config.SpringMvcConfig;
import org.junit.Assert;
@ -24,10 +23,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* BaseService接口实现测试 (需先执行example中的初始化SQL)
@ -88,7 +84,7 @@ public class BaseServiceTest {
dictionary.setItemName("证件类型");
dictionary.setParentId(0L);
dictionaryService.createEntity(dictionary);
Assert.assertTrue(dictionary.getPrimaryKey() != null);
// 查询是否创建成功
LambdaQueryWrapper<Dictionary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dictionary::getType, TYPE);
@ -160,4 +156,90 @@ public class BaseServiceTest {
Assert.assertTrue(pagingJsonResult.getPage().getOrderBy().equals("id:DESC"));
}
/**
* 测试1-多的批量新建/更新/删除操作
*/
@Test
@Transactional
public void testCreateUpdateDeleteEntityAndRelatedEntities(){
// 创建
String TYPE = "ID_TYPE";
// 定义
Dictionary dictionary = new Dictionary();
dictionary.setType(TYPE);
dictionary.setItemName("证件类型");
dictionary.setParentId(0L);
// 子项
List<Dictionary> dictionaryList = new ArrayList<>();
String[] itemNames = {"身份证", "驾照", "护照"}, itemValues = {"SFZ","JZ","HZ"};
for(int i=0; i<itemNames.length; i++){
Dictionary dict = new Dictionary();
dict.setType(TYPE);
dict.setItemName(itemNames[i]);
dict.setItemValue(itemValues[i]);
dict.setParentId(dictionary.getId());
dictionaryList.add(dict);
}
boolean success = dictionaryService.createEntityAndRelatedEntities(dictionary, dictionaryList, Dictionary::setParentId);
Assert.assertTrue(success);
dictionary.setItemName(dictionary.getItemName()+"_2");
dictionaryList.remove(1);
Dictionary dict = new Dictionary();
dict.setType(TYPE);
dict.setItemName("港澳通行证");
dict.setItemValue("GATXZ");
dictionaryList.add(dict);
success = dictionaryService.updateEntityAndRelatedEntities(dictionary, dictionaryList, Dictionary::setParentId);
Assert.assertTrue(success);
// 查询是否创建成功
LambdaQueryWrapper<Dictionary> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dictionary::getType, TYPE).ne(Dictionary::getParentId, 0);
List<Dictionary> dictionaryList2 = dictionaryService.getEntityList(queryWrapper);
Assert.assertTrue(V.notEmpty(dictionaryList2));
List<String> itemNames2 = BeanUtils.collectToList(dictionaryList2, Dictionary::getItemName);
Assert.assertTrue(itemNames2.contains("港澳通行证"));
//success = dictionaryService.updateEntityAndRelatedEntities(dictionary, null, Dictionary::setParentId);
//Assert.assertTrue(success);
//dictionaryList2 = dictionaryService.getEntityList(queryWrapper);
//Assert.assertTrue(V.isEmpty(dictionaryList2));
success = dictionaryService.deleteEntityAndRelatedEntities(dictionary.getId(), Dictionary.class, Dictionary::setParentId);
Assert.assertTrue(success);
dictionaryList2 = dictionaryService.getEntityList(queryWrapper);
Assert.assertTrue(V.isEmpty(dictionaryList2));
}
@Test
public void testJsonResult(){
Map map = new HashMap();
map.put("k", "123");
JsonResult jsonResult = JsonResult.OK(map);
JsonResult jsonResult2 = JsonResult.OK(map);
Assert.assertEquals(jsonResult.getData(), jsonResult2.getData());
String msg = "参数name不匹配";
jsonResult = new JsonResult(Status.FAIL_VALIDATION, msg);
jsonResult2 = JsonResult.FAIL_VALIDATION(msg);
Assert.assertTrue(jsonResult.getMsg().endsWith(msg));
Assert.assertTrue(jsonResult2.getMsg().endsWith(msg));
}
@Test
public void testExist(){
boolean exists = dictionaryService.exists(Dictionary::getType, "GENDER");
Assert.assertTrue(exists);
}
@Test
public void testContextHelper(){
String database = ContextHelper.getDatabaseType();
System.out.println(database);
Assert.assertTrue(database.equals("mysql") || database.equals("oracle"));
}
}

View File

@ -3,6 +3,7 @@ package diboot.core.test.util;
import com.baomidou.mybatisplus.annotation.TableId;
import com.diboot.core.entity.Dictionary;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.vo.DictionaryVO;
import org.junit.Assert;
import org.junit.Test;
@ -34,9 +35,13 @@ public class BeanUtilsTest {
Map<String, Object> map = new HashMap<>();
map.put("type", "STATUS");
map.put("itemName",itemName);
map.put("editable", true);
map.put("createTime", "2018-09-12 23:09");
Dictionary dictionary3 = new Dictionary();
BeanUtils.bindProperties(dictionary3, map);
Assert.assertTrue(dictionary2.getItemName().equals(itemName));
Assert.assertTrue(dictionary3.getItemName().equals(itemName));
Assert.assertTrue(dictionary3.isEditable() == true);
Assert.assertTrue(dictionary3.getCreateTime() != null);
}
@Test
@ -125,6 +130,38 @@ public class BeanUtilsTest {
}
@Test
public void testBuildTree(){
List<Dictionary> dictionaryList = new ArrayList<>();
for(long id=1001; id<=1005; id++){
Dictionary dictionary1 = new Dictionary();
dictionary1.setId(id);
dictionary1.setParentId(1L);
dictionaryList.add(dictionary1);
}
Dictionary parent = new Dictionary();
parent.setId(1L);
parent.setParentId(0L);
dictionaryList.add(parent);
// 正常数据
List<DictionaryVO> list = BeanUtils.convertList(dictionaryList, DictionaryVO.class);
list = BeanUtils.buildTree(list);
Assert.assertEquals(list.size(), 1);
Assert.assertEquals(list.get(0).getChildren().size(), 5);
// 异常数据告警
Dictionary dict2 = new Dictionary();
dict2.setId(1L);
dict2.setParentId(1L);
dictionaryList.add(dict2);
list = BeanUtils.convertList(dictionaryList, DictionaryVO.class);
try{
list = BeanUtils.buildTree(list);
}
catch (Exception e){
Assert.assertTrue(e.getMessage().contains("请检查"));
}
}
}

View File

@ -40,6 +40,11 @@
</div>
</div>
</div>
<div class="custom content">
<div class="features">
<img src="../public/structure.png" alt="" style="display: block; margin: 0 auto; width: 100%; height: auto;">
</div>
</div>
<div class="footer">
<div class="footer-content">
<div class="footer-item"></div>

View File

@ -39,6 +39,48 @@ module.exports = {
]
}
],
'/guide/diboot-file/': [
{
title: '文件组件 使用指南',
collapsable: true,
sidebarDepth: 2,
children: [
['/guide/diboot-file/介绍', '介绍'],
['/guide/diboot-file/开始使用', '开始使用'],
['/guide/diboot-file/自定义扩展', '自定义扩展'],
]
}
],
'/guide/diboot-antd-admin/': [
{
title: 'diboot-antd-admin 项目指南',
collapsable: true,
sidebarDepth: 2,
children: [
['/guide/diboot-antd-admin/介绍', '介绍'],
['/guide/diboot-antd-admin/开始使用', '开始使用'],
['/guide/diboot-antd-admin/添加页面', '添加页面'],
['/guide/diboot-antd-admin/权限控制', '权限控制'],
['/guide/diboot-antd-admin/接口请求', '接口请求'],
['/guide/diboot-antd-admin/CRUD快速集成', 'CRUD快速集成'],
]
}
],
'/guide/diboot-element-admin/': [
{
title: 'diboot-element-admin 指南',
collapsable: true,
sidebarDepth: 2,
children: [
['/guide/diboot-element-admin/介绍', '介绍'],
['/guide/diboot-element-admin/开始使用', '开始使用'],
['/guide/diboot-element-admin/添加页面', '添加页面'],
['/guide/diboot-element-admin/权限控制', '权限控制'],
['/guide/diboot-antd-admin/接口请求', '接口请求'],
['/guide/diboot-element-admin/CRUD快速集成', 'CRUD快速集成'],
]
}
],
'/guide/diboot-devtools/': [
{
title: 'diboot-devtools 使用指南',
@ -59,21 +101,45 @@ module.exports = {
text: '基础组件 指南',
items: [
{ text: 'core基础内核', link: '/guide/diboot-core/安装' },
{ text: 'IAM身份认证', link: '/guide/diboot-iam/介绍' }
{ text: 'IAM身份认证', link: '/guide/diboot-iam/介绍' },
{ text: 'File文件组件', link: '/guide/diboot-file/介绍' }
]
}, {
text: '前端项目 指南',
items: [
{ text: 'diboot-antd-admin', link: '/guide/diboot-antd-admin/介绍' },
{ text: 'diboot-element-admin', link: '/guide/diboot-element-admin/介绍' }
]
}, {
text: 'devtools助理 指南',
link: '/guide/diboot-devtools/介绍'
},{
text: '捐助我们',
link: '/guide/donate/'
},{
text: '项目合作',
link:'http://www.dibo.ltd/contect.html'
},{
text: 'Gitee', link: 'https://gitee.com/dibo_software/diboot-v2'
},{
text: 'GitHub', link: 'https://github.com/dibo-software/diboot-v2'
}, {
text: '开发团队',
items: [
{
text: '项目合作',
link:'http://www.dibo.ltd/contect.html'
},
{
text: '捐助我们',
link: '/guide/donate/'
}
]
}, {
text: '代码仓库',
items: [
{
text: 'Gitee',
link: 'https://gitee.com/dibo_software/diboot-v2'
},
{
text: 'GitHub',
link: 'https://github.com/dibo-software/diboot-v2'
}
]
}, {
text: '优秀案例',
link: '/other/excellentExample'
},{
text: '1.x旧版', link: 'https://www.diboot.com/diboot-v1/'
}]

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -0,0 +1,125 @@
# CRUD快速集成
## 开始之前
* 在diboot-antd-admin中我们对CRUD等常用功能进行了一些抽象将常用的列表、详情、新建、更新、删除等功能需要的相关属性与方法都抽象成了vue的mixins文件这些文件在**src/components/diboot/mixins**文件夹下,强烈建议您先阅览以下他们。
* 也可以对已有的一些页面组件代码进行阅读,比如**src/views/system/iamUser**文件夹下的相关组件代码。
## 列表页
1. 引入列表的mixins文件如下
```javascript
import list from '@/components/diboot/mixins/list'
export default {
mixins: [list]
}
```
2. 配置列表页的接口前缀这个接口前缀在mixins的处理中会自动拼接'/list'如果后端代码是通过diboot自动生成的这也是后端默认的接口规则
3. 自定义列表接口:如果您的列表接口最后面不是'/list',而是'/getList'那么可以在data中设置listApi属性如下
```javascript
export default {
data () {
return {
listApi: 'getList'
}
}
}
```
4. 配置columns数据columns数据是定义该列表页需要显示哪些列的默认的column只需要配置title与dataIndex属性即可如下
```javascript
export default {
data () {
return {
columns: [
{
title: '姓名',
dataIndex: 'realname'
}
]
}
}
}
```
5. 删除数据直接在删除按钮上调用remove函数即可传入需要删除的当前id。
6. 导出数据直接在导出按钮上调用exportData函数即可将会传入当前查询参数并异步下载文件。
7. 自定义查询参数处理方法重写rebuildQuery方法接收mixins中已经定义的customQueryParam与queryParam的合并值返回处理后的值该方法将会在获取列表数据前或导出数据前被调用。
8. 钩子函数afterLoadList在列表加载完毕将会执行该操作另外删除函数返回的是 Promise对象所以可以使用.then在删除完毕时执行某些操作。
9. 相关配置:
* customQueryParam: 不会被搜索栏改变的初始查询参数对象,一般用于页面固定的查询参数
* queryParam与查询条件绑定的查询参数对象
* advanced用于构建可收缩/展开的查询框的状态参数
* more当前对象的关联数据对象
* getMore是否在该页面初始化时加载当前对象的关联数据对象加载的关联数据对象存储在more中默认为false
* getListFromMixin是否在页面初始化时自动加载列表数据默认为true
* loadingData标记页面加载数据状态
* pagination分页配置
* baseApi接口前缀必须配置
* listApi列表数据接口默认为 /list
* deleteApiPrefix删除接口前缀默认为 /
* exportApi: 导出接口,默认为 /export
## 新建与更新
1. 引入表单的mixins文件如下
```javascript
import form from '@/components/diboot/mixins/form'
export default {
mixins: [form]
}
```
2. 相同业务对象的新建与更新使用同一个表单组件默认以抽屉形式打开可在引入地方直接调用该组件的open方法进行打开。
3. 使用以上引入列表mixins的方式引入form的mixins文件将会具有mixins中的所有能力。
4. 已有功能:
* open函数可直接调用打开该表单
* 提供根据open中参数情况自动切换新建与更新操作
* 提供打开与关闭完成的钩子函数
* 提供表单提交函数 onSubmit
* 对form下校验规则的自动校验
* 提供对校验的自定义操作
* 提供校验完成后的enhance钩子函数可对需要提交的数据进行再处理
* 可以对新建数据或更新数据的方法重写
* 提供提交成功与提交失败的钩子函数
* 提供表单默认布局参数
* 提供对关联数据的自动加载
* 提供各项配置支持自定义接口等
5. 相关配置:
* baseApi与列表页相同
* createApi新建接口将自动拼接在baseApi之后默认为 /
* updateApiPrefix: 更新接口前缀将自动拼接在baseApi之后默认为 /
* model: 更新时加载的原数据
* title页面标题默认为新建或更新
* reloadMore加载的关联数据对象默认为{}如果list页面以及由more数据且不与自身关联可通过传参方式直接使用list中的more参数
* getMore初始化时是否加载reloadMore数据默认为false
* state当前组件状态对象默认为{visible: false, confirmSubmit: false}
6. 钩子函数:
* afterOpen在组件打开后或者更新时数据加载完毕后执行该函数
* afterClose 在组件关闭后,执行该函数
* enhance在校验完成后对提交数据进行处理的函数
* submitSuccess 提交成功后执行该函数默认关闭该组件并发送complete和changeKey事件
* submitFailed 提交失败后,执行该函数,默认提示错误消息
## 查看详情
1. 引入表单的mixins文件如下
```javascript
import detail from '@/components/diboot/mixins/detail'
export default {
mixins: [detail]
}
```
2. 已有功能:
* 自动加载当前记录数据
* 关闭弹窗或者抽屉
* 可通过父组件传入width参数控制抽屉的宽度
3. 相关配置:
* baseApi与列表页相同
* visible当前组件显示状态默认为false
* model当前详情框详情数据
* title当前详情框标题
4. 钩子函数:
* afterOpen: 打开详情之后执行的函数
* afterClose关闭之后执行的函数

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -0,0 +1,31 @@
# diboot-antd-admin前端基础项目
## 说明
diboot-antd-admin前端基础项目是一个与diboot其他后端组件构成的后端系统相吻合的前端项目。
您可以将此用于您的日常开发中和diboot其他组件一起用于快速构建您的实际项目享受自动化带来的快速开发体验。
此间开发披星戴月日夜兼程难免有不完善之处恳请您加入我们diboot的QQ群或微信群一起来讨论留下您宝贵的指点与建议或者在github中向我们提交PR感念不尽
## 相关技术栈
* 前端基础(自不必多言)
* Vue全家桶套餐[Vue](https://cn.vuejs.org/index.html), [vue-router](https://router.vuejs.org/zh/), [axios](https://github.com/axios/axios), [vuex](https://vuex.vuejs.org/zh/)
* Ant Design 的 Vue 实现:[Ant Design Vue](https://www.antdv.com/docs/vue/introduce/)
* 中后台解决方案:[ant-design-pro-vue](https://github.com/sendya/ant-design-pro-vue)
* ES6 语法
## 项目特性
* 基于开源项目[ant-design-pro-vue](https://github.com/sendya/ant-design-pro-vue)
* 在**ant-design-pro-vue**项目基础上,进行精简;
* 登录、权限、接口对接上与diboot-v2相关组件构建的后端应用无缝集成且开箱可用
* 提取CRUD页面相关通用属性与方法到mixins文件中少写代码多做事情
* 菜单到按钮级别的细粒度权限控制;
* 智能化的权限配置方案;
* 自动化的token交换方案
* 预置多种常用请求方式,轻松完成异步文件下载等;
* 数据字典管理功能;
* 登录人员管理界面;
* 角色与权限管理功能;
* 权限管理功能;
* 登录日志管理功能。

View File

@ -0,0 +1,63 @@
# 开始使用diboot-antd-admin
## 新建项目
从Github仓库中clone最新的项目到本地
```bash
git clone https://github.com/dibo-software/diboot-antd-admin my-admin
cd my-admin
```
## 安装依赖
项目根目录下执行以下命令,安装项目所需依赖
```bash
yarn
# OR
npm install
```
## 检查接口前缀与代理配置
对于axios和vue.config.js的devServer的proxy配置中接口前缀需要与后端一致项目默认为 /api如果您的后端context-path也是 /api那么axios和vue.config.js中的接口前缀不必改动。
默认后端接口端口为8080如果您的后端端口不是8080需要更改vue.config.js中关于devServer的proxy配置。
若需更改axios中接口前缀需在**src/utils/request.js**文件中更改常量BASE_URL的值如下
```javascript
const BASE_URL = '/api'
```
若需更改项目接口代理,需更改项目根目录下**vue.config.js**文件中的devServer配置项如下
```javascript
devServer: {
port: 9000,
proxy: {
'/api': {
target: 'http://localhost:8080',
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': '/api'
}
}
}
}
```
## 启动项目
执行以下命令启动项目
```bash
yarn server
# OR
npm run server
```
运行完成后,点击命令行提示出的地址,打开页面成功,项目启动就完成了。

View File

@ -0,0 +1,63 @@
# 接口请求
## 相关配置
我们在 [ant-design-pro-vue](https://github.com/sendya/ant-design-pro-vue) 项目对axios配置的基础上添加了一些增强性的更改与扩展这些更改都是在 **src/utils/request.js**中完成的。
* 常量配置:
* **BASE_URL**接口基础路径对应后端的context-path配置
* **JWT_HEADER_KEY**登录成功后请求头中携带token的字段后端默认为**authtoken**
* **TOKEN_REFRESH_EXPIRE**token超时自动刷新的重试间隔时间分钟建议设置为 后端设置的登录过期时间的 1/8
* axios相关配置
* 在请求拦截器与响应拦截器中我们已经对token相关流程以及文件下载和遇到请求异常提示错误消息等做了处理如果您需要对其进行自定义更改这些配置即可
## 接口扩展
我们对axios的默认调用方式进行了一些扩展主要包括了常用的一些接口请求函数。
* 调用方式如下:
```javascript
import { dibootApi } from '@/utils/request'
async getData() {
const params = {name: 'xxx'}
const res = await dibootApi.get(`/test/getData`, params)
if (res.code === 0) {
...
} else {
...
}
}
// 或
getData() {
const params = {name: 'xxx'}
dibootApi.get(`/test/getData`, params).then(res => {
if (res.code === 0) {
...
} else {
...
}
})
}
```
* 已有函数:
* **dibootApi.get**
* GET方式请求接口传入 {} 类型对象
* **dibootApi.post**
* POST方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.put**
* PUT方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.delete**
* DELETE方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.upload**
* POST方式请求接口传入 formData 对象
* **dibootApi.download**
* GET方式请求接口传入 {} 类型对象
* 返回arraybuffer类型数据
* **dibootApi.postDownload**
* POST方式请求接口传入 {} 类型对象
* 返回arraybuffer类型数据
* 常用语复杂参数提交导出

View File

@ -0,0 +1,85 @@
# 权限控制
## 简介
* 该项目使用的权限控制模型为RBAC角色权限模型
* 支持按钮级别的权限控制;
* 权限分发路径:菜单/按钮/权限-->树状层级结构的权限集-->角色-->角色列表-->用户;
* 在添加新的需要进行权限控制的页面后,需要进行该页面权限的配置,然后在相关角色中对该权限进行授权;
## 菜单权限配置
菜单如果要进行权限控制,一定需要添加在 **src/config/router.config.js** 文件中的 asyncRouterMap 中,且需要配置 meta 属性中的 permission 列表,如下:
```javascript
{
path: '/system/iamLoginTrace/list',
name: 'IamLoginTraceList',
component: () => import('@/views/system/iamLoginTrace/list'),
meta: { title: '登录日志查看', keepAlive: true, permission: ['IamLoginTrace'] }
}
```
## 按钮权限配置
按钮的权限控制,是通过指令控制该按钮是否显示来实现的,一共具有三个指令来控制是否按钮是否显示。
* v-action
* 将该按钮或元素在具有配置的权限时显示出来;
* 只能添加单条按钮权限编码,比如 v-action:add 意味着具有add编码的新建权限该按钮将会显示, v-action:update意味着具有update编码的更新权限时该按钮显示等。
* v-permission
* 将该按钮或元素在具有权限列表中某条权限时显示出来;
* 可以添加多条按钮权限编码,有一条权限存在,就将显示该按钮,比如 v-permission="['update', 'delete']"标识当具有update或者delete中的一种权限时该按钮将会显示。
* v-permission-missing
* 对该按钮或元素在具有权限的时候隐藏;
* 可以添加多条权限编码,有一条权限存在,就将隐藏该按钮,如下代码就将会在没有详情、更新、删除权限时,将操作栏显示为 -
```html
<span v-permission-missing="['detail', 'update', 'delete']">
-
</span>
```
上述按钮权限配置,可以在已有的相关功能中找到:
```html
<span slot="action" slot-scope="text, record">
<a v-action:detail href="javascript:;" @click="$refs.detail.open(record.id)">详情</a>
<a-divider v-action:detail v-permission="['update', 'delete']" type="vertical" />
<a-dropdown v-permission="['update', 'delete']">
<a class="ant-dropdown-link">
更多 <a-icon type="down"/>
</a>
<a-menu slot="overlay">
<a-menu-item v-action:update>
<a @click="$refs.form.open(record.id)">编辑</a>
</a-menu-item>
<a-menu-item v-action:delete>
<a href="javascript:;" @click="remove(record.id)">删除</a>
</a-menu-item>
</a-menu>
</a-dropdown>
<span v-permission-missing="['detail', 'update', 'delete']">
-
</span>
</span>
```
## 菜单权限管理
* 使用具有超级管理员权限的账号登入项目;
* 打开 系统管理-->菜单权限管理 页面,可看到已有权限列表:
![权限列表页面](./images/permission-list.png)
* 点击新建,开始配置新增页面的权限,下图是已有权限配置好之后的表单示例:
![权限表单页面](./images/permission-form.png)
* 菜单编码对应上述**菜单权限配置**中的permission列表中的值默认我们只在meta的permission中配置一个值与此处对应即可
* 下方**按钮/权限里列表**配置中,按钮/权限编码,需要与上述添加在按钮的指令中的参数一致,这里默认配置了一些常用操作类型到数据字典中,如果有新的操作按钮,可以到数据字典的**前端按钮/权限编码**中进行增加;
* 在权限配置中,对于常用的权限,都可以通过点击和选取来完成对菜单和按钮权限的配置;
* 在配置好前端菜单与接口列表,和前端按钮与接口列表后,也完成了前端菜单与按钮和后端接口的关系绑定,之后在对角色设置相关权限后,控制前端界面是否显示的同时,也控制了该角色下的用户对后端对应接口的访问权限;
* 在权限配置完成后,如果需要更改权限显示顺序,可以点击该列表页面右上角“排序”按钮进行权限拖拽排序操作。
## 角色授权管理
* 打开 系统管理-->角色权限管理 页面,可看到当前已有角色列表;
* 新建角色 或 选择需要更改授权的角色进行编辑操作;
* 对需要授权的权限进行选中,不需要的权限取消即可,操作直观简便。
![角色授权页面](./images/role-permission-form.png)

View File

@ -0,0 +1,61 @@
# 添加页面
添加页面需要添加页面相关的Vue组件与相对应的路由配置
## 添加Vue组件
* 在**src/views**文件夹下创建页面对应的文件夹以及对应的页面组件文件
## 添加路由配置
* 后台菜单是根据路由配置自动生成的,具体可参考[路由与菜单](https://pro.loacg.com/docs/router-and-nav)
* 在**src/config/router.config.js**文件中,可以配置需要新增页面的路由。
* 对于需要进行权限控制的菜单需要放到asyncRouterMap中进行配置其他不需要进行权限控制或所有人可用的菜单可以放到constantRouterMap中。
* 路由配置方式,可参考已有配置,如下:
```javascript
// 系统管理
{
path: '/system',
redirect: '/system/dictionary/list',
component: PageView,
meta: { title: '系统管理', icon: 'dashboard' },
children: [
{
path: '/system/dictionary/list',
name: 'DictList',
component: () => import('@/views/system/dictionary/list'),
meta: { title: '数据字典管理', keepAlive: true, permission: ['Dictionary'] }
},
{
path: '/system/iamUser/list',
name: 'IamUserList',
component: () => import('@/views/system/iamUser/list'),
meta: { title: '系统用户管理', keepAlive: true, permission: ['IamUser'] }
},
{
path: '/system/iamRole/list',
name: 'IamRoleList',
component: () => import('@/views/system/iamRole/list'),
meta: { title: '角色权限管理', keepAlive: true, permission: ['IamRole'] }
},
{
path: '/system/iamFrontendPermission/list',
name: 'IamFrontendPermission',
component: () => import('@/views/system/iamFrontendPermission/list'),
meta: { title: '菜单权限管理', keepAlive: true, permission: ['IamFrontendPermission'] }
},
{
path: '/system/iamLoginTrace/list',
name: 'IamLoginTraceList',
component: () => import('@/views/system/iamLoginTrace/list'),
meta: { title: '登录日志查看', keepAlive: true, permission: ['IamLoginTrace'] }
}
]
}
```
* keepAlive设置为true将会在重新进入该页面时保持之前的数据如果设置为false则会重新加载数据
* permission为显示该菜单所需要该用户具有的权限码这个码会在稍后配置系统权限的时候自动代入。
* 如果不配置permission将不会对该菜单进行权限验证所有用户都可以访问该页面如果permission的列表中配置了多个权限码那么具有其中一个就将会具有当前菜单的访问权限。
* 配置完成后,就可以对这些页面进行访问了(如果配置了权限码,可以先使用具有管理员权限的账号访问到)。

View File

@ -2,7 +2,7 @@
## Service类
> 对于一个自定义的entity您可以像以往的习惯一样开发service相关代码我们推荐您继承diboot-core中封装好的BaseService接口及BaseServiceImpl实现。
> 对于一个自定义的entity我们推荐您继承diboot-core中封装好的BaseService接口及BaseServiceImpl实现。
```java
package com.example.demo.service;
@ -96,6 +96,29 @@ System.out.println(success);
===> true
```
### createEntityAndRelatedEntities
```java
/**
* 添加entity 及 其关联子项entities
* @param entity 主表entity
* @param relatedEntities 从表/关联表entities
* @param relatedEntitySetter 设置关联从表绑定id的setter如Dictionary::setParentId
* @return
*/
boolean createEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
```
> 该接口将对一个1对多关联数据的设置关联id并批量保存
```java
boolean success = dictionaryService.createEntityAndRelatedEntities(dictionary, dictionaryList, Dictionary::setParentId);
// 输出
===> true
```
类似的还有1对多数据的批量更新与删除
~~~java
boolean updateEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
boolean deleteEntityAndRelatedEntities(T entity, List<RE> relatedEntities, ISetter<RE, R> relatedEntitySetter);
~~~
### deleteEntity
```java
boolean deleteEntity(Serializable id);

View File

@ -26,10 +26,10 @@ MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
以下依赖在引入diboot-core-starter依赖中可以不再引入。
:::
* **javax.servlet-api**(javax.servlet:javax.servlet-api:4.0.1)
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.2.1.RELEASE)
* **spring-boot-starter-web**(org.springframework.boot:spring-boot-starter-web:2.2.4.RELEASE)
* **mybatis-plus-boot-starter**(com.baomidou:mybatis-plus-boot-starter:3.2.0)
* **commons-io**(commons-io:commons-io:2.6)
* **commons-lang3**(org.apache.commons:commons-lang3:3.8.1)
* **commons-lang3**(org.apache.commons:commons-lang3:3.9)
* **fastjson**(com.alibaba:fastjson:1.2.60)
:::tip

View File

@ -28,6 +28,6 @@ public JsonResult getVOList(UserDto userDto, HttpServletRequest request) throws
//调用super.buildQueryWrapper(entityOrDto, request) 或者直接调用 QueryBuilder.toQueryWrapper(entityOrDto) 进行转换
QueryWrapper<User> queryWrapper = super.buildQueryWrapper(userDto, request);
//... 查询list
return new JsonResult(Status.OK, list);
return JsonResult.OK(list);
}
```

View File

@ -2,15 +2,15 @@
## diboot-devtools是什么
> diboot-devtools是一个面向Java开发人员的开发助理有了她你可以摆脱重复性的Coding更专注于业务分析,提高开发效率和代码质量。
> diboot-devtools是一个面向Java开发人员的开发助理有了她你可以摆脱CRUD等重复性的Coding更专注于业务实现,提高开发效率和代码质量。
> **diboot-devtools - 将重复有规律的事情自动化**
## 我们的优势
* 支持常用的五大数据库MySQLMariaDBORACLESQLServer, PostgreSQL
* 使用简单只需在项目中引入devtools依赖添加相关配置信息后即可启动运行。
* 使用简单只需在项目中引入devtools依赖添加相关配置信息后即可随本地项目启动运行。
* 基于主流框架SpringBoot + Mybatis-Plus打造全新优化内核[diboot-core](https://github.com/dibo-software/diboot-v2/tree/master/diboot-core),保证生成的代码更简洁,质量更高。
* 功能强大,实现数据结构变更与代码联动同步,更方便维护数据库表结构及关联关系,一键生成/更新代码。
* 功能强大,实现数据结构变更与代码联动同步,更方便维护数据库表结构及关联关系,一键生成与非覆盖式更新代码。
* 通过devtools维护数据结构标准化了数据结构定义同时数据结构变动SQL会被自动记录便于同步更新生产等环境数据库。
* 使用灵活,可按需启用更多功能。例如:是否开启引入 `Lombok`、`Swagger`等。
* 适用场景广紧随SpringBoot步伐迭代微服务、单体应用等开发场景通用。
@ -37,4 +37,4 @@
1. 上面的这波操作,带来的另外一个好处是:需要开发人员写的只有真正的业务逻辑代码,代码的可维护性提高。
## devtools支持数据库
MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL
MySQL、MariaDB、ORACLE、SQLServer、PostgreSQL

View File

@ -5,21 +5,21 @@
## 引入依赖
* Gradle项目引入依赖
```
providedCompile("com.diboot:diboot-devtools-spring-boot-starter:2.0.4")
compile("com.diboot:diboot-core-spring-boot-starter:2.0.4")
providedCompile("com.diboot:diboot-devtools-spring-boot-starter:2.0.5")
compile("com.diboot:diboot-core-spring-boot-starter:2.0.5")
```
* Maven项目引入依赖
```
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-devtools-spring-boot-starter</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.4</version>
<version>2.0.5</version>
</dependency>
```
> diboot-devtools-spring-boot-starter 是用于开发过程的助手工具,须声明 **provided**以免打包至非开发环境。
@ -43,6 +43,7 @@ diboot.devtools.output-path-sql=diboot-example/src/main/resources/
diboot.devtools.enable-lombok=true
#diboot.devtools.enable-swagger=false
#diboot.devtools.generate-mapper-xml=false
#diboot.devtools.controller-mapping-extend=false
```
* 配置信息说明
@ -50,13 +51,14 @@ diboot.devtools.enable-lombok=true
* spring.main.allow-bean-definition-overriding=true遇到同样名字的Bean时允许覆盖。
**以下diboot-devtools 相关配置均可根据实际情况填写**
* diboot.devtools.codes-version当前使用diboot-devtools的版本号。
* diboot.devtools.codes-copyright生成代码的版权归属显示在每个类或接口的注释中。
* diboot.devtools.codes-author生成代码的作者显示在每个类或接口的注释中。
* diboot.devtools.output-path-*:分别指向当前项目中`Entity`、`VO`、`Service及其实现类`、`Mapper及映射文件`、`Controller`、`SQL文件所在的路径`。
* diboot.devtools.generate-mapper-xml是否生成Mapper.xml文件默认true
* diboot.devtools.enable-lombok是否引入`Lombok`注解若设置true请注意添加Lombok依赖。
* diboot.devtools.enable-swagger是否引入`Swagger`注解若设置true请注意添加Swagger依赖。
* codes-version当前使用diboot-devtools的版本号。
* codes-copyright生成代码的版权归属显示在每个类或接口的注释中。
* codes-author生成代码的作者显示在每个类或接口的注释中。
* output-path-*:分别指向当前项目中`Entity`、`VO`、`Service及其实现类`、`Mapper及映射文件`、`Controller`、`SQL文件所在的路径`。
* generate-mapper-xml是否生成Mapper.xml文件默认true
* enable-lombok是否引入`Lombok`注解若设置true请注意添加Lombok依赖。
* enable-swagger是否引入`Swagger`注解若设置true请注意添加Swagger依赖。
* controller-mapping-extend: controller是否继承crud的url mapping默认为false
:::warning
如果您使用的是**PostgreSQL数据库**那么需要额外添加两行配置以此来适配boolean类型字段所对应的数据库的boolean类型需添加的配置如下
@ -75,7 +77,7 @@ diboot-devtools在初次运行中会自动安装所需数据库表如果
在项目入口文件 `Application` 上点击右键,在弹出的菜单上点击 `RUN 'Application'`
当出现类似下面提示时,表示启动成功:
```
: Started Application in 14.223 seconds (JVM running for 16.693)
: Started Application in 4.223 seconds (JVM running for 16.693)
```
## 打开管理页面

View File

@ -20,11 +20,11 @@
* 程序生成无序ID程序自动生成无序的32位无序字符串(UUID)来作为ID该ID无规律可循不可用作排序非必需情况不建议使用。
* 预置字段:
* id
* extdata扩展字段用来存放该表各类相关冗余数据的json字符串比如存放冗余的创建人姓名等。
* is_deleted逻辑删除标记字段用来表明该条数据是否已删除默认为0代表未删除1代表已删除。
* create_by创建人id。
* create_time创建时间数据库默认值为创建时的当前系统时间。
* create_by创建人id。
* update_time更新时间数据库默认值为更新时的当前系统时间。
* extdata预留扩展字段用来存放该表各类相关冗余数据的json字符串。
2. SQL建表
@ -53,7 +53,7 @@
* 代码中字段标签将显示为该字段的名称,如:姓名,教师等,建议使用名词,因为该备注会用于拼接校验提示信息等。
4. 数据类型
* devtools提供了6种数据类型供选择分别是String, Long, Integer, Boolean, Date, Double。
* devtools提供了7种数据类型供选择分别是String, Long, Integer, Boolean, Date, Double, BigDecimal
* Entity类中使用这里所选的数据类型。数据库表中存放的Type将会根据所选择的数据类型自动进行匹配。
5. 长度
@ -88,7 +88,7 @@
设置该字段关联关系的方式,可选择`无`、`数据表`、`数据字典`。
* 选择关联`数据表`后,将会弹出可供选择的数据库表。依次是`关联方式`(1-1, n-1, n-n),`哪个表`(关联表名)、`哪个字段`(关联表中与该字段对应的字段)、`显示的字段`(显示关联表信息的字段)、附加选项`在目标对象中绑定`(在目标对象中绑定当前对象)。
如果指定了`显示的字段`,则生成代码将生成@BindField如果未指定则生成@BindEntity。
如果n-1关联中选择了`在目标对象中绑定`则将在目标表的VO中反向绑定当前表对应的Entity集合。
如果n-1关联中选择了`在主对象中绑定`则将在目标主表的VO中反向绑定当前从表的Entity集合。
* 选择关联`数据字典`后,将会弹出可供选择的数据字典,选择一个元数据关联到该字段即可。

View File

@ -0,0 +1,110 @@
# CRUD快速集成
## 开始之前
* 在diboot-element-admin中我们对CRUD等常用功能进行了一些抽象将常用的列表、详情、新建、更新、删除等功能需要的相关属性与方法都抽象成了vue的mixins文件这些文件在**src/components/diboot/mixins**文件夹下,强烈建议您先阅览以下他们。
* 也可以对已有的一些页面组件代码进行阅读,比如**src/views/system/iamUser**文件夹下的相关组件代码。
## 列表页
1. 引入列表的mixins文件如下
```javascript
import list from '@/components/diboot/mixins/list'
export default {
mixins: [list]
}
```
2. 配置列表页的接口前缀这个接口前缀在mixins的处理中会自动拼接'/list'如果后端代码是通过diboot自动生成的这也是后端默认的接口规则
3. 自定义列表接口:如果您的列表接口最后面不是'/list',而是'/getList'那么可以在data中设置listApi属性如下
```javascript
export default {
data () {
return {
listApi: 'getList'
}
}
}
```
4. 删除数据直接在删除按钮上调用remove函数即可传入需要删除的当前id。
5. 导出数据直接在导出按钮上调用exportData函数即可将会传入当前查询参数并异步下载文件。
6. 自定义查询参数处理方法重写rebuildQuery方法接收mixins中已经定义的customQueryParam与queryParam的合并值返回处理后的值该方法将会在获取列表数据前或导出数据前被调用。
7. 钩子函数afterLoadList在列表加载完毕将会执行该操作另外删除函数返回的是 Promise对象所以可以使用.then在删除完毕时执行某些操作。
8. 相关配置:
* customQueryParam: 不会被搜索栏改变的初始查询参数对象,一般用于页面固定的查询参数
* queryParam与查询条件绑定的查询参数对象
* more当前对象的关联数据对象
* getMore是否在该页面初始化时加载当前对象的关联数据对象加载的关联数据对象存储在more中默认为false
* getListFromMixin是否在页面初始化时自动加载列表数据默认为true
* loadingData标记页面加载数据状态
* pagination分页配置
* baseApi接口前缀必须配置
* listApi列表数据接口默认为 /list
* deleteApiPrefix删除接口前缀默认为 /
* exportApi: 导出接口,默认为 /export
## 新建与更新
1. 引入表单的mixins文件如下
```javascript
import form from '@/components/diboot/mixins/form'
export default {
mixins: [form]
}
```
2. 相同业务对象的新建与更新使用同一个表单组件默认以抽屉形式打开可在引入地方直接调用该组件的open方法进行打开。
3. 使用以上引入列表mixins的方式引入form的mixins文件将会具有mixins中的所有能力。
4. 已有功能:
* open函数可直接调用打开该表单
* 提供根据open中参数情况自动切换新建与更新操作
* 提供打开与关闭完成的钩子函数
* 提供表单提交函数 onSubmit
* 对form下校验规则的自动校验
* 提供对校验的自定义操作
* 提供校验完成后的enhance钩子函数可对需要提交的数据进行再处理
* 可以对新建数据或更新数据的方法重写
* 提供提交成功与提交失败的钩子函数
* 提供表单默认布局参数
* 提供对关联数据的自动加载
* 提供各项配置支持自定义接口等
5. 相关配置:
* baseApi与列表页相同
* createApi新建接口将自动拼接在baseApi之后默认为 /
* updateApiPrefix: 更新接口前缀将自动拼接在baseApi之后默认为 /
* initFormData表单初始数据表示表单数据结构
* form: 更新时装载加载的原数据新建时为上述initFormData数据的克隆
* title页面标题默认为新建或更新
* reloadMore加载的关联数据对象默认为{}如果list页面以及由more数据且不与自身关联可通过传参方式直接使用list中的more参数
* getMore初始化时是否加载reloadMore数据默认为false
* state当前组件状态对象默认为{visible: false, confirmSubmit: false}
6. 钩子函数:
* afterOpen在组件打开后或者更新时数据加载完毕后执行该函数
* afterClose 在组件关闭后,执行该函数
* enhance在校验完成后对提交数据进行处理的函数
* submitSuccess 提交成功后执行该函数默认关闭该组件并发送complete和changeKey事件
* submitFailed 提交失败后,执行该函数,默认提示错误消息
## 查看详情
1. 引入表单的mixins文件如下
```javascript
import detail from '@/components/diboot/mixins/detail'
export default {
mixins: [detail]
}
```
2. 已有功能:
* 自动加载当前记录数据
* 关闭弹窗或者抽屉
* 可通过父组件传入width参数控制抽屉的宽度
3. 相关配置:
* baseApi与列表页相同
* visible当前组件显示状态默认为false
* model当前详情框详情数据
* title当前详情框标题
4. 钩子函数:
* afterOpen: 打开详情之后执行的函数
* afterClose关闭之后执行的函数

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,31 @@
# diboot-element-admin前端基础项目
## 说明
diboot-element-admin前端基础项目是一个与diboot其他后端组件构成的后端系统相吻合的前端项目。
您可以将此用于您的日常开发中和diboot其他组件一起用于快速构建您的实际项目享受自动化带来的快速开发体验。
此间开发披星戴月日夜兼程难免有不完善之处恳请您加入我们diboot的QQ群或微信群一起来讨论留下您宝贵的指点与建议或者在github中向我们提交PR感念不尽
## 相关技术栈
* 前端基础(自不必多言)
* Vue全家桶套餐[Vue](https://cn.vuejs.org/index.html), [vue-router](https://router.vuejs.org/zh/), [axios](https://github.com/axios/axios), [vuex](https://vuex.vuejs.org/zh/)
* 组件库:[Element](https://element.eleme.cn/#/zh-CN)
* 基于Element的中后台管理基础项目[vue-element-admin](https://panjiachen.github.io/vue-element-admin-site/zh/)
* ES6 语法
## 项目特性
* 基于开源项目[vue-element-admin](https://panjiachen.github.io/vue-element-admin-site/zh/)的基础模板[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
* 在**vue-admin-template**项目基础上增加了vue-element-admin具有的多页签、菜单搜索、全屏显示、调整布局大小等功能
* 登录、权限、接口对接上与diboot-v2相关组件构建的后端应用无缝集成且开箱可用
* 提取CRUD页面相关通用属性与方法到mixins文件中少写代码多做事情
* 菜单到按钮级别的细粒度权限控制;
* 智能化的权限配置方案;
* 自动化的token交换方案
* 预置多种常用请求方式,轻松完成异步文件下载等;
* 数据字典管理功能;
* 登录人员管理界面;
* 角色与权限管理功能;
* 权限管理功能;
* 登录日志管理功能。

View File

@ -0,0 +1,38 @@
# 开始使用diboot-element-admin
## 新建项目
从Github仓库中clone最新的项目到本地
```bash
git clone https://github.com/dibo-software/diboot-element-admin my-admin
cd my-admin
```
## 安装依赖
项目根目录下执行以下命令,安装项目所需依赖
```bash
yarn
# OR
npm install
```
## 检查接口前缀
* 开发环境下需检查项目根目录下 .env.development 文件内配置项**VUE_APP_BASE_API**该配置项需要与后端接口前缀即context-path相同默认为/api
* 生产环境下需检查项目根目录下 .env.production 文件内相关配置项。
## 启动项目
执行以下命令启动项目
```bash
yarn server
# OR
npm run server
```
运行完成后,点击命令行提示出的地址,打开页面成功,项目启动就完成了。

View File

@ -0,0 +1,63 @@
# 接口请求
## 相关配置
我们在 [vue-element-admin](https://panjiachen.github.io/vue-element-admin-site/zh/) 项目对axios配置的基础上添加了一些增强性的更改与扩展这些更改都是在 **src/utils/request.js**中完成的。
* 常量配置:
* **BASE_URL**接口基础路径对应后端的context-path配置
* **JWT_HEADER_KEY**登录成功后请求头中携带token的字段后端默认为**authtoken**
* **TOKEN_REFRESH_EXPIRE**token超时自动刷新的重试间隔时间分钟建议设置为 后端设置的登录过期时间的 1/8
* axios相关配置
* 在请求拦截器与响应拦截器中我们已经对token相关流程以及文件下载和遇到请求异常提示错误消息等做了处理如果您需要对其进行自定义更改这些配置即可
## 接口扩展
我们对axios的默认调用方式进行了一些扩展主要包括了常用的一些接口请求函数。
* 调用方式如下:
```javascript
import { dibootApi } from '@/utils/request'
async getData() {
const params = {name: 'xxx'}
const res = await dibootApi.get(`/test/getData`, params)
if (res.code === 0) {
...
} else {
...
}
}
// 或
getData() {
const params = {name: 'xxx'}
dibootApi.get(`/test/getData`, params).then(res => {
if (res.code === 0) {
...
} else {
...
}
})
}
```
* 已有函数:
* **dibootApi.get**
* GET方式请求接口传入 {} 类型对象
* **dibootApi.post**
* POST方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.put**
* PUT方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.delete**
* DELETE方式请求接口传入 {} 类型数据
* 以JSON格式提交
* **dibootApi.upload**
* POST方式请求接口传入 formData 对象
* **dibootApi.download**
* GET方式请求接口传入 {} 类型对象
* 返回arraybuffer类型数据
* **dibootApi.postDownload**
* POST方式请求接口传入 {} 类型对象
* 返回arraybuffer类型数据
* 常用语复杂参数提交导出

View File

@ -0,0 +1,120 @@
# 权限控制
## 简介
* 该项目使用的权限控制模型为RBAC角色权限模型
* 支持按钮级别的权限控制;
* 权限分发路径:菜单/按钮/权限-->树状层级结构的权限集-->角色-->角色列表-->用户;
* 在添加新的需要进行权限控制的页面后,需要进行该页面权限的配置,然后在相关角色中对该权限进行授权;
## 菜单权限配置
菜单如果要进行权限控制,一定需要添加在 **src/router/index.js** 文件中的 asyncRoutes 中,且需要配置 meta 属性中的 permission 列表,如下:
```javascript
{
path: 'iamLoginTrace/list',
name: 'IamLoginTraceList',
component: () => import('@/views/system/iamLoginTrace/list'),
meta: { title: '登录日志查看', permission: ['IamLoginTrace'] }
}
```
## 按钮权限配置
按钮的权限控制,是通过指令控制该按钮是否显示来实现的,一共具有三个指令来控制是否按钮是否显示。
* v-permission
* 将该按钮或元素在具有权限列表中的某条权限时显示出来;
* 可以添加多条按钮权限编码,有一条权限存在,就将显示该按钮,比如 v-permission="['update', 'delete']"标识当具有update或者delete中的一种权限时该按钮将会显示。
* v-permission-again
* 与上述v-permission指令功能一样用于为同一按钮同时设置两条与逻辑的指令即 v-permission 与 v-permission-again 这两条指令的权限都存在时,才显示该按钮或元素。
```html
<span
v-permission="['detail']"
v-permission-again="['update', 'delete']"
>
<el-divider
direction="vertical"
/>
</span>
```
* v-permission-missing
* 对该按钮或元素在具有权限的时候隐藏;
* 可以添加多条权限编码,有一条权限存在,就将隐藏该按钮,如下代码就将会在没有详情、更新、删除权限时,将操作栏显示为 -
```html
<span v-permission-missing="['detail', 'update', 'delete']">
-
</span>
```
上述按钮权限配置,可以在已有的相关功能中找到:
```html
<template slot-scope="{row}">
<el-button
v-permission="['detail']"
type="text"
@click="$refs.detail.open(row.id)"
>
详情
</el-button>
<span
v-permission="['detail']"
v-permission-again="['update', 'delete']"
>
<el-divider
direction="vertical"
/>
</span>
<el-dropdown
v-permission="['update', 'delete']"
@command="command => menuCommand(command, row)"
>
<el-button type="text">
更多<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-permission="['update']"
command="update"
icon="el-icon-edit"
>
更新
</el-dropdown-item>
<el-dropdown-item
v-permission="['delete']"
command="delete"
icon="el-icon-delete"
>
删除
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<span v-permission-missing="['detail', 'update', 'delete']">
-
</span>
</template>
```
## 菜单权限管理
* 使用具有超级管理员权限的账号登入项目;
* 打开 系统管理-->菜单权限管理 页面,可看到已有权限列表:
![权限列表页面](./images/permission-list.png)
* 点击新建,开始配置新增页面的权限,下图是已有权限配置好之后的表单示例:
![权限表单页面](./images/permission-form.png)
* 菜单编码对应上述**菜单权限配置**中的permission列表中的值默认我们只在meta的permission中配置一个值与此处对应即可
* 下方**按钮/权限里列表**配置中,按钮/权限编码,需要与上述添加在按钮的指令中的参数一致,这里默认配置了一些常用操作类型到数据字典中,如果有新的操作按钮,可以到数据字典的**前端按钮/权限编码**中进行增加;
* 在权限配置中,对于常用的权限,都可以通过点击和选取来完成对菜单和按钮权限的配置;
* 在配置好前端菜单与接口列表,和前端按钮与接口列表后,也完成了前端菜单与按钮和后端接口的关系绑定,之后在对角色设置相关权限后,控制前端界面是否显示的同时,也控制了该角色下的用户对后端对应接口的访问权限;
* 在权限配置完成后,如果需要更改权限显示顺序,可以点击该列表页面右上角“排序”按钮进行权限拖拽排序操作。
## 角色授权管理
* 打开 系统管理-->角色权限管理 页面,可看到当前已有角色列表;
* 新建角色 或 选择需要更改授权的角色进行编辑操作;
* 对需要授权的权限进行选中,不需要的权限取消即可,操作直观简便。
![角色授权页面](./images/role-permission-form.png)

View File

@ -0,0 +1,61 @@
# 添加页面
添加页面需要添加页面相关的Vue组件与相对应的路由配置
## 添加Vue组件
* 在**src/views**文件夹下创建页面对应的文件夹以及对应的页面组件文件
## 添加路由配置
* 后台菜单是根据路由配置自动生成的,具体可参考[路由和侧边栏](https://panjiachen.github.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html)
* 在**src/router/index.js**文件中,可以配置需要新增页面的路由。
* 对于需要进行权限控制的菜单需要放到asyncRoutes中进行配置其他不需要进行权限控制或所有人可用的菜单可以放到constantRoutes中。
* 路由配置方式,可参考已有配置,如下:
```javascript
// 系统管理
{
path: '/system',
component: Layout,
redirect: '/system/dictionary/list',
name: 'System',
meta: { title: '系统管理', icon: 'dashboard' },
children: [
{
path: 'dictionary/list',
name: 'DictList',
component: () => import('@/views/system/dictionary/list'),
meta: { title: '数据字典管理', permission: ['Dictionary'] }
},
{
path: 'iamUser/list',
name: 'IamUserList',
component: () => import('@/views/system/iamUser/list'),
meta: { title: '系统用户管理', permission: ['IamUser'] }
},
{
path: 'iamRole/list',
name: 'IamRoleList',
component: () => import('@/views/system/iamRole/list'),
meta: { title: '角色权限管理', permission: ['IamRole'] }
},
{
path: 'iamFrontendPermission/list',
name: 'IamFrontendPermission',
component: () => import('@/views/system/iamFrontendPermission/list'),
meta: { title: '菜单权限管理', permission: ['IamFrontendPermission'] }
},
{
path: 'iamLoginTrace/list',
name: 'IamLoginTraceList',
component: () => import('@/views/system/iamLoginTrace/list'),
meta: { title: '登录日志查看', permission: ['IamLoginTrace'] }
}
]
}
```
* permission为显示该菜单所需要该用户具有的权限码这个码会在稍后配置系统权限的时候自动代入。
* 如果不配置permission将不会对该菜单进行权限验证所有用户都可以访问该页面如果permission的列表中配置了多个权限码那么具有其中一个就将会具有当前菜单的访问权限。
* 配置完成后,就可以对这些页面进行访问了(如果配置了权限码,可以先使用具有管理员权限的账号访问到)。

View File

@ -0,0 +1,9 @@
# diboot-file: 文件处理组件
## 组件特性
* EasyExcel轻量封装支持Java注解校验与@BindDict注解实现字典name-value转换提供完善的校验错误提示
* 封装常用的文件本地存储、上传下载、图片压缩水印等常用处理
* Starter启动自动安装依赖的数据表
* 启用devtools自动生成初始样例controller代码到本地
> 组件依赖的数据表upload_file在组件starter初次启动时将自动初始化。

View File

@ -0,0 +1,143 @@
# diboot-file 使用说明
## 1、引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-file-spring-boot-starter:2.0.5")
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-file-spring-boot-starter</artifactId>
<version>2.0.5</version>
</dependency>
~~~
diboot-file会自动依赖以下jar包无需重复引入
* commons-fileupload: 1.4
* easyexcel:2.1.x
* okhttp:4.3.x
* thumbnailator: 0.4.9 (图片压缩,不需要可剔除)
* easy-captcha: 1.6.x (验证码,不需要可剔除)
> file组件依赖一张表 upload_file ,用于存储文件/附件上传记录。该表将由diboot-file-starter初次加载时自动初始化。
> 如果使用diboot-devtools还需要引入devtools相关依赖可自动生成相关的上传下载controller。
## 2、参数配置
diboot-file组件有以下一个配置项用于设置本地文件的存储起始目录子目录会按分类与日期自动创建
配置参数: 文件的本地存储路径
~~~
files.storage.directory=/myfile
~~~
## 3. 使用说明
### 3.1 EasyExcel的增强优化
* 支持基于Java validator注解的自动数据校验支持@BindDict注解自动转换字典显示值-存储值
使用示例:
~~~java
@NotNull(message = "用户状态不能为空") // 自动校验
@BindDict(type = "USER_STATUS") // 自动转换数据字典 label-value
@ExcelProperty(value = "状态", index = 4, converter = DictConverter.class)
private String userStatus;
~~~
* 轻量封装增强的Excel Data Listener
继承后只需要实现自定义校验additionalValidate() 和 保存数据的saveData()方法。
~~~
// 适用于已知固定表头的excel读取
FixedHeadExcelListener
// 适用于动态表头的excel读取
DynamicHeadExcelListener
~~~
* 优化汇总校验错误的提示内容
校验失败提示示例:
~~~json
{
"msg": "数据校验不通过: 1行3列: '测试' 无匹配字典定义; 2行: 姓名不能为空职位长度不能超过10",
"code": 500
}
~~~
### 3.2 常用文件处理封装
* 提供BaseFileController用于文件上传下载的Controller继承
使用示例:
~~~java
//上传文件
@PostMapping("/upload")
public JsonResult upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception{
// 保存文件并创建UploadFile上传记录
return super.uploadFile(file, Dictionary.class, request);
}
// 下载文件
@GetMapping("/download/{fileUuid}")
public JsonResult download(@PathVariable("fileUuid")String fileUuid, HttpServletResponse response) throws Exception {
UploadFile uploadFile = uploadFileService.getEntity(fileUuid);
if(uploadFile == null){
return JsonResult.FAIL_VALIDATION("文件不存在");
}
// 下载
HttpHelper.downloadLocalFile(uploadFile.getStoragePath(), "导出文件.txt", response);
return null;
}
// 自定义文件存储
@Override
protected <T> UploadFile saveFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception {
// 自定义文件存储,默认本地存储
return super.saveFile(file, entityClass, request);
}
~~~
* 提供BaseExcelFileController用于Excel导入导出类的Controller继承
使用示例:
~~~java
//预览excel数据
@PostMapping("/preview")
public JsonResult preview(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
return super.excelPreview(file, request);
}
//预览无校验错误后 提交
@PostMapping("/previewSave")
public <T> JsonResult previewSave(@RequestParam("previewFileName")String previewFileName, @RequestParam("originFileName")String originFileName, HttpServletRequest request) throws Exception {
return super.excelPreviewSave(Department.class, previewFileName, originFileName, request);
}
//无预览 直接导入
@PostMapping("/import")
public <T> JsonResult upload(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception {
return super.uploadExcelFile(file, Department.class, request);
}
~~~
### 3.3 其他常用文件处理相关工具类
~~~
// 保存上传文件至本地
FileHelper.saveFile(MultipartFile file, String fileName)
// 下载本地文件
HttpHelper.downloadLocalFile(String localFilePath, String exportFileName, HttpServletResponse response)
// 下载网络文件至本地
HttpHelper.downloadHttpFile(String fileUrl, String targetFilePath)
// 图片保存,压缩,加水印等 需依赖Thumbnails组件
ImageHelper.saveImage(MultipartFile file, String imgName)
ImageHelper.generateThumbnail(String sourcePath, String targetPath, int width, int height)
ImageHelper.addWatermark(String filePath, String watermark)
// zip压缩
ZipHelper.zipFile(String srcRootDir, File file, ZipOutputStream zos, String... matchKeyword)
~~~
## 4 样例参考 - [diboot-file-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-file-example)

View File

@ -0,0 +1,14 @@
# diboot-file: 文件处理组件
## 1、扩展其他存储方式
* 继承BaseFileController重写saveFile方法
使用示例:
~~~java
// 自定义文件存储
@Override
protected <T> UploadFile saveFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception {
// 自定义文件存储,默认本地存储
return super.saveFile(file, entityClass, request);
}
~~~

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -3,15 +3,18 @@
## 组件特性
* 开箱即用的RBAC角色权限模型
* 基于JWT的认证授权支持申请token、刷新token
* 简化的BindPermission注解支持菜单+操作两级权限控制
* 支持BindPermission注解自动收集并更新至数据表
* 简化的BindPermission注解支持兼容shiro的简化权限绑定与自动鉴权
* 自动提取需要验证的后端接口, 借助前端功能方便绑定前后端菜单按钮权限
* 预置用户名密码登录(密码带盐加密), 并支持多种登录方式扩展
* 预置默认用户实体,并支持灵活替换用户类型
* 默认启用内存缓存,并支持自定义缓存实现类
* starter启动自动安装依赖的数据表启用devtools还可自动生成初始controller代码到本地
## 角色权限模型说明
![角色权限模型](./images/iam-base.jpg)
![角色权限模型](./images/iam-base.png)
基于“用户-角色-权限”的基础模型扩展“账号”实体,以支持多种登录方式。
组件包含了与此模型相关的后端代码且依赖的数据结构在组件starter初次启动时将自动初始化。
> 组件包含了与此模型相关的后端代码且依赖的数据结构在组件starter初次启动时将自动初始化。
> 配套的前端基础框架有antd、element-ui前端代码参考: diboot-antd-admin 及 diboot-element-admin

View File

@ -3,14 +3,14 @@
## 1、引入依赖
Gradle:
~~~gradle
compile("com.diboot:diboot-iam-base-spring-boot-starter:2.0.4-RC2")
compile("com.diboot:diboot-iam-base-spring-boot-starter:2.0.5")
~~~
或Maven
~~~xml
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-iam-base-spring-boot-starter</artifactId>
<version>2.0.4-RC2</version>
<version>2.0.5</version>
</dependency>
~~~
> 配置了数据库连接初次启动时iam-base starter组件会自初始化生成相关的表及初始数据。
@ -67,7 +67,7 @@ public class IamUserController extends BaseCrudMappingRestController<IamUser, Ia
}
~~~
* BindPermission注解支持自动更新维护程序启动后异步收集权限注解并比对、更新数据库权限表
* * BindPermission注解支持自动提取需要认证的接口列表提供给前端进行快捷绑定
## 5、登录/注册/退出
* 登录:

View File

@ -31,5 +31,4 @@ Employee currentUser = IamSecurityUtils.getCurrentUser();
需要创建缓存实现类实现CacheManager接口并配置参数diboot.iam.cache-manager-class为你的缓存类。
```
diboot.iam.cache-manager-class=com.xxx.MyCacheManager
```
```

View File

@ -0,0 +1,11 @@
# 优秀案例
## 说明
* 优秀案例展示将展示Diboot用户们基于此开发的系列产品列表。
* 如果您也使用我们的Diboot开发了相关产品可以通过[Diboot优秀案例申请](http://s.dibo.ltd/#/f/748dc26142fc46af946ce9acea0aff76)来提交您的产品案例。
* 对于您提交的案例申请,我们将尽快更新到该列表中,感谢您的支持。
## 案例列表
* [帝博数据统计平台](http://s.dibo.ltd/#/g)

View File

@ -164,7 +164,7 @@ async-settle@^1.0.0:
dependencies:
async-done "^1.2.2"
atob@^2.1.1:
atob@^2.1.2:
version "2.1.2"
resolved "https://registry.npm.taobao.org/atob/download/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=
@ -207,6 +207,13 @@ binary-extensions@^1.0.0:
resolved "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha1-WYr+VHVbKGilMw0q/51Ou1Mgm2U=
bindings@^1.5.0:
version "1.5.0"
resolved "https://registry.npm.taobao.org/bindings/download/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
integrity sha1-EDU8npRTNLwFEabZCzj7x8nFBN8=
dependencies:
file-uri-to-path "1.0.0"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -281,9 +288,9 @@ chokidar@^2.0.0:
fsevents "^1.2.7"
chownr@^1.1.1:
version "1.1.3"
resolved "https://registry.npm.taobao.org/chownr/download/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
integrity sha1-Qtg31SOWiNVfMDADpQgjD6ZycUI=
version "1.1.4"
resolved "https://registry.npm.taobao.org/chownr/download/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha1-b8nXtC0ypYNZYzdmbn0ICE2izGs=
class-utils@^0.3.5:
version "0.3.6"
@ -429,7 +436,7 @@ debug@^3.2.6:
decamelize@^1.1.1:
version "1.2.0"
resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1580010393599&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decode-uri-component@^0.2.0:
@ -532,7 +539,7 @@ error-ex@^1.2.0:
es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50:
version "0.10.53"
resolved "https://registry.npm.taobao.org/es5-ext/download/es5-ext-0.10.53.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fes5-ext%2Fdownload%2Fes5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
resolved "https://registry.npm.taobao.org/es5-ext/download/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1"
integrity sha1-k8WjrP2+8nUiCtcmRK0C7hg2jeE=
dependencies:
es6-iterator "~2.0.3"
@ -587,9 +594,9 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
homedir-polyfill "^1.0.1"
ext@^1.1.2:
version "1.2.0"
resolved "https://registry.npm.taobao.org/ext/download/ext-1.2.0.tgz?cache=0&sync_timestamp=1573137628152&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fext%2Fdownload%2Fext-1.2.0.tgz#8dd8d2dd21bcced3045be09621fa0cbf73908ba4"
integrity sha1-jdjS3SG8ztMEW+CWIfoMv3OQi6Q=
version "1.4.0"
resolved "https://registry.npm.taobao.org/ext/download/ext-1.4.0.tgz?cache=0&sync_timestamp=1575036693653&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fext%2Fdownload%2Fext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
integrity sha1-ia56BxWPedNVF4gpBDJAd+Q3kkQ=
dependencies:
type "^2.0.0"
@ -637,6 +644,11 @@ fancy-log@^1.3.2:
parse-node-version "^1.0.0"
time-stamp "^1.0.0"
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/file-uri-to-path/download/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha1-VTp7hEb/b2hDWcRF8eN6BdrMM90=
fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.npm.taobao.org/fill-range/download/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
@ -720,7 +732,7 @@ fragment-cache@^0.2.1:
fs-minipass@^1.2.5:
version "1.2.7"
resolved "https://registry.npm.taobao.org/fs-minipass/download/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
resolved "https://registry.npm.taobao.org/fs-minipass/download/fs-minipass-1.2.7.tgz?cache=0&sync_timestamp=1579628689954&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffs-minipass%2Fdownload%2Ffs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
integrity sha1-zP+FcIQef+QmVpPaiJNsVa7X98c=
dependencies:
minipass "^2.6.0"
@ -739,12 +751,12 @@ fs.realpath@^1.0.0:
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@^1.2.7:
version "1.2.9"
resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-1.2.9.tgz?cache=0&sync_timestamp=1573319474313&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
integrity sha1-P17WZYPM1vQAtaANtvfoYTY+OI8=
version "1.2.11"
resolved "https://registry.npm.taobao.org/fsevents/download/fsevents-1.2.11.tgz?cache=0&sync_timestamp=1580708699417&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3"
integrity sha1-Z79X9HWPAu3oj7KhcS/vTRU1i+M=
dependencies:
bindings "^1.5.0"
nan "^2.12.1"
node-pre-gyp "^0.12.0"
function-bind@^1.1.1:
version "1.1.1"
@ -952,9 +964,9 @@ homedir-polyfill@^1.0.1:
parse-passwd "^1.0.0"
hosted-git-info@^2.1.4:
version "2.8.5"
resolved "https://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.8.5.tgz?cache=0&sync_timestamp=1570493570687&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhosted-git-info%2Fdownload%2Fhosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
integrity sha1-dZz88sTRVq3lmwst+r3cQqa5xww=
version "2.8.8"
resolved "https://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.8.8.tgz?cache=0&sync_timestamp=1583018405733&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhosted-git-info%2Fdownload%2Fhosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha1-dTm9S8Hg4KiVgVouAmJCCxKFhIg=
iconv-lite@^0.4.4:
version "0.4.24"
@ -1034,7 +1046,7 @@ is-binary-path@^1.0.0:
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz?cache=0&sync_timestamp=1569904999656&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-buffer%2Fdownload%2Fis-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
resolved "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha1-76ouqdqg16suoTqXsritUf776L4=
is-data-descriptor@^0.1.4:
@ -1167,7 +1179,7 @@ is-windows@^1.0.1, is-windows@^1.0.2:
isarray@1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz?cache=0&sync_timestamp=1562592125418&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
isexe@^2.0.0:
@ -1199,27 +1211,27 @@ just-debounce@^1.0.0:
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-3.2.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
dependencies:
is-buffer "^1.1.5"
kind-of@^4.0.0:
version "4.0.0"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-4.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
dependencies:
is-buffer "^1.1.5"
kind-of@^5.0.0, kind-of@^5.0.2:
version "5.1.0"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-5.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
integrity sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=
kind-of@^6.0.0, kind-of@^6.0.2:
version "6.0.2"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha1-ARRrNqYhjmTljzqNZt5df8b20FE=
version "6.0.3"
resolved "https://registry.npm.taobao.org/kind-of/download/kind-of-6.0.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkind-of%2Fdownload%2Fkind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha1-B8BQNKbDSfoG4k+jWqdttFgM5N0=
last-run@^1.1.0:
version "1.1.1"
@ -1336,9 +1348,9 @@ minimist@0.0.8:
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
version "1.2.5"
resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=
minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
version "2.9.0"
@ -1408,9 +1420,9 @@ nanomatch@^1.2.9:
to-regex "^3.0.1"
needle@^2.2.1:
version "2.4.0"
resolved "https://registry.npm.taobao.org/needle/download/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
integrity sha1-aDPnSXXERGQlkOFadQKIxfk5tXw=
version "2.3.3"
resolved "https://registry.npm.taobao.org/needle/download/needle-2.3.3.tgz?cache=0&sync_timestamp=1583154705070&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fneedle%2Fdownload%2Fneedle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117"
integrity sha1-oEGtHQSocbDrtmb0C6rx+0eGcRc=
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
@ -1421,10 +1433,10 @@ next-tick@~1.0.0:
resolved "https://registry.npm.taobao.org/next-tick/download/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
node-pre-gyp@^0.12.0:
version "0.12.0"
resolved "https://registry.npm.taobao.org/node-pre-gyp/download/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
integrity sha1-ObpLsUOdoDApX4meO1ILd4V2YUk=
node-pre-gyp@*:
version "0.14.0"
resolved "https://registry.npm.taobao.org/node-pre-gyp/download/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
integrity sha1-mgWWUzuHcom8rU4UOYLKPZBN3IM=
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
@ -1435,12 +1447,12 @@ node-pre-gyp@^0.12.0:
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4"
tar "^4.4.2"
nopt@^4.0.1:
version "4.0.1"
resolved "https://registry.npm.taobao.org/nopt/download/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
version "4.0.3"
resolved "https://registry.npm.taobao.org/nopt/download/nopt-4.0.3.tgz?cache=0&sync_timestamp=1583704592540&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnopt%2Fdownload%2Fnopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
integrity sha1-o3XK2dAv2SEnjZVMIlTVqlfhXkg=
dependencies:
abbrev "1"
osenv "^0.1.4"
@ -1475,17 +1487,25 @@ now-and-later@^2.0.0:
once "^1.3.2"
npm-bundled@^1.0.1:
version "1.0.6"
resolved "https://registry.npm.taobao.org/npm-bundled/download/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
integrity sha1-57qarc75YrthJI+RchzZMrP+a90=
version "1.1.1"
resolved "https://registry.npm.taobao.org/npm-bundled/download/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
integrity sha1-Ht1XCGWpTNsbyCIHdeKUZsn7I0s=
dependencies:
npm-normalize-package-bin "^1.0.1"
npm-normalize-package-bin@^1.0.1:
version "1.0.1"
resolved "https://registry.npm.taobao.org/npm-normalize-package-bin/download/npm-normalize-package-bin-1.0.1.tgz?cache=0&sync_timestamp=1575936472299&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnpm-normalize-package-bin%2Fdownload%2Fnpm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
integrity sha1-bnmkHyP9I1wGIyGCKNp9nCO49uI=
npm-packlist@^1.1.6:
version "1.4.6"
resolved "https://registry.npm.taobao.org/npm-packlist/download/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4"
integrity sha1-U7o+0R+FIwefFFc3bdN57k6kL/Q=
version "1.4.8"
resolved "https://registry.npm.taobao.org/npm-packlist/download/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha1-Vu5swTW5+YrT1Rwcldoiu7my7z4=
dependencies:
ignore-walk "^3.0.1"
npm-bundled "^1.0.1"
npm-normalize-package-bin "^1.0.1"
npmlog@^4.0.2:
version "4.1.2"
@ -1761,9 +1781,9 @@ read-pkg@^1.0.0:
path-type "^1.0.0"
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=
version "2.3.7"
resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha1-Hsoc9xGu+BTAT2IlKjamL2yyO1c=
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
@ -1874,9 +1894,9 @@ resolve-url@^0.2.1:
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0:
version "1.12.0"
resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.12.0.tgz?cache=0&sync_timestamp=1564641434608&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
integrity sha1-P8ZEo1yEpIVUYJ/ybsUrZvpXffY=
version "1.15.1"
resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.15.1.tgz?cache=0&sync_timestamp=1580944709531&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8"
integrity sha1-J73N7/6vLWJEuVuw+fS0ZTRR8+g=
dependencies:
path-parse "^1.0.6"
@ -1887,7 +1907,7 @@ ret@~0.1.10:
rimraf@^2.6.1:
version "2.7.1"
resolved "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
resolved "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz?cache=0&sync_timestamp=1581229948248&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frimraf%2Fdownload%2Frimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w=
dependencies:
glob "^7.1.3"
@ -1928,7 +1948,7 @@ semver-greatest-satisfied-range@^1.1.0:
"semver@2 || 3 || 4 || 5", semver@^5.3.0:
version "5.7.1"
resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz?cache=0&sync_timestamp=1565627367398&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha1-qVT5Ma66UI0we78Gnv8MAclhFvc=
set-blocking@^2.0.0, set-blocking@~2.0.0:
@ -1982,11 +2002,11 @@ snapdragon@^0.8.1:
use "^3.1.0"
source-map-resolve@^0.5.0:
version "0.5.2"
resolved "https://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
integrity sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=
version "0.5.3"
resolved "https://registry.npm.taobao.org/source-map-resolve/download/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
integrity sha1-GQhmvs51U+H48mei7oLGBrVQmho=
dependencies:
atob "^2.1.1"
atob "^2.1.2"
decode-uri-component "^0.2.0"
resolve-url "^0.2.1"
source-map-url "^0.4.0"
@ -2059,9 +2079,9 @@ stream-exhaust@^1.0.1:
integrity sha1-rNrI2lnvK8HheiwMz2wyDRIOVV0=
stream-shift@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
version "1.0.1"
resolved "https://registry.npm.taobao.org/stream-shift/download/stream-shift-1.0.1.tgz?cache=0&sync_timestamp=1576147157429&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstream-shift%2Fdownload%2Fstream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
integrity sha1-1wiCgVWasneEJCebCHfaPDktWj0=
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
@ -2082,7 +2102,7 @@ string-width@^1.0.1, string-width@^1.0.2:
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz?cache=0&sync_timestamp=1565170823020&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring_decoder%2Fdownload%2Fstring_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=
dependencies:
safe-buffer "~5.1.0"
@ -2121,9 +2141,9 @@ sver-compat@^1.5.0:
es6-iterator "^2.0.1"
es6-symbol "^3.1.1"
tar@^4:
tar@^4.4.2:
version "4.4.13"
resolved "https://registry.npm.taobao.org/tar/download/tar-4.4.13.tgz?cache=0&sync_timestamp=1570286254496&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftar%2Fdownload%2Ftar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
resolved "https://registry.npm.taobao.org/tar/download/tar-4.4.13.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftar%2Fdownload%2Ftar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha1-Q7NkvFKIjVVSmGN7ENYHkCVKtSU=
dependencies:
chownr "^1.1.1"
@ -2144,7 +2164,7 @@ through2-filter@^3.0.0:
through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
version "2.0.5"
resolved "https://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
resolved "https://registry.npm.taobao.org/through2/download/through2-2.0.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fthrough2%2Fdownload%2Fthrough2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
integrity sha1-AcHjnrMdB8t9A6lqcIIyYLIxMs0=
dependencies:
readable-stream "~2.3.6"
@ -2398,7 +2418,7 @@ yallist@^3.0.0, yallist@^3.0.3:
yargs-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-5.0.0.tgz?cache=0&sync_timestamp=1572648717575&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
resolved "https://registry.npm.taobao.org/yargs-parser/download/yargs-parser-5.0.0.tgz?cache=0&sync_timestamp=1584134734937&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fyargs-parser%2Fdownload%2Fyargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=
dependencies:
camelcase "^3.0.0"

View File

@ -0,0 +1,122 @@
# diboot-file: 文件处理组件
## 组件特性
* EasyExcel轻量封装支持自动校验与字典转换优化校验错误的提示内容
* 封装常用的文件本地存储、上传下载、图片压缩水印等常用处理
* Starter启动自动安装依赖的数据表
### 1. EasyExcel增强优化
* 支持基于Java validator注解的自动数据校验支持@BindDict注解自动转换字典显示值-存储值
使用示例:
~~~java
@NotNull(message = "用户状态不能为空") // 自动校验
@BindDict(type = "USER_STATUS") // 自动转换数据字典 label-value
@ExcelProperty(value = "状态", index = 4, converter = DictConverter.class)
private String userStatus;
~~~
* 轻量封装增强的Excel Data Listener
继承后只需要实现自定义校验additionalValidate() 和 保存数据的saveData()方法。
~~~
// 适用于已知固定表头的excel读取
FixedHeadExcelListener
// 适用于动态表头的excel读取
DynamicHeadExcelListener
~~~
* 优化汇总校验错误的提示内容
校验失败提示示例:
~~~json
{
"msg": "数据校验不通过: 1行3列: '测试' 无匹配字典定义; 2行: 姓名不能为空职位长度不能超过10",
"code": 500
}
~~~
### 2. 常用文件处理封装
* 提供BaseFileController用于文件上传下载的Controller继承
使用示例:
~~~java
//上传文件
@PostMapping("/upload")
public JsonResult upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception{
// 保存文件并创建UploadFile上传记录
return super.uploadFile(file, Dictionary.class, request);
}
// 下载文件
@GetMapping("/download/{fileUuid}")
public JsonResult download(@PathVariable("fileUuid")String fileUuid, HttpServletResponse response) throws Exception {
UploadFile uploadFile = uploadFileService.getEntity(fileUuid);
if(uploadFile == null){
return JsonResult.FAIL_VALIDATION("文件不存在");
}
// 下载
HttpHelper.downloadLocalFile(uploadFile.getStoragePath(), "导出文件.txt", response);
return null;
}
// 自定义文件存储
@Override
protected <T> UploadFile saveFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception {
// 自定义文件存储,默认本地存储
return super.saveFile(file, entityClass, request);
}
~~~
* 提供BaseExcelFileController用于Excel导入导出类的Controller继承
使用示例:
~~~java
//预览excel数据
@PostMapping("/preview")
public JsonResult preview(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception {
return super.excelPreview(file, request);
}
//预览无校验错误后 提交
@PostMapping("/previewSave")
public <T> JsonResult previewSave(@RequestParam("previewFileName")String previewFileName, @RequestParam("originFileName")String originFileName, HttpServletRequest request) throws Exception {
return super.excelPreviewSave(Department.class, previewFileName, originFileName, request);
}
//无预览 直接导入
@PostMapping("/import")
public <T> JsonResult upload(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception {
return super.uploadExcelFile(file, Department.class, request);
}
~~~
* 其他常用文件处理相关工具类
~~~
// 保存上传文件至本地
FileHelper.saveFile(MultipartFile file, String fileName)
// 下载本地文件
HttpHelper.downloadLocalFile(String localFilePath, String exportFileName, HttpServletResponse response)
// 下载网络文件至本地
HttpHelper.downloadHttpFile(String fileUrl, String targetFilePath)
// 图片保存,压缩,加水印等 需依赖Thumbnails组件
ImageHelper.saveImage(MultipartFile file, String imgName)
ImageHelper.generateThumbnail(String sourcePath, String targetPath, int width, int height)
ImageHelper.addWatermark(String filePath, String watermark)
// zip压缩
ZipHelper.zipFile(String srcRootDir, File file, ZipOutputStream zos, String... matchKeyword)
~~~
### 3. Starter启动自动安装依赖的数据表
file组件依赖一张表 upload_file ,用于存储文件/附件上传记录。该表将由diboot-file-starter初次加载时自动初始化。
diboot-file组件有以下一个配置项用于设置本地文件的存储起始目录子目录会按分类与日期自动创建
*配置参数*:
~~~
files.storage.directory=/myfile
~~~
## 样例参考 - [diboot-file-example](https://github.com/dibo-software/diboot-v2-example/tree/master/diboot-file-example)

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>diboot-root</artifactId>
<groupId>com.diboot</groupId>
<version>2.0.5</version>
</parent>
<artifactId>diboot-file-spring-boot-starter</artifactId>
<version>2.0.5</version>
<packaging>jar</packaging>
<description>diboot file component project</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 依赖core-starter -->
<dependency>
<groupId>com.diboot</groupId>
<artifactId>diboot-core-spring-boot-starter</artifactId>
<version>2.0.5</version>
</dependency>
<!-- 文件上传 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!-- Excel处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.6</version>
</dependency>
<!-- http -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.3.1</version>
</dependency>
<!-- 图片压缩 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.9</version>
</dependency>
<!-- 验证码示例 -->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.dtd</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package com.diboot.file.config;
/**
* 文件组件相关常量定义
* @author mazc@dibo.ltd
* @version v2.0
* @date 2020/02/18
*/
public class Cons {
/**
* 文件路径分隔符
*/
public static final String FILE_PATH_SEPARATOR = "/";
public enum FILE_STATUS {
S,
F
}
}

View File

@ -0,0 +1,161 @@
package com.diboot.file.controller;
import com.diboot.core.config.BaseConfig;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.JsonResult;
import com.diboot.core.vo.Status;
import com.diboot.file.entity.UploadFile;
import com.diboot.file.excel.listener.FixedHeadExcelListener;
import com.diboot.file.util.ExcelHelper;
import com.diboot.file.util.FileHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Excel导入基类Controller
* @author Mazc@dibo.ltd
* @version 2.0
* @date 2020/02/20
*/
@Slf4j
public abstract class BaseExcelFileController extends BaseFileController {
// 初始文件名参数
protected static final String ORIGIN_FILE_NAME = "originFileName";
// 预览文件名参数
protected static final String PREVIEW_FILE_NAME = "previewFileName";
/***
* 获取对应的ExcelDataListener
* @return
*/
protected abstract FixedHeadExcelListener getExcelDataListener();
/***
* excel数据预览
* @param request
* @return
* @throws Exception
*/
public JsonResult excelPreview(MultipartFile file, HttpServletRequest request) throws Exception {
Map<String, Object> dataMap = new HashMap();
savePreviewExcelFile(file, request, dataMap);
return JsonResult.OK(dataMap);
}
/***
* 预览后提交保存
* @param request
* @param
* @return
* @throws Exception
*/
public <T> JsonResult excelPreviewSave(Class<T> entityClass, String previewFileName, String originFileName, HttpServletRequest request) throws Exception {
if(V.isEmpty(previewFileName) || V.isEmpty(originFileName)){
throw new BusinessException(Status.FAIL_INVALID_PARAM, "预览保存失败,参数 tempFileName 或 originFileName 未指定!");
}
String fileUid = S.substringBefore(previewFileName, ".");
String fullPath = FileHelper.getFullPath(previewFileName);
String ext = FileHelper.getFileExtByName(originFileName);
// 描述
String description = getString(request, "description");
// 保存文件上传记录
UploadFile uploadFile = new UploadFile().setUuid(fileUid)
.setFileName(originFileName).setStoragePath(fullPath).setFileType(ext)
.setRelObjType(entityClass.getSimpleName()).setDescription(description);
super.createUploadFile(uploadFile, request);
return JsonResult.OK();
}
/***
* 直接上传excel
* @param request
* @param
* @return
* @throws Exception
*/
public <T> JsonResult uploadExcelFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception {
checkIsExcel(file);
return super.uploadFile(file, entityClass, request);
}
/**
* 保存上传文件
* @param request
* @return
* @throws Exception
*/
private void savePreviewExcelFile(MultipartFile file, HttpServletRequest request, Map<String, Object> dataMap) throws Exception{
checkIsExcel(file);
// 保存文件到本地
UploadFile uploadFile = super.saveFile(file, getExcelDataListener().getExcelModelClass(), request);
// 预览
FixedHeadExcelListener listener = getExcelDataListener();
listener.setRequestParams(super.getParamsMap(request));
try {
ExcelHelper.previewReadExcel(uploadFile.getStoragePath(), listener);
}
catch (Exception e) {
log.warn("解析并校验excel文件失败", e);
if(V.notEmpty(e.getMessage())){
throw new Exception(e.getMessage());
}
throw e;
}
// 绑定属性到model
dataMap.put("header", listener.getFieldHeaders());
dataMap.put(ORIGIN_FILE_NAME, file.getOriginalFilename());
dataMap.put(PREVIEW_FILE_NAME, FileHelper.getFileName(uploadFile.getStoragePath()));
List dataList = listener.getDataList();
if(V.notEmpty(dataList) && dataList.size() > BaseConfig.getPageSize()){
dataList = dataList.subList(0, BaseConfig.getPageSize());
}
//最多返回前端十条数据
dataMap.put("dataList", dataList);
}
/**
* 保存文件之后的处理逻辑如解析excel
*/
@Override
protected int extractDataCount(String fileUuid, String fullPath, HttpServletRequest request) throws Exception{
FixedHeadExcelListener listener = getExcelDataListener();
listener.setUploadFileUuid(fileUuid);
listener.setPreview(false);
listener.setRequestParams(super.getParamsMap(request));
try{
ExcelHelper.readAndSaveExcel(fullPath, listener);
}
catch(Exception e){
log.warn("上传数据错误: "+ e.getMessage(), e);
if(V.notEmpty(e.getMessage())){
throw new Exception(e.getMessage());
}
throw e;
}
return listener.getDataList().size();
}
/**
* 检查是否为合法的excel文件
* @param file
* @throws Exception
*/
private void checkIsExcel(MultipartFile file) throws Exception{
if(V.isEmpty(file)) {
throw new BusinessException(Status.FAIL_INVALID_PARAM, "未获取待处理的excel文件");
}
String fileName = file.getOriginalFilename();
if (V.isEmpty(fileName) || !FileHelper.isExcel(fileName)) {
log.debug("非Excel类型: " + fileName);
throw new BusinessException(Status.FAIL_VALIDATION, "请上传合法的Excel格式文件");
}
}
}

View File

@ -0,0 +1,125 @@
package com.diboot.file.controller;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.diboot.core.controller.BaseController;
import com.diboot.core.exception.BusinessException;
import com.diboot.core.util.S;
import com.diboot.core.util.V;
import com.diboot.core.vo.JsonResult;
import com.diboot.core.vo.Pagination;
import com.diboot.core.vo.Status;
import com.diboot.file.entity.UploadFile;
import com.diboot.file.service.UploadFileService;
import com.diboot.file.util.FileHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Excel导入基类Controller
* @author Mazc@dibo.ltd
* @version 2.0
* @date 2020/02/20
*/
@Slf4j
public abstract class BaseFileController extends BaseController {
@Autowired
protected UploadFileService uploadFileService;
/***
* 获取文件上传记录
* <p>
* url参数示例: /${bindURL}?pageSize=20&pageIndex=1
* </p>
* @return JsonResult
* @throws Exception
*/
protected JsonResult getEntityListWithPaging(Wrapper queryWrapper, Pagination pagination) throws Exception {
// 查询当前页的数据
List entityList = uploadFileService.getEntityList(queryWrapper, pagination);
// 返回结果
return JsonResult.OK(entityList).bindPagination(pagination);
}
/***
* 直接上传文件
* @param request
* @param
* @return
* @throws Exception
*/
public <T> JsonResult uploadFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception {
if(file == null) {
throw new BusinessException(Status.FAIL_INVALID_PARAM, "未获取待处理的文件!");
}
String originFileName = file.getOriginalFilename();
if (V.isEmpty(originFileName) || !FileHelper.isValidFileExt(originFileName)) {
log.debug("非法的文件类型: " + originFileName);
throw new BusinessException(Status.FAIL_VALIDATION, "请上传合法的文件格式!");
}
// 保存文件
UploadFile uploadFile = saveFile(file, entityClass, request);
// 保存上传记录
createUploadFile(uploadFile, request);
// 返回结果
Map<String, String> dataMap = new HashMap<>();
dataMap.put("uuid", uploadFile.getUuid());
dataMap.put("accessUrl", uploadFile.getAccessUrl());
return JsonResult.OK(dataMap);
}
/**
* 保存文件
* @param file
* @param entityClass
* @param request
* @param <T>
* @return
* @throws Exception
*/
protected <T> UploadFile saveFile(MultipartFile file, Class<T> entityClass, HttpServletRequest request) throws Exception{
// 文件后缀
String originFileName = file.getOriginalFilename();
String ext = FileHelper.getFileExtByName(file.getOriginalFilename());
// 先保存文件
String fileUid = S.newUuid();
String newFileName = fileUid + "." + ext;
String storageFullPath = FileHelper.saveFile(file, newFileName);
UploadFile uploadFile = new UploadFile();
uploadFile.setUuid(fileUid).setFileName(originFileName).setFileType(ext);
uploadFile.setRelObjType(entityClass.getSimpleName()).setStoragePath(storageFullPath);
String description = getString(request, "description");
uploadFile.setDescription(description);
// 返回uploadFile对象
return uploadFile;
}
/**
* 保存上传文件信息
* @param uploadFile
* @param request
* @throws Exception
*/
protected void createUploadFile(UploadFile uploadFile, HttpServletRequest request) throws Exception{
// 保存文件之后的处理逻辑
int dataCount = extractDataCount(uploadFile.getUuid(), uploadFile.getStoragePath(), request);
uploadFile.setDataCount(dataCount);
// 保存文件上传记录
uploadFileService.createEntity(uploadFile);
}
/**
* 保存文件之后的处理逻辑如解析excel
*/
protected int extractDataCount(String fileUuid, String fullPath, HttpServletRequest request) throws Exception{
return 0;
}
}

View File

@ -0,0 +1,67 @@
package com.diboot.file.entity;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.diboot.core.entity.BaseEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;
/**
* file基础父类
* @author wangyl@dibo.ltd
* @version v2.0
* @date 2019/07/18
*/
@Getter @Setter @Accessors(chain = true)
public class UploadFile extends BaseEntity {
private static final long serialVersionUID = 201L;
// 废弃默认主键
@TableField(exist = false)
private Long id;
// 声明新主键uuid
@TableId(type = IdType.UUID)
private String uuid;
@NotNull(message = "关联对象类不能为空!")
@TableField
private String relObjType = null;
@TableField
@NotNull(message = "关联对象ID不能为空")
private Long relObjId;
@TableField
@NotNull(message = "文件名不能为空!")
@Length(max = 100, message = "文件名长度超出了最大限制!")
private String fileName;
@TableField
@JSONField(serialize = false)
private String storagePath;
/**
* 访问URL
*/
@TableField
private String accessUrl;
@TableField
private String fileType;
/**
* 文件包含记录数
*/
@TableField
private int dataCount = 0;
@TableField
@Length(max = 200, message = "备注长度超出了最大限制!")
private String description;
}

View File

@ -0,0 +1,51 @@
package com.diboot.file.excel;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.fastjson.annotation.JSONField;
import java.io.Serializable;
/***
* excel数据导入导出实体基类
* @auther wangyl@dibo.ltd
* @date 2019-10-9
*/
public class BaseExcelModel implements Serializable {
private static final long serialVersionUID = 6343247548525494223L;
/**
* 验证错误
*/
@ExcelIgnore
@JSONField(serialize = false)
private String validateError;
@ExcelIgnore
@JSONField(serialize = false)
private int rowIndex;
public int getRowIndex(){
return rowIndex;
}
public String getValidateError(){
return validateError;
}
/**
* 绑定错误
* @param validateError
*/
public void addValidateError(String validateError){
if(this.validateError == null){
this.validateError = validateError;
}
else{
this.validateError += ", " + validateError;
}
}
public void setRowIndex(int rowIndex){
this.rowIndex = rowIndex;
}
}

View File

@ -0,0 +1,139 @@
package com.diboot.file.excel.cache;
import com.diboot.core.binding.annotation.BindDict;
import com.diboot.core.service.DictionaryService;
import com.diboot.core.util.BeanUtils;
import com.diboot.core.util.ContextHelper;
import com.diboot.core.util.V;
import com.diboot.core.vo.KeyValue;
import com.diboot.file.excel.BaseExcelModel;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 数据字典临时缓存
* @author Mazc@dibo.ltd
* @version v2.0
* @date 2020/02/22
*/
@Slf4j
public class DictTempCache {
/**
* 数据字典-子项映射的缓存
*/
private static Map<String, Map<String, Object>> DICT_TYPE_ITEMS_MAP = new ConcurrentHashMap<>();
/**
* 数据字典-缓存时间戳缓存
*/
private static Map<String, Long> DICT_TYPE_TIMESTAMP_MAP = new ConcurrentHashMap<>();
/**
* 无字典绑定的缓存
*/
private static Set<String> NO_DICT_MODELS = new HashSet<>();
/**
* model-字典映射
*/
private static Map<String, List<String>> MODEL_DICTS = new HashMap<>();
/**
* 刷新model字典缓存
* @param modelClass
* @param <T>
*/
public static <T extends BaseExcelModel> void refreshDictCache(Class<T> modelClass){
// 无字典model
if(NO_DICT_MODELS.contains(modelClass.getName())){
return;
}
List<String> dictTypes = extractDictTypes(modelClass);
if(V.notEmpty(dictTypes)){
for(String dictType : dictTypes){
if(DICT_TYPE_ITEMS_MAP.containsKey(dictType) == false || isExpired(DICT_TYPE_TIMESTAMP_MAP.get(dictType)) == true){
List<KeyValue> list = ContextHelper.getBean(DictionaryService.class).getKeyValueList(dictType);
Map<String, Object> name2ValueMap = BeanUtils.convertKeyValueList2Map(list);
DICT_TYPE_ITEMS_MAP.put(dictType, name2ValueMap);
DICT_TYPE_TIMESTAMP_MAP.put(dictType, System.currentTimeMillis());
}
}
}
}
/**
* 获取字典值
* @param dictType
* @param dictName
* @return
*/
public static String getDictValue(String dictType, String dictName){
Map<String, Object> map = DICT_TYPE_ITEMS_MAP.get(dictType);
if(map == null){
log.warn("无法找到数据字典定义: "+dictType);
return dictName;
}
if(map.get(dictName) == null){
return null;
}
return (String)map.get(dictName);
}
/**
* 获取字典名label
* @param dictType
* @param dictValue
* @return
*/
public static String getDictLabel(String dictType, String dictValue) throws Exception{
Map<String, Object> map = DICT_TYPE_ITEMS_MAP.get(dictType);
if(map == null){
log.warn("无法找到数据字典定义: "+dictType);
return dictValue;
}
for( Map.Entry<String, Object> entry : map.entrySet()){
if(dictValue.equals(entry.getValue())){
return entry.getKey();
}
}
return null;
}
/**
* 提取注解绑定
* @param modelClass
* @param <T>
* @return
*/
private static <T extends BaseExcelModel> List<String> extractDictTypes(Class<T> modelClass){
if(MODEL_DICTS.containsKey(modelClass.getName())){
return MODEL_DICTS.get(modelClass.getName());
}
// 检测model是否包含dict注解
List<String> dictTypes = new ArrayList<>();
BeanUtils.extractAllFields(modelClass).forEach(fld -> {
if(fld.getAnnotation(BindDict.class) != null){
BindDict bindDict = fld.getAnnotation(BindDict.class);
dictTypes.add(bindDict.type());
}
});
if(dictTypes.isEmpty()){
NO_DICT_MODELS.add(modelClass.getName());
}
else{
MODEL_DICTS.put(modelClass.getName(), dictTypes);
}
return dictTypes;
}
/**
* 是否超期
* @return
*/
private static boolean isExpired(Long cacheTime){
if(cacheTime == null){
return true;
}
return (System.currentTimeMillis() - cacheTime) > 600000;
}
}

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