Merge remote-tracking branch 'remotes/origin/develop'
# Conflicts: # README.md
42
README.md
|
@ -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 "文件组件").
|
||||
|
||||
> 其他组件逐步开发中 ...
|
||||
|
||||
## 四、技术交流群
|
||||
## 五、技术交流群
|
||||
|
||||
如果您有技术问题,欢迎加群交流:
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
-- 添加备注
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
~~~
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,5 +23,5 @@ public @interface BindDict {
|
|||
* 数据字典项取值字段
|
||||
* @return
|
||||
*/
|
||||
String field();
|
||||
String field() default "";
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
/***
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/***
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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语句
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -55,6 +55,14 @@ public class BusinessException extends RuntimeException {
|
|||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义内容提示
|
||||
* @param msg
|
||||
*/
|
||||
public BusinessException(String msg) {
|
||||
super( msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义内容提示
|
||||
* @param status
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
||||
/***
|
||||
* 创建或更新entity(entity.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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "删除字典子项异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,7 @@ public class D extends DateUtils{
|
|||
/***
|
||||
* 星期
|
||||
*/
|
||||
protected static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
|
||||
public static final String[] WEEK = new String[]{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
|
||||
|
||||
/***
|
||||
* 当前的日期时间
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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("请检查"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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/'
|
||||
}]
|
||||
|
|
After Width: | Height: | Size: 69 KiB |
|
@ -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:关闭之后执行的函数
|
||||
|
After Width: | Height: | Size: 211 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 60 KiB |
|
@ -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交换方案;
|
||||
* 预置多种常用请求方式,轻松完成异步文件下载等;
|
||||
* 数据字典管理功能;
|
||||
* 登录人员管理界面;
|
||||
* 角色与权限管理功能;
|
||||
* 权限管理功能;
|
||||
* 登录日志管理功能。
|
|
@ -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
|
||||
```
|
||||
|
||||
运行完成后,点击命令行提示出的地址,打开页面成功,项目启动就完成了。
|
||||
|
|
@ -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类型数据
|
||||
* 常用语复杂参数提交导出
|
|
@ -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)
|
||||
|
|
@ -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的列表中配置了多个权限码,那么具有其中一个就将会具有当前菜单的访问权限。
|
||||
* 配置完成后,就可以对这些页面进行访问了(如果配置了权限码,可以先使用具有管理员权限的账号访问到)。
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
```
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
## diboot-devtools是什么?
|
||||
|
||||
> diboot-devtools是一个面向Java开发人员的开发助理,有了她,你可以摆脱重复性的Coding,更专注于业务分析,提高开发效率和代码质量。
|
||||
> diboot-devtools是一个面向Java开发人员的开发助理,有了她,你可以摆脱CRUD等重复性的Coding,更专注于业务实现,提高开发效率和代码质量。
|
||||
|
||||
> **diboot-devtools - 将重复有规律的事情自动化**
|
||||
|
||||
## 我们的优势
|
||||
* 支持常用的五大数据库(MySQL,MariaDB,ORACLE,SQLServer, 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
|
|
@ -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)
|
||||
```
|
||||
|
||||
## 打开管理页面
|
||||
|
|
|
@ -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集合。
|
||||
|
||||
* 选择关联`数据字典`后,将会弹出可供选择的数据字典,选择一个元数据关联到该字段即可。
|
||||
|
||||
|
|
|
@ -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:关闭之后执行的函数
|
||||
|
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 54 KiB |
|
@ -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交换方案;
|
||||
* 预置多种常用请求方式,轻松完成异步文件下载等;
|
||||
* 数据字典管理功能;
|
||||
* 登录人员管理界面;
|
||||
* 角色与权限管理功能;
|
||||
* 权限管理功能;
|
||||
* 登录日志管理功能。
|
|
@ -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
|
||||
```
|
||||
|
||||
运行完成后,点击命令行提示出的地址,打开页面成功,项目启动就完成了。
|
||||
|
|
@ -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类型数据
|
||||
* 常用语复杂参数提交导出
|
|
@ -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)
|
||||
|
|
@ -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的列表中配置了多个权限码,那么具有其中一个就将会具有当前菜单的访问权限。
|
||||
* 配置完成后,就可以对这些页面进行访问了(如果配置了权限码,可以先使用具有管理员权限的账号访问到)。
|
|
@ -0,0 +1,9 @@
|
|||
# diboot-file: 文件处理组件
|
||||
|
||||
## 组件特性
|
||||
* EasyExcel轻量封装,支持Java注解校验与@BindDict注解实现字典name-value转换,提供完善的校验错误提示
|
||||
* 封装常用的文件本地存储、上传下载、图片压缩水印等常用处理
|
||||
* Starter启动自动安装依赖的数据表
|
||||
* 启用devtools,自动生成初始样例controller代码到本地
|
||||
|
||||
> 组件依赖的数据表upload_file,在组件starter初次启动时将自动初始化。
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
||||
~~~
|
||||
|
Before Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 99 KiB |
|
@ -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
|
|
@ -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、登录/注册/退出
|
||||
* 登录:
|
||||
|
|
|
@ -31,5 +31,4 @@ Employee currentUser = IamSecurityUtils.getCurrentUser();
|
|||
需要创建缓存实现类实现CacheManager接口,并配置参数diboot.iam.cache-manager-class为你的缓存类。
|
||||
```
|
||||
diboot.iam.cache-manager-class=com.xxx.MyCacheManager
|
||||
```
|
||||
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
# 优秀案例
|
||||
|
||||
## 说明
|
||||
|
||||
* 优秀案例展示,将展示Diboot用户们基于此开发的系列产品列表。
|
||||
* 如果您也使用我们的Diboot开发了相关产品,可以通过[Diboot优秀案例申请](http://s.dibo.ltd/#/f/748dc26142fc46af946ce9acea0aff76)来提交您的产品案例。
|
||||
* 对于您提交的案例申请,我们将尽快更新到该列表中,感谢您的支持。
|
||||
|
||||
## 案例列表
|
||||
|
||||
* [帝博数据统计平台](http://s.dibo.ltd/#/g)
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
|
@ -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>
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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格式文件!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
139
diboot-file-starter/src/main/java/com/diboot/file/excel/cache/DictTempCache.java
vendored
Normal 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;
|
||||
}
|
||||
|
||||
}
|