第一波重构

This commit is contained in:
zhou-hao
2019-09-27 11:09:35 +08:00
parent 29945567cf
commit b6033ba794
967 changed files with 1122 additions and 56087 deletions

133
README.md
View File

@@ -4,136 +4,5 @@
[![Build Status](https://travis-ci.org/hs-web/hsweb-framework.svg?branch=master)](https://travis-ci.org/hs-web/hsweb-framework)
[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=flat-square)](https://www.apache.org/licenses/LICENSE-2.0.html)
[贡献代码](CONTRIBUTING.md) [用户手册](https://docs.hsweb.io)
## 应用场景
1. 完全开源的后台管理系统.
2. 细粒度(按钮,行,列)权限控制的后台管理系统.
3. 模块化的后台管理系统.
4. 功能可拓展的后台管理系统.
5. 集成各种常用功能的后台管理系统.
6. 前后分离的后台管理系统.
注意:
项目主要基于`spring-boot`,`mybatis`. 在使用`hsweb`之前,你应该对`spring-boot`有一定的了解.
项目模块太多?不要被吓到.我们不推荐将本项目直接`clone`后修改,运行.而是使用maven依赖的方式使用`hsweb`.
选择自己需要的模块进行依赖,正式版发布后,所有模块都将发布到maven中央仓库.
你可以参照[demo](https://github.com/hs-web/hsweb3-demo)进行使用.
## 文档
各个模块的使用方式查看对应模块下的 `README.md`,在使用之前,
你可以先粗略浏览一下各个模块,对每个模块的作用有大致的了解.
## 核心技术选型
1. Java 8
2. Maven3
3. Spring Boot 1.5.x
4. Mybatis
5. Hsweb Easy Orm (使用`hsweb-easy-orm`拓展`Myabtis`实现动态条件)
## 模块简介
| 模块 | 说明 | 进度 |
| ------------- |:-------------:| ----|
|[hsweb-authorization](hsweb-authorization)|权限控制| 100%|
|[hsweb-commons](hsweb-commons) |基础通用功能| 100%|
|[hsweb-concurrent](hsweb-concurrent)|并发包,缓存,锁,计数器等| 80%|
|[hsweb-core](hsweb-core)|框架核心,基础工具类| 100%|
|[hsweb-datasource](hsweb-datasource)|数据源| 100%|
|[hsweb-logging](hsweb-logging)| 日志| 100%|
|[hsweb-starter](hsweb-starter)|模块启动器| 100%|
|[hsweb-system](hsweb-system)|**系统常用功能**| 80%|
|[hsweb-thirdparty](hsweb-thirdparty)| 第三方插件 | 100% |
## 核心特性
1. DSL风格,可拓展的通用curd,支持前端直接传参数,无需担心任何sql注入.
```java
//where name = #{name} limit 0,20
createQuery().where("name",name).list(0,20);
//update s_user set name = #{user.name} where id = #{user.id}
createUpdate().set(user::getName).where(user::getId).exec();
```
2. 灵活的权限控制
```java
@PostMapping("/account")
@Authorize(permission="account-manager",action="add")
public ResponseMessage<Sring> addAccount(@RequestBody Account account){
return ok(accountService.addAccount(account));
}
@GettMapping("/account")
@Authorize(permission="account-manager",action="query",dataAccess=@RequiresDataAccess)//开启数据权限控制
public ResponseMessage<PageResult<Account>> addAccount(QueryParamEntity query){
//用户设置了数据权限后,query的参数属性将被修改
return ok(accountService.selectPager(query));
}
```
3. 灵活的模块版本维护脚本
`resources/hsweb-starter.js`
```js
//组件信息
var info = {
groupId: "com.company",
artifactId: "module-name",
version: "1.0.2",
website: "company.com",
author: "作者",
comment: "模块名称"
};
//版本更新信息
var versions = [
{
version: "1.0.2", //当info.version大于等于此版本号时,执行upgrade
upgrade: function (context) {
var database = context.database;
//增加冻结金额字段
database.createOrAlter("acc_account")
.addColumn().name("freeze_balance").jdbcType(JDBCType.BIGINT).comment("冻结金额").commit()
.comment("资金账户")
.commit();
}
}
];
var JDBCType = java.sql.JDBCType;
//首次引入依赖,将执行安装操作
function install(context) {
var database = context.database;
database.createOrAlter("acc_account")
.addColumn().name("id").varchar(32).notNull().primaryKey().comment("ID").commit()
.addColumn().name("account_no").varchar(32).notNull().comment("资金账户号").commit()
//更多字段
//索引
.index().name("idx_acc_account_no")
.column("account_no").commit()//account_no索引
.comment("资金账户").commit();
}
//设置依赖,固定代码,无需修改
dependency.setup(info)
.onInstall(install) //安装时执行
.onUpgrade(function (context) { //更新时执行
var upgrader = context.upgrader;
upgrader.filter(versions) //过滤版本信息
.upgrade(function (newVer) { //执行更新
newVer.upgrade(context);
});
})
.onUninstall(function (context) { //卸载时执行
});
```
# 基于spring-boot2,全响应式的后台管理框架

View File

@@ -12,11 +12,6 @@
<description>授权,权限管理API</description>
<artifactId>hsweb-authorization-api</artifactId>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-boost-aop</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-core</artifactId>

View File

@@ -4,7 +4,7 @@ import org.apache.commons.codec.digest.DigestUtils;
import org.hswebframework.expands.script.engine.DynamicScriptEngine;
import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
import org.hswebframework.utils.StringUtils;
import org.hswebframework.web.BusinessException;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.access.ScriptDataAccessConfig;

View File

@@ -19,7 +19,7 @@
package org.hswebframework.web.authorization.oauth2.client.simple;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.NotFoundException;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.web.authorization.oauth2.client.OAuth2RequestBuilderFactory;
import org.hswebframework.web.authorization.oauth2.client.OAuth2RequestService;
import org.hswebframework.web.authorization.oauth2.client.OAuth2ServerConfig;

View File

@@ -18,7 +18,7 @@
package org.hswebframework.web.authorization.oauth2.client.simple;
import org.hswebframework.web.NotFoundException;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.web.authorization.oauth2.client.*;
import org.hswebframework.web.authorization.oauth2.client.request.OAuth2Session;
import org.hswebframework.web.authorization.oauth2.client.simple.session.AuthorizationCodeSession;
@@ -28,9 +28,7 @@ import org.hswebframework.web.authorization.oauth2.client.simple.session.Passwor
import org.hswebframework.web.oauth2.core.GrantType;
import org.hswebframework.web.oauth2.core.OAuth2Constants;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.function.Supplier;

View File

@@ -22,7 +22,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import org.hswebframework.web.BusinessException;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory;
import org.hswebframework.web.authorization.oauth2.client.exception.OAuth2RequestException;
@@ -30,8 +30,6 @@ import org.hswebframework.web.authorization.oauth2.client.request.definition.Res
import org.hswebframework.web.authorization.oauth2.client.response.OAuth2Response;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.oauth2.core.ErrorType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.function.Function;

View File

@@ -19,7 +19,7 @@
package org.hswebframework.web.authorization.oauth2.client.simple.session;
import org.apache.commons.codec.binary.Base64;
import org.hswebframework.web.BusinessException;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.authorization.oauth2.client.*;
import org.hswebframework.web.authorization.oauth2.client.exception.OAuth2RequestException;
import org.hswebframework.web.authorization.oauth2.client.request.OAuth2Request;

View File

@@ -1,7 +1,7 @@
package org.hswebframework.web.authorization.oauth2.client.simple.provider;
import com.alibaba.fastjson.JSON;
import org.hswebframework.web.BusinessException;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.authorization.oauth2.client.exception.OAuth2RequestException;
import org.hswebframework.web.authorization.simple.SimpleUser;
import org.hswebframework.web.authorization.simple.builder.SimpleAuthenticationBuilderFactory;
@@ -12,8 +12,6 @@ import org.junit.Test;
import java.math.BigDecimal;
import static org.junit.Assert.*;
/**
* TODO 完成注释
*

View File

@@ -1 +0,0 @@
# 增强模块,提供一些增强工具如验证器

View File

@@ -1,3 +0,0 @@
# AOP增强模块
提供aop常用操作需要的公共类

View File

@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 http://www.hswebframework.org
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~
-->
<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">
<parent>
<artifactId>hsweb-boost</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-boost-aop</artifactId>
<description>对AOP提供一些增强功能</description>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-utils</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,29 +0,0 @@
<?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">
<parent>
<artifactId>hsweb-boost</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-boost-excel</artifactId>
<description>便捷EXCEL操作工具</description>
<dependencies>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-expands-office</artifactId>
<version>${hsweb.expands.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-entity</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,303 +0,0 @@
package org.hswebframework.web.excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.expands.office.excel.ExcelIO;
import org.hswebframework.web.ApplicationContextHolder;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.dict.EnumDict;
import org.hswebframework.web.dict.ItemDefine;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
@Slf4j
@SuppressWarnings("all")
public class DefaultExcelImporter implements ExcelImporter {
protected static Map<Class, Map<Class, HeaderMapper>> headerMappings = new ConcurrentHashMap<>();
protected static ExcelCellConverter DEFAULT_CONVERTER = new ExcelCellConverter() {
@Override
public Object convertFromCell(Object from) {
return from;
}
@Override
public Object convertToCell(Object from) {
if (from instanceof EnumDict) {
return ((EnumDict) from).getText();
}
return from;
}
};
protected ExcelCellConverter defaultConvert = DEFAULT_CONVERTER;
protected Map<Class, HeaderMapper> createHeaderMapping(Class type) {
//一些基本类型不做处理
if (type == String.class
|| Number.class.isAssignableFrom(type)
|| ClassUtils.isPrimitiveWrapper(type)
|| type.isPrimitive()
|| type.isEnum()
|| type.isArray()
|| Date.class.isAssignableFrom(type)) {
return new java.util.HashMap<>();
}
AtomicInteger index = new AtomicInteger(0);
Map<Class, DefaultHeaderMapper> headerMapperMap = new HashMap<>();
ReflectionUtils.doWithFields(type, field -> {
Excel excel = field.getAnnotation(Excel.class);
ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
if ((excel == null && apiModelProperty == null) || (excel != null && excel.ignore())) {
return;
}
String header = excel == null ? apiModelProperty.value() : excel.value();
HeaderMapping mapping = new HeaderMapping();
mapping.header = header;
mapping.field = field.getName();
mapping.index = excel == null || excel.exportOrder() == -1 ? index.getAndAdd(1) : excel.exportOrder();
if (null != excel) {
mapping.enableImport = excel.enableImport();
mapping.enableExport = excel.enableExport();
mapping.children = () -> getHeaderMapper(field.getType(), excel.group());
for (Class group : excel.group()) {
headerMapperMap.computeIfAbsent(group, DefaultHeaderMapper::new)
.mappings
.add(mapping);
}
mapping.converter = createConvert(excel.converter(), field.getType());
} else {
mapping.converter = createConvert(ExcelCellConverter.class, field.getType());
mapping.children = () -> getHeaderMapper(field.getType());
headerMapperMap.computeIfAbsent(Void.class, DefaultHeaderMapper::new)
.mappings
.add(mapping);
}
});
return (Map) headerMapperMap;
}
@SneakyThrows
protected <T> ExcelCellConverter<T> createConvert(Class<? extends ExcelCellConverter> converterClass, Class<T> type) {
if (converterClass != ExcelCellConverter.class) {
try {
return ApplicationContextHolder.get().getBean(converterClass);
} catch (Exception e) {
log.warn("can not get bean ({}) from spring context.", converterClass, e);
return converterClass.newInstance();
}
}
return defaultConvert;
}
@Getter
class HeaderMapping implements Comparable<HeaderMapping> {
private String field;
private String header;
private int index;
private boolean enableExport = true;
private boolean enableImport = true;
private Supplier<HeaderMapper> children;
private ExcelCellConverter converter;
public HeaderMapping copy() {
HeaderMapping mapping = new HeaderMapping();
mapping.children = children;
mapping.field = field;
mapping.header = header;
mapping.index = index;
mapping.enableImport = enableImport;
mapping.enableExport = enableExport;
mapping.children = children;
mapping.converter = converter;
return mapping;
}
@Override
public int compareTo(HeaderMapping o) {
return Integer.compare(index, o.index);
}
}
class DefaultHeaderMapper implements HeaderMapper {
@Getter
private Class group;
public DefaultHeaderMapper(Class group) {
this.group = group;
}
private Map<String, HeaderMapping> fastMapping = new HashMap<>();
private final List<HeaderMapping> mappings = new ArrayList<HeaderMapping>() {
private static final long serialVersionUID = 5995980497414973311L;
@Override
public boolean add(HeaderMapping o) {
fastMapping.put(o.header, o);
fastMapping.put(o.field, o);
return super.add(o);
}
};
@Override
public Optional<HeaderMapping> getMapping(String key) {
return Optional.ofNullable(fastMapping.computeIfAbsent(key, k -> {
//尝试获取嵌套的属性
for (HeaderMapping mapping : mappings) {
String newKey = key;
//字段嵌套
if (newKey.startsWith(mapping.field)) {
newKey = newKey.substring(mapping.field.length());
}
//表头嵌套
else if (newKey.startsWith(mapping.header)) {
newKey = newKey.substring(mapping.header.length());
} else {
continue;
}
HeaderMapper mapper = mapping.children.get();
if (null != mapper) {
HeaderMapping map = mapper.getMapping(newKey).orElse(null);
if (map != null) {
map = map.copy();
map.field = mapping.field.concat(".").concat(map.field);
map.header = mapping.header.concat(map.header);
return map;
}
}
}
return null;
}));
}
}
interface HeaderMapper {
Optional<HeaderMapping> getMapping(String key);
}
protected HeaderMapper getHeaderMapper(Class type, Class... group) {
Map<Class, HeaderMapper> mapperMap = headerMappings.computeIfAbsent(type, this::createHeaderMapping);
if (group != null && group.length > 0) {
return Arrays.stream(group)
.map(mapperMap::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
} else {
return mapperMap.get(Void.class);
}
}
@Override
@SneakyThrows
public <T> Result<T> doImport(InputStream inputStream, Class<T> type, Function<T, Error> afterParsed, Class... group) {
AtomicInteger counter = new AtomicInteger(0);
AtomicInteger errorCounter = new AtomicInteger(0);
List<T> data = new ArrayList<>();
List<Error> errors = new ArrayList<>();
HeaderMapper headerMapper = getHeaderMapper(type, group);
if (headerMapper == null) {
throw new UnsupportedOperationException("不支持导入此类型");
}
ExcelIO.read(inputStream, row -> {
counter.getAndAdd(1);
Map<String, Object> mapValue = row.getResult();
Map<String, Object> newValue = new HashMap<>();
for (Map.Entry<String, Object> entry : mapValue.entrySet()) {
String key = entry.getKey();
HeaderMapping mapping = headerMapper.getMapping(key).orElse(null);
if (mapping == null || !mapping.enableImport) {
continue;
}
Object value = mapping.getConverter().convertFromCell(entry.getValue());
String field = mapping.getField();
//嵌套的字段
if (field.contains(".")) {
String tmpField = field;
Map<String, Object> nestMapValue = newValue;
while (tmpField.contains(".")) {
// nest.obj.name => [nest,obj.name]
String[] nestFields = tmpField.split("[.]", 2);
//nest
String nestField = nestFields[0];
//obj.name
tmpField = nestFields[1];
Object nestValue = nestMapValue.get(nestField);
//构造嵌套对象为map
if (nestValue == null) {
nestMapValue.put(nestField, nestMapValue = new HashMap<>());
} else {
if (nestValue instanceof Map) {
nestMapValue = ((Map) nestValue);
} else {
//这里几乎不可能进入...
nestMapValue.put(nestField, nestMapValue = FastBeanCopier.copy(nestValue, new HashMap<>()));
}
}
}
//最后nestMapValue就为最里层嵌套的对象了
nestMapValue.put(tmpField, value);
} else {
newValue.put(field, value);
}
}
//创建实例并将map复制到实例中
T instance = FastBeanCopier.getBeanFactory().newInstance(type);
FastBeanCopier.copy(newValue, instance);
data.add(instance);
Error error = afterParsed.apply(instance);
if (null != error) {
errorCounter.getAndAdd(1);
error.setRowIndex(counter.get());
error.setSheetIndex(row.getSheet());
errors.add(error);
}
});
return Result.<T>builder()
.data(data)
.errors(errors)
.success(counter.get() - errorCounter.get())
.total(counter.get())
.error(errorCounter.get())
.build();
}
}

View File

@@ -1,60 +0,0 @@
package org.hswebframework.web.excel;
import io.swagger.annotations.ApiModelProperty;
import java.lang.annotation.*;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Excel {
/**
* @return EXCEL表头
* @see ApiModelProperty#value()
*/
String value() default "";
/**
* @return 读取指定的工作薄,-1为默认
*/
int sheetIndex() default -1;
/**
* @return 是否取消EXCEL导入导出功能
*/
boolean ignore() default false;
/**
* @return 是否开启导入, 开启后的字段才能进行导入
*/
boolean enableImport() default true;
/**
* @return 是否开启导出, 开启后的字段才能进行导出
*/
boolean enableExport() default true;
/**
* @return 导出时, 表头的顺序
*/
int exportOrder() default -1;
/**
* @return 导出分组, 可通过分组导入导出不同的字段信息
*/
Class[] group() default Void.class;
/**
* 自定义单元格转换器, 用于对数据字典等字段进行自定义转换
*
* @return 实例必须注入到spring容器中
* @see org.springframework.context.ApplicationContext#getBean(Class)
*/
Class<? extends ExcelCellConverter> converter() default ExcelCellConverter.class;
}

View File

@@ -1,11 +0,0 @@
package org.hswebframework.web.excel;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public interface ExcelCellConverter<T> {
T convertFromCell(Object from);
Object convertToCell(T from);
}

View File

@@ -1,66 +0,0 @@
package org.hswebframework.web.excel;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.bean.FastBeanCopier;
import java.io.InputStream;
import java.util.List;
import java.util.function.Function;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public interface ExcelImporter {
ExcelImporter instance = new DefaultExcelImporter();
/**
* 解析excel为指定的class对象,并返回解析结果.类上的属性需要注解{@link Excel}或者{@link io.swagger.annotations.ApiModelProperty}.
*
* @param inputStream excel文件流,支持xls和xlsx
* @param type 要解析为的类型
* @param afterParsed 每解析完一个对象都会调用此接口,用于自定义操作,如: 数据校验
* @param group 导入的分组 {@link Excel#group()},如果不指定则为 {@link Void#getClass()}
* @param <T> 泛型
* @return 导入结果, 包含了成功, 失败信息
* @see Excel
* @see FastBeanCopier#getBeanFactory()
* @see ExcelImporter#instance 默认的实现
*/
<T> Result<T> doImport(InputStream inputStream, Class<T> type, Function<T, Error> afterParsed, Class... group);
@Builder
@Getter
@Setter
class Result<T> {
int total;
int success;
int error;
List<Header> headers;
List<T> data;
List<Error> errors;
}
@Builder
@Getter
@Setter
class Error {
int sheetIndex;
int rowIndex;
int errorType;
Object reason;
}
@Builder
@Getter
@Setter
class Header {
int sheetIndex;
@SuppressWarnings("all")
String header;
String field;
}
}

View File

@@ -1,57 +0,0 @@
package org.hswebframework.web.excel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hswebframework.web.commons.bean.Bean;
import org.hswebframework.web.commons.entity.DataStatusEnum;
import org.junit.Assert;
import org.junit.Test;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public class DefaultExcelImporterTest {
@Test
public void test() {
ExcelImporter.Result<TestBean> result = ExcelImporter
.instance
.doImport(this.getClass().getResourceAsStream("/test.xls"), TestBean.class, bean -> null);
Assert.assertEquals(result.success, 1);
System.out.println(JSON.toJSONString(result.getData(), SerializerFeature.PrettyFormat));
TestBean bean = result.getData().get(0);
Assert.assertNotNull(bean.status);
Assert.assertNotNull(bean.nest);
Assert.assertNotNull(bean.nest.nest);
}
@Getter
@Setter
@ToString
public static class TestBean implements Bean {
private static final long serialVersionUID = -5394537136669692305L;
@Excel("姓名")
private String name;
@Excel("年龄")
private int age;
@Excel("状态")
private DataStatusEnum status;
@Excel("嵌套-")
private TestBean nest;
}
}

View File

@@ -1,36 +0,0 @@
<?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">
<parent>
<artifactId>hsweb-boost</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-boost-ftp</artifactId>
<description>ftp操作工具,使用ftp连接池.</description>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jool-java-8</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,80 +0,0 @@
package org.hswebframework.web.ftp;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.hswebframework.web.ftp.pool.FTPClientPool;
import org.jooq.lambda.Unchecked;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author zhouhao
* @since 3.0
*/
@AllArgsConstructor
public class DefaultFTPOperation implements FTPOperation {
private FTPClientPool pool;
@SneakyThrows
protected FTPClient getClient() {
return pool.borrowObject();
}
protected void returnClient(FTPClient client) {
pool.returnObject(client);
}
public <T> T doExecute(Function<FTPClient, T> function) {
FTPClient client = getClient();
try {
return function.apply(client);
} finally {
returnClient(client);
}
}
@Override
public boolean delete(String fileName) {
return doExecute(Unchecked.function(client -> client.deleteFile(fileName)));
}
@Override
public boolean rename(String from, String to) {
return doExecute(Unchecked.function(client -> client.rename(from, to)));
}
@Override
public boolean download(String fileName, OutputStream outputStream) {
return doExecute(Unchecked.function(client -> client.retrieveFile(fileName, outputStream)));
}
@Override
public boolean upload(String fileName, InputStream input) {
return doExecute(Unchecked.function(client -> client.storeFile(fileName, input)));
}
@Override
public boolean upload(String fileName, String text) {
return doExecute(Unchecked.function(client -> {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(text.getBytes())) {
return client.storeFile(fileName, inputStream);
}
}));
}
@Override
public void list(String path, Consumer<FTPFile> consumer) {
doExecute(Unchecked.function(client -> {
Arrays.stream(client.listFiles(path)).forEach(consumer);
return null;
}));
}
}

View File

@@ -1,35 +0,0 @@
package org.hswebframework.web.ftp;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* @author zhouhao
* @since 3.0
*/
public interface FTPOperation {
boolean delete(String fileName);
boolean rename(String from, String to);
boolean download(String fileName, OutputStream outputStream);
boolean upload(String fileName, InputStream input);
boolean upload(String fileName, String text);
void list(String path, Consumer<FTPFile> consumer);
<T> T doExecute(Function<FTPClient, T> command);
interface HandleExceptionFunction<T>{
T apply(FTPClient client) throws Exception;
}
}

View File

@@ -1,76 +0,0 @@
package org.hswebframework.web.ftp.pool;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.ConnectException;
public class FTPClientFactory implements PooledObjectFactory<FTPClient> {
private static Logger logger = LoggerFactory.getLogger(FTPClientFactory.class);
private FTPClientProperties config;
public FTPClientFactory(FTPClientProperties config) {
this.config = config;
}
public PooledObject<FTPClient> makeObject() throws Exception {
FTPClient ftpClient = new FTPClient();
ftpClient.setConnectTimeout(config.getClientTimeout());
ftpClient.connect(config.getHost(), config.getPort());
int reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect();
logger.warn("FTPServer refused connection");
return null;
}
boolean result = ftpClient.login(config.getUsername(), config.getPassword());
if (!result) {
throw new ConnectException("ftp登陆失败:" + config.getUsername() + "/password:" + config.getPassword() + "@" + config.getHost());
}
ftpClient.setFileType(config.getTransferFileType());
ftpClient.setBufferSize(1024);
ftpClient.setControlEncoding(config.getEncoding());
if (config.isPassiveMode()) {
ftpClient.enterLocalPassiveMode();
}
return new DefaultPooledObject<>(ftpClient);
}
@Override
public void destroyObject(PooledObject<FTPClient> p) throws Exception {
try {
p.getObject().logout();
} finally {
p.getObject().disconnect();
}
}
@Override
public boolean validateObject(PooledObject<FTPClient> p) {
try {
p.getObject().sendNoOp();
} catch (IOException e) {
logger.warn("validateObject ftp error!", e);
return false;
}
return p.getObject().isConnected() && p.getObject().isAvailable();
}
@Override
public void activateObject(PooledObject<FTPClient> p) throws Exception {
p.getObject().sendNoOp();
}
@Override
public void passivateObject(PooledObject<FTPClient> p) {
}
}

View File

@@ -1,22 +0,0 @@
package org.hswebframework.web.ftp.pool;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.AbandonedConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
public class FTPClientPool extends GenericObjectPool<FTPClient> {
public FTPClientPool(PooledObjectFactory<FTPClient> factory) {
super(factory);
}
public FTPClientPool(PooledObjectFactory<FTPClient> factory, GenericObjectPoolConfig config) {
super(factory, config);
}
public FTPClientPool(PooledObjectFactory<FTPClient> factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) {
super(factory, config, abandonedConfig);
}
}

View File

@@ -1,25 +0,0 @@
package org.hswebframework.web.ftp.pool;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
@Getter
@Setter
@ToString
public class FTPClientProperties extends GenericObjectPoolConfig {
private String host;
private int port = 22;
private String username;
private String password;
private boolean passiveMode = true;
private String encoding = "utf-8";
private int clientTimeout = 10 * 1000;
private int threadNum = 20;
private int transferFileType = FTPClient.BINARY_FILE_TYPE;
private boolean renameUploaded = false;
private int retryTimes = 3;
}

View File

@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 http://www.hswebframework.org
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~
-->
<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">
<parent>
<artifactId>hsweb-framework</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>一些增强功能</description>
<artifactId>hsweb-boost</artifactId>
<packaging>pom</packaging>
<modules>
<module>hsweb-boost-aop</module>
<module>hsweb-boost-ftp</module>
<module>hsweb-boost-excel</module>
</modules>
</project>

View File

@@ -1,52 +0,0 @@
<?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">
<parent>
<artifactId>hsweb-commons</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-commons-bean</artifactId>
<description>通用增删改查-通用Bean模块</description>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.athaydes</groupId>
<artifactId>spock-reports</artifactId>
<version>1.2.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,38 +0,0 @@
package org.hswebframework.web.commons.bean;
import org.hswebframework.web.bean.FastBeanCopier;
import java.io.Serializable;
/**
* @author zhouhao
* @since 3.0
*/
public interface Bean extends Serializable {
/**
* 从指定的对象中复制属性到本对象
*
* @param from 要复制的对象
* @param ignore 不复制的字段
* @param <T> 对象类型
* @return 原始对象
* @see FastBeanCopier
*/
@SuppressWarnings("all")
default <T extends Bean> T copyFrom(Object from, String... ignore) {
return (T) FastBeanCopier.copy(from, this, ignore);
}
/**
* 将对象的属性复制到指定的对象中
*
* @param to 要复制到的对象
* @param ignore 不复制的字段
* @param <T> 对象类型
* @return 复制后的对象
* @see FastBeanCopier
*/
default <T> T copyTo(T to, String... ignore) {
return FastBeanCopier.copy(this, to, ignore);
}
}

View File

@@ -1,46 +0,0 @@
package org.hswebframework.web.commons.bean;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.web.validate.SimpleValidateResults;
import org.hswebframework.web.validate.ValidationException;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
/**
* @author zhouhao
* @since 3.0
*/
@Slf4j
public final class BeanValidator {
private BeanValidator() {
}
static volatile Validator validator;
public static Validator getValidator() {
if (validator == null) {
synchronized (BeanValidator.class) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
return validator = factory.getValidator();
}
}
return validator;
}
public static <T> T tryValidate(T bean, Class... group) {
Set<ConstraintViolation<T>> violations = getValidator().validate(bean, group);
if (!violations.isEmpty()) {
SimpleValidateResults results = new SimpleValidateResults();
for (ConstraintViolation<T> violation : violations) {
results.addResult(violation.getPropertyPath().toString(), violation.getMessage());
}
throw new ValidationException(results);
}
return bean;
}
}

View File

@@ -1,38 +0,0 @@
package org.hswebframework.web.commons.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* @author zhouhao
* @since 3.0
*/
@Configuration
public class BeanValidatorAutoConfiguration implements BeanPostProcessor {
@Bean(name = "validator")
@ConditionalOnMissingBean(Validator.class)
public Validator validator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
return factory.getValidator();
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Validator) {
BeanValidator.validator = ((Validator) bean);
}
return bean;
}
}

View File

@@ -1,22 +0,0 @@
package org.hswebframework.web.commons.bean;
/**
* 支持验证的bean
*
* @author zhouhao
* @since 3.0
*/
public interface ValidateBean extends Bean {
/**
* 尝试验证此bean,如果验证未通过,将抛出{@link org.hswebframework.web.validate.ValidationException}
*
* @param group 验证分组
* @param <T> 当前对象类型
* @return 当前对象
*/
default <T extends ValidateBean> T tryValidate(Class... group) {
BeanValidator.tryValidate(this, group);
return (T) this;
}
}

View File

@@ -1,3 +0,0 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.hswebframework.web.commons.bean.BeanValidatorAutoConfiguration

View File

@@ -1,43 +0,0 @@
package org.hswebframework.web.commons.bean
import org.hibernate.validator.constraints.NotBlank
import org.hswebframework.web.validator.group.CreateGroup
import org.hswebframework.web.validator.group.UpdateGroup
/**
* @author zhouhao
* @since 3.0.2
*/
class BeanValidatorTest extends spock.lang.Specification {
def "测试初始化验证器"() {
given: "初始化"
def validator = BeanValidator.getValidator();
expect: "成功"
null != validator
}
def doValidate(TestBean bean, Class group) {
try {
bean.tryValidate(group);
return null;
} catch (Exception e) {
return e.message;
}
}
def "测试group验证"() {
expect: "验证多个group"
doValidate(new TestBean(name: name), group as Class) == message
where:
name | group | message
null | CreateGroup.class | "姓名不能为空"
"" | CreateGroup.class | "姓名不能为空"
null | UpdateGroup.class | null
"" | UpdateGroup.class | "长度必须在2-20之间"
"张三" | UpdateGroup.class | null
}
}

View File

@@ -1,20 +0,0 @@
package org.hswebframework.web.commons.bean;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hswebframework.web.validator.group.CreateGroup;
import org.hswebframework.web.validator.group.UpdateGroup;
/**
* @author zhouhao
* @since 3.0.2
*/
@Data
public class TestBean implements ValidateBean {
@NotBlank(groups = CreateGroup.class, message = "姓名不能为空")
@Length(min = 2, max = 20, message = "长度必须在2-20之间", groups = UpdateGroup.class)
private String name;
}

View File

@@ -1,85 +0,0 @@
# 通用Controller
提供增删改查的restful接口
`RequestMapping("/user)`为例
| 功能 | http method&url | 响应 | 说明 |
| ------------- | -------------| ------------- | ----|
|查询|GET /user|HTTP Status:200 {"status":200,"result":{"data":[],"total":0}} |可进行[动态查询](#动态查询)|
|不分页查询|GET /user/no-paging|HTTP Status:200 {"status":200,"result":[]} |可进行[动态查询](#动态查询)|
|获取指定id的数据|GET /user/{id}|HTTP Status:200 {"status":200,"result":{"name":""} |可进行[动态查询](#动态查询)|
|新增|POST /user|HTTP Status:201 {"status":201,"result":"{id}"} |contentType='application/json' |
|更新|PUT /user/{id}|HTTP Status:200 {"status":200} |contentType='application/json'|
|新增或者更新|PATCH /user|HTTP Status:200 {"status":200,"result":"{id}"} |contentType='application/json' |
|删除|DELETE /user/{id}|HTTP Status:200 {"status":200} | |
# 动态查询
目前支持动态查询条件类 `QueryParamEntity`:
前端传参数:
1. 普通条件
```html
terms[0].column=name&terms[0].termType=like&terms[0].value=张三
```
等同于sql
```sql
where name like ?
where name like '张三'
```
2. 复杂条件
```html
terms[0].column=name&terms[0].termType=eq&terms[0].value=张三
&terms[1].column=name&terms[1].termType=eq&terms[1].type=or&terms[1].value=李四
```
等同于sql
```sql
where name =? or name = ?
where name = '张三' or name = '李四'
```
3. 嵌套条件
```html
terms[0].column=name&terms[0].termType=like&terms[0].value=张%
&terms[1].type=and
&terms[1].terms[0].column=age&terms[1].terms[0].termType=gt&terms[1].terms[0].value=10
&terms[1].terms[1].column=age&terms[1].terms[1].termType=lt&terms[1].terms[1].value=18
```
等同于sql
```sql
where name like ? and (age>? and age <?)
where name like '张%' and (age>10 and age <18)
```
4. 排序
```html
sorts[0].name=age&sorts[0].order=desc
```
等同于sql
```sql
order by age desc
```
5. 分页
```html
pageIndex=0&pageSize=20
```
不分页查询
```html
paging=false
```
6. 指定要查询的列
```html
includes=id,name,age
```
等同于sql
```sql
select id,name,age from ......
```
不查询的列参数为excludes,如:`excludes=comment,phone`
注意: 以上参数都进行了验证,不会有sql注入问题。

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ /*
~ * Copyright 2019 http://www.hswebframework.org
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
~ */
-->
<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">
<parent>
<artifactId>hsweb-commons</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-commons-controller</artifactId>
<description>通用增删改查-通用Controller模块</description>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-service-simple</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework</groupId>
<artifactId>hsweb-easy-orm-rdb</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-access-logging-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-authorization-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-model</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,87 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.commons.entity.RecordCreationEntity;
import org.hswebframework.web.commons.entity.RecordModifierEntity;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.logging.AccessLogger;
import org.hswebframework.web.service.CreateEntityService;
import org.hswebframework.web.service.InsertService;
import org.hswebframework.web.validator.group.CreateGroup;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import static org.hswebframework.web.controller.message.ResponseMessage.ok;
/**
* 通用新增控制器<br>
* 使用:实现该接口,注解@RestController 以及@RequestMapping("/myController")
* 客户端调用: 通过POST请求,contentType为application/json 。参数为E泛型的json格式
* <pre>
* curl -l -H "Content-type: application/json" -X POST -d '{"field1":"value1","field2":"value2"}' http://domain/contextPath/myController
* </pre>
*
* @author zhouhao
* @since 3.0
*/
public interface CreateController<E, PK, M> {
@Authorize(ignore = true)
<S extends InsertService<E, PK> & CreateEntityService<E>> S getService();
@Authorize(action = Permission.ACTION_ADD)
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@ApiOperation(value = "新增")
default ResponseMessage<PK> add(@RequestBody M data) {
E entity = modelToEntity(data, getService().createEntity());
//自动添加创建人和创建时间
if (entity instanceof RecordCreationEntity) {
RecordCreationEntity creationEntity = (RecordCreationEntity) entity;
creationEntity.setCreateTimeNow();
// creationEntity.setCreatorId(Authentication.current()
// .map(Authentication::getUser)
// .map(User::getId)
// .orElse(null));
}
//修改人和修改时间
if (entity instanceof RecordModifierEntity) {
RecordModifierEntity creationEntity = (RecordModifierEntity) entity;
creationEntity.setModifyTimeNow();
// creationEntity.setModifierId(Authentication.current()
// .map(Authentication::getUser)
// .map(User::getId)
// .orElse(null));
}
return ok(getService().insert(entity));
}
@Authorize(ignore = true)
E modelToEntity(M model, E entity);
}

View File

@@ -1,53 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.controller;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.service.CrudService;
/**
* 通用增删改查控制器
*
* @author zhouhao
* @see QueryController
* @see CreateController
* @see UpdateController
* @see DeleteController
* @see CrudService
* @since 3.0
*/
public interface CrudController<E, PK, Q extends Entity, M>
extends QueryController<E, PK, Q>
, UpdateController<E, PK, M>
, CreateController<E, PK, M>
, DeleteController<E,PK> {
@Override
@SuppressWarnings("unchecked")
@Authorize(ignore = true)
CrudService<E, PK> getService();
@Override
@Authorize(ignore = true)
default E modelToEntity(M model, E entity) {
return FastBeanCopier.copy(model, entity);
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.logging.AccessLogger;
import org.hswebframework.web.service.DeleteService;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import static org.hswebframework.web.controller.message.ResponseMessage.ok;
/**
* 通用删除控制器
*
* @author zhouhao
*/
public interface DeleteController<E,PK> {
@Authorize(ignore = true)
DeleteService<E,PK> getService();
@Authorize(action = Permission.ACTION_DELETE)
@DeleteMapping(path = "/{id:.+}")
@ApiOperation("删除数据")
default ResponseMessage<E> deleteByPrimaryKey(@PathVariable PK id) {
return ok(getService().deleteByPk(id));
}
}

View File

@@ -1,41 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.controller;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.GenericEntity;
import org.hswebframework.web.service.CrudService;
/**
* 通用实体的增删改查控制器
*
* @author zhouhao
* @see GenericEntity
* @see CrudController
* @see CrudService
*/
public interface GenericEntityController<E extends GenericEntity<PK>, PK, Q extends Entity, M>
extends CrudController<E, PK, Q, M> {
@Override
@Authorize(ignore = true)
CrudService<E, PK> getService();
}

View File

@@ -1,116 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.controller;
import io.swagger.annotations.ApiOperation;
import org.hswebframework.web.NotFoundException;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.PagerResult;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.service.QueryByEntityService;
import org.hswebframework.web.service.QueryService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
import static org.hswebframework.web.controller.message.ResponseMessage.ok;
/**
* 通用查询控制器。
*
* @param <E> 实体类型
* @param <PK> 主键类型
* @param <Q> 查询条件实体类型,默认提供{@link QueryParamEntity}实现
* @author zhouhao
* @see QueryParamEntity
* @see 3.0
*/
public interface QueryController<E, PK, Q extends Entity> {
/**
* 获取实现了{@link QueryByEntityService}和{@link QueryService}的服务类
*
* @param <T> 服务类泛型
* @return 服务类实例
*/
@Authorize(ignore = true)
<T extends QueryByEntityService<E> & QueryService<E, PK>> T getService();
/**
* 根据参数动态查询。<br>
* 参数泛型如果为QueryParamEntity,
* 客户的参数 ?terms[0].column=name&terms[0].value=小明
* 则执行查询条件 where name = '小明'
* 具体使用方法参照 {@link QueryParamEntity}
*
* @param param 参数
* @return 查询结果
*/
@Authorize(action = Permission.ACTION_QUERY)
@GetMapping
@ApiOperation(value = "根据动态条件查询", responseReference = "get")
default ResponseMessage<PagerResult<E>> list(Q param) {
return ok(getService().selectPager(param));
}
@Authorize(action = Permission.ACTION_QUERY)
@GetMapping("/no-paging")
@ApiOperation(value = "不分页动态查询", responseReference = "get")
default ResponseMessage<List<E>> listNoPaging(Q param) {
if (param instanceof QueryParamEntity) {
((QueryParamEntity) param).setPaging(false);
}
return ok(getService().select(param));
}
@Authorize(action = Permission.ACTION_QUERY)
@GetMapping("/count")
@ApiOperation(value = "根据动态条件统计", responseReference = "get")
default ResponseMessage<Integer> count(Q param) {
return ok(getService().count(param));
}
@Authorize(action = Permission.ACTION_GET)
@GetMapping(path = "/{id:.+}")
@ApiOperation("根据主键查询")
default ResponseMessage<E> getByPrimaryKey(@PathVariable PK id) {
return ok(assertNotNull(getService().selectByPk(id)));
}
@Authorize(action = Permission.ACTION_GET)
@GetMapping(path = "/ids")
@ApiOperation("根据主键查询多条记录")
default ResponseMessage<List<E>> getByPrimaryKey(@RequestParam List<PK> ids) {
return ok(assertNotNull(getService().selectByPk(ids)));
}
@Authorize(ignore = true)
static <T> T assertNotNull(T obj) {
if (null == obj) {
throw new NotFoundException("{data_not_exist}");
}
return obj;
}
}

View File

@@ -1,54 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.controller;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.service.CrudService;
import org.springframework.beans.BeanUtils;
/**
* 通用增删改查控制器
*
* @author zhouhao
* @see QueryController
* @see CreateController
* @see UpdateController
* @see DeleteController
* @see CrudService
* @since 3.0
*/
public interface SimpleCrudController<E, PK, Q extends Entity>
extends QueryController<E, PK, Q>
, UpdateController<E, PK, E>
, CreateController<E, PK, E>
, DeleteController<E,PK> {
@Override
@SuppressWarnings("unchecked")
@Authorize(ignore = true)
CrudService<E, PK> getService();
@Override
@Authorize(ignore = true)
default E modelToEntity(E model, E entity) {
// model = entity
return model;
}
}

View File

@@ -1,42 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.controller;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.GenericEntity;
import org.hswebframework.web.service.CrudService;
/**
* 通用实体的增删改查控制器
*
* @author zhouhao
* @see GenericEntity
* @see CrudController
* @see CrudService
*/
public interface SimpleGenericEntityController<E extends GenericEntity<PK>, PK, Q extends Entity>
extends SimpleCrudController<E, PK, Q> {
@Override
@Authorize(ignore = true)
CrudService<E, PK> getService();
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.annotation.Logical;
import org.hswebframework.web.commons.entity.RecordCreationEntity;
import org.hswebframework.web.commons.entity.RecordModifierEntity;
import org.hswebframework.web.controller.message.ResponseMessage;
import org.hswebframework.web.logging.AccessLogger;
import org.hswebframework.web.service.CreateEntityService;
import org.hswebframework.web.service.UpdateService;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 通用更新控制器
*
* @author zhouhao
*/
public interface UpdateController<E, PK, M> {
<S extends UpdateService<E, PK> & CreateEntityService<E>> S getService();
@Authorize(action = Permission.ACTION_UPDATE)
@PutMapping(path = "/{id}")
@ApiOperation("修改数据")
default ResponseMessage<Integer> updateByPrimaryKey(@PathVariable PK id, @RequestBody M data) {
E entity = modelToEntity(data, getService().createEntity());
if (entity instanceof RecordModifierEntity) {
RecordModifierEntity creationEntity = (RecordModifierEntity) entity;
creationEntity.setModifyTimeNow();
creationEntity.setModifierId(Authentication.current()
.map(Authentication::getUser)
.map(User::getId)
.orElse(null));
}
return ResponseMessage.ok(getService().updateByPk(id, entity));
}
@Authorize(action = {Permission.ACTION_UPDATE, Permission.ACTION_ADD}, logical = Logical.AND)
@PatchMapping
@ApiOperation("新增或者修改")
default ResponseMessage<PK> saveOrUpdate(@RequestBody M data) {
E entity = modelToEntity(data, getService().createEntity());
//自动添加创建人和创建时间
if (entity instanceof RecordCreationEntity) {
RecordCreationEntity creationEntity = (RecordCreationEntity) entity;
creationEntity.setCreateTimeNow();
creationEntity.setCreatorId(Authentication.current()
.map(Authentication::getUser)
.map(User::getId)
.orElse(null));
}
//修改人和修改时间
if (entity instanceof RecordModifierEntity) {
RecordModifierEntity creationEntity = (RecordModifierEntity) entity;
creationEntity.setModifyTimeNow();
creationEntity.setModifierId(Authentication.current()
.map(Authentication::getUser)
.map(User::getId)
.orElse(null));
}
return ResponseMessage.ok(getService().saveOrUpdate(entity));
}
/**
* 将model转为entity
*
* @param model
* @param entity
* @return 转换后的结果
* @see org.hswebframework.web.commons.model.Model
* @see org.hswebframework.web.commons.entity.Entity
*/
@Authorize(ignore = true)
E modelToEntity(M model, E entity);
}

View File

@@ -1,46 +0,0 @@
package org.hswebframework.web.controller.message;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author zhouhao
*/
public class MapResponseMessage extends ResponseMessage<Map<String, Object>> {
public MapResponseMessage() {
result(new LinkedHashMap<>());
}
public MapResponseMessage put(String key, Object value) {
result.put(key, value);
return this;
}
public static MapResponseMessage ok() {
return new MapResponseMessage();
}
public static MapResponseMessage ok(String message) {
MapResponseMessage responseMessage = new MapResponseMessage();
responseMessage.message = message;
return responseMessage;
}
public static MapResponseMessage error() {
return new MapResponseMessage();
}
public static MapResponseMessage error(String message) {
MapResponseMessage mapResponseMessage = new MapResponseMessage();
mapResponseMessage.message = message;
return mapResponseMessage;
}
public static MapResponseMessage error(int status, String message) {
MapResponseMessage mapResponseMessage = new MapResponseMessage();
mapResponseMessage.message = message;
mapResponseMessage.status = status;
return mapResponseMessage;
}
}

View File

@@ -1,267 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.controller.message;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
/**
* 响应消息,controller中处理后返回此对象响应请求结果给客户端。
*
* @since 2.0
*/
@ApiModel(description = "响应结果")
public class ResponseMessage<T> implements Serializable {
private static final long serialVersionUID = 8992436576262574064L;
protected String message;
protected T result;
protected int status;
private Long timestamp;
/**
* @since 3.0.0-RC
*/
private String code;
@ApiModelProperty("调用结果消息")
public String getMessage() {
return message;
}
@ApiModelProperty(value = "状态码", required = true)
public int getStatus() {
return status;
}
@ApiModelProperty("成功时响应数据")
public T getResult() {
return result;
}
@ApiModelProperty(value = "时间戳", required = true, dataType = "Long")
public Long getTimestamp() {
return timestamp;
}
@ApiModelProperty(value = "业务代码")
public String getCode() {
return code;
}
public static <T> ResponseMessage<T> error(String message) {
return error(500, message);
}
public static <T> ResponseMessage<T> error(int status, String message) {
ResponseMessage<T> msg = new ResponseMessage<>();
msg.message = message;
msg.status(status);
return msg.putTimeStamp();
}
public static <T> ResponseMessage<T> ok() {
return ok(null);
}
private ResponseMessage<T> putTimeStamp() {
this.timestamp = System.currentTimeMillis();
return this;
}
public static <T> ResponseMessage<T> ok(T result) {
return new ResponseMessage<T>()
.result(result)
.putTimeStamp()
.status(200);
}
public ResponseMessage<T> result(T result) {
this.result = result;
return this;
}
public ResponseMessage<T> code(String code) {
this.code = code;
return this;
}
/**
* 过滤字段:指定需要序列化的字段
*/
private transient Map<Class<?>, Set<String>> includes;
/**
* 过滤字段:指定不需要序列化的字段
*/
private transient Map<Class<?>, Set<String>> excludes;
public ResponseMessage() {
}
public ResponseMessage<T> include(Class<?> type, String... fields) {
return include(type, Arrays.asList(fields));
}
public ResponseMessage<T> include(Class<?> type, Collection<String> fields) {
if (includes == null) {
includes = new HashMap<>();
}
if (fields == null || fields.isEmpty()) {
return this;
}
fields.forEach(field -> {
if (field.contains(".")) {
String tmp[] = field.split("[.]", 2);
try {
Field field1 = type.getDeclaredField(tmp[0]);
if (field1 != null) {
include(field1.getType(), tmp[1]);
}
} catch (Exception ignore) {
}
} else {
getStringListFromMap(includes, type).add(field);
}
});
return this;
}
public ResponseMessage<T> exclude(Class type, Collection<String> fields) {
if (excludes == null) {
excludes = new HashMap<>();
}
if (fields == null || fields.isEmpty()) {
return this;
}
fields.forEach(field -> {
if (field.contains(".")) {
String tmp[] = field.split("[.]", 2);
try {
Field field1 = type.getDeclaredField(tmp[0]);
if (field1 != null) {
exclude(field1.getType(), tmp[1]);
}
} catch (Exception ignore) {
}
} else {
getStringListFromMap(excludes, type).add(field);
}
});
return this;
}
public ResponseMessage<T> exclude(Collection<String> fields) {
if (excludes == null) {
excludes = new HashMap<>();
}
if (fields == null || fields.isEmpty()) {
return this;
}
Class type;
if (getResult() != null) {
type = getResult().getClass();
} else {
return this;
}
exclude(type, fields);
return this;
}
public ResponseMessage<T> include(Collection<String> fields) {
if (includes == null) {
includes = new HashMap<>();
}
if (fields == null || fields.isEmpty()) {
return this;
}
Class type;
if (getResult() != null) {
type = getResult().getClass();
} else {
return this;
}
include(type, fields);
return this;
}
public ResponseMessage<T> exclude(Class type, String... fields) {
return exclude(type, Arrays.asList(fields));
}
public ResponseMessage<T> exclude(String... fields) {
return exclude(Arrays.asList(fields));
}
public ResponseMessage<T> include(String... fields) {
return include(Arrays.asList(fields));
}
protected Set<String> getStringListFromMap(Map<Class<?>, Set<String>> map, Class type) {
return map.computeIfAbsent(type, k -> new HashSet<>());
}
@Override
public String toString() {
return JSON.toJSONStringWithDateFormat(this, "yyyy-MM-dd HH:mm:ss");
}
public ResponseMessage<T> status(int status) {
this.status = status;
return this;
}
@ApiModelProperty(hidden = true)
public Map<Class<?>, Set<String>> getExcludes() {
return excludes;
}
@ApiModelProperty(hidden = true)
public Map<Class<?>, Set<String>> getIncludes() {
return includes;
}
public void setMessage(String message) {
this.message = message;
}
public void setResult(T result) {
this.result = result;
}
public void setStatus(int status) {
this.status = status;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
}

View File

@@ -1,9 +0,0 @@
{
"groupId": "${project.groupId}",
"artifactId": "${project.artifactId}",
"path": "hsweb-commons/hsweb-commons-controller",
"gitCommitHash": "${git.commit.hash}",
"gitRepository": "http://github.com/hs-web/hsweb-framework",
"version": "${project.version}"
}

View File

@@ -28,7 +28,21 @@
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>

View File

@@ -16,10 +16,18 @@
*
*/
package org.hswebframework.web.service;
package org.hswebframework.web.crud.entity;
import java.io.Serializable;
/**
* 实体总接口,所有实体需实现此接口
*
* @author zhouhao
* @since 3.0
*/
public interface Service {
public interface Entity extends Serializable {
}

View File

@@ -0,0 +1,19 @@
package org.hswebframework.web.crud.entity;
/**
* 逻辑删除
*
* @author zhouhao
* @since 3.0.6
*/
public interface LogicalDeleteEntity {
Boolean getDeleted();
void setDeleted(boolean deleted);
Long getDeleteTime();
void setDeleteTime(Long deleteTime);
}

View File

@@ -21,6 +21,7 @@ package org.hswebframework.web.crud.entity;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.core.param.QueryParam;
import java.util.ArrayList;
import java.util.List;
@@ -38,7 +39,7 @@ public class PagerResult<E> {
return new PagerResult<>(total, list);
}
public static <E> PagerResult<E> of(int total, List<E> list, QueryParamEntity entity) {
public static <E> PagerResult<E> of(int total, List<E> list, QueryParam entity) {
PagerResult<E> pagerResult = new PagerResult<>(total, list);
pagerResult.setPageIndex(entity.getThinkPageIndex());
pagerResult.setPageSize(entity.getPageSize());

View File

@@ -0,0 +1,30 @@
package org.hswebframework.web.crud.entity;
/**
* 记录创建信息的实体类,包括创建人和创建时间。
* 此实体类与行级权限控制相关联:只能操作自己创建的数据
*
* @author zhouhao
* @since 3.0
*/
public interface RecordCreationEntity extends Entity {
String creatorId = "creatorId";
String createTime = "createTime";
String getCreatorId();
void setCreatorId(String creatorId);
Long getCreateTime();
void setCreateTime(Long createTime);
default void setCreateTimeNow() {
setCreateTime(System.currentTimeMillis());
}
default String getCreatorIdProperty() {
return creatorId;
}
}

View File

@@ -0,0 +1,29 @@
package org.hswebframework.web.crud.entity;
/**
* 记录修改信息的实体类,包括修改人和修改时间。
*
* @author zhouhao
* @since 3.0.6
*/
public interface RecordModifierEntity extends Entity {
String modifierId = "modifierId";
String modifyTime = "modifyTime";
String getModifierId();
void setModifierId(String modifierId);
Long getModifyTime();
void setModifyTime(Long modifyTime);
default void setModifyTimeNow() {
setModifyTime(System.currentTimeMillis());
}
default String getModifierIdProperty() {
return modifierId;
}
}

View File

@@ -0,0 +1,12 @@
package org.hswebframework.web.crud.entity.factory;
import java.util.function.Function;
/**
* 默认的实体映射
*
* @author zhouhao
*/
@FunctionalInterface
public interface DefaultMapperFactory extends Function<Class, MapperEntityFactory.Mapper> {
}

View File

@@ -0,0 +1,10 @@
package org.hswebframework.web.crud.entity.factory;
/**
* 默认的属性复制器
*
* @author zhouhao
*/
@FunctionalInterface
public interface DefaultPropertyCopier extends PropertyCopier<Object, Object> {
}

View File

@@ -0,0 +1,113 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.crud.entity.factory;
/**
* 实体工厂接口,系统各个地方使用此接口来创建实体,在实际编码中也应该使用此接口来创建实体,而不是使用new方式来创建
*
* @author zhouhao
* @see MapperEntityFactory
* @since 3.0
*/
public interface EntityFactory {
/**
* 根据类型创建实例
* <p>
* e.g.
* <pre>
* entityFactory.newInstance(UserEntity.class);
* </pre>
*
* @param entityClass 要创建的class
* @param <T> 类型
* @return 创建结果
*/
<T> T newInstance(Class<T> entityClass);
/**
* 根据类型创建实例,如果类型无法创建,则使用默认类型进行创建
* <p>
* e.g.
* <pre>
* entityFactory.newInstance(UserEntity.class,SimpleUserEntity.class);
* </pre>
*
* @param entityClass 要创建的class
* @param defaultClass 默认class,当{@code entityClass}无法创建时使用此类型进行创建
* @param <T> 类型
* @return 实例
*/
<T> T newInstance(Class<T> entityClass, Class<? extends T> defaultClass);
/**
* 创建实体并设置默认的属性
*
* @param entityClass 实体类型
* @param defaultProperties 默认属性
* @param <S> 默认属性的类型
* @param <T> 实体类型
* @return 创建结果
* @see EntityFactory#copyProperties(Object, Object)
*/
default <S, T> T newInstance(Class<T> entityClass, S defaultProperties) {
return copyProperties(defaultProperties, newInstance(entityClass));
}
/**
* 创建实体并设置默认的属性
*
* @param entityClass 实体类型
* @param defaultClass 默认class
* @param defaultProperties 默认属性
* @param <S> 默认属性的类型
* @param <T> 实体类型
* @return 创建结果
* @see EntityFactory#copyProperties(Object, Object)
*/
default <S, T> T newInstance(Class<T> entityClass, Class<? extends T> defaultClass, S defaultProperties) {
return copyProperties(defaultProperties, newInstance(entityClass, defaultClass));
}
/**
* 根据类型获取实体的真实的实体类型,
* 可通过此方法获取获取已拓展的实体类型,如:<br>
* <code>
* factory.getInstanceType(MyBeanInterface.class);
* </code>
*
* @param entityClass 类型
* @param <T> 泛型
* @return 实体类型
*/
<T> Class<T> getInstanceType(Class<T> entityClass);
/**
* 拷贝对象的属性
*
* @param source 要拷贝到的对象
* @param target 被拷贝的对象
* @param <S> 要拷贝对象的类型
* @param <T> 被拷贝对象的类型
* @return 被拷贝的对象
*/
<S, T> T copyProperties(S source, T target);
}

View File

@@ -0,0 +1,240 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.crud.entity.factory;
import lombok.SneakyThrows;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.web.bean.BeanFactory;
import org.hswebframework.web.bean.FastBeanCopier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Supplier;
/**
* @author zhouhao
* @since 3.0
*/
@SuppressWarnings("unchecked")
public class MapperEntityFactory implements EntityFactory, BeanFactory {
private Map<Class, Mapper> realTypeMapper = new HashMap<>();
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Map<String, PropertyCopier> copierCache = new HashMap<>();
private static final DefaultMapperFactory DEFAULT_MAPPER_FACTORY = clazz -> {
String simpleClassName = clazz.getPackage().getName().concat(".Simple").concat(clazz.getSimpleName());
try {
return defaultMapper(Class.forName(simpleClassName));
} catch (ClassNotFoundException ignore) {
// throw new NotFoundException(e.getMessage());
}
return null;
};
/**
* 默认的属性复制器
*/
private static final DefaultPropertyCopier DEFAULT_PROPERTY_COPIER = FastBeanCopier::copy;
private DefaultMapperFactory defaultMapperFactory = DEFAULT_MAPPER_FACTORY;
private DefaultPropertyCopier defaultPropertyCopier = DEFAULT_PROPERTY_COPIER;
public MapperEntityFactory() {
}
public <T> MapperEntityFactory(Map<Class<T>, Mapper> realTypeMapper) {
this.realTypeMapper.putAll(realTypeMapper);
}
public <T> MapperEntityFactory addMapping(Class<T> target, Mapper<? extends T> mapper) {
realTypeMapper.put(target, mapper);
return this;
}
public MapperEntityFactory addCopier(PropertyCopier copier) {
Class source = ClassUtils.getGenericType(copier.getClass(), 0);
Class target = ClassUtils.getGenericType(copier.getClass(), 1);
if (source == null || source == Object.class) {
throw new UnsupportedOperationException("generic type " + source + " not support");
}
if (target == null || target == Object.class) {
throw new UnsupportedOperationException("generic type " + target + " not support");
}
addCopier(source, target, copier);
return this;
}
public <S, T> MapperEntityFactory addCopier(Class<S> source, Class<T> target, PropertyCopier<S, T> copier) {
copierCache.put(getCopierCacheKey(source, target), copier);
return this;
}
private String getCopierCacheKey(Class source, Class target) {
return source.getName().concat("->").concat(target.getName());
}
@Override
public <S, T> T copyProperties(S source, T target) {
Objects.requireNonNull(source);
Objects.requireNonNull(target);
try {
PropertyCopier<S, T> copier = copierCache.<S, T>get(getCopierCacheKey(source.getClass(), target.getClass()));
if (null != copier) {
return copier.copyProperties(source, target);
}
return (T) defaultPropertyCopier.copyProperties(source, target);
} catch (Exception e) {
logger.warn("copy properties error", e);
}
return target;
}
protected <T> Mapper<T> initCache(Class<T> beanClass) {
Mapper<T> mapper = null;
Class<T> realType = null;
ServiceLoader<T> serviceLoader = ServiceLoader.load(beanClass, this.getClass().getClassLoader());
Iterator<T> iterator = serviceLoader.iterator();
if (iterator.hasNext()) {
realType = (Class<T>) iterator.next().getClass();
}
//尝试使用 Simple类如: package.SimpleUserBean
if (realType == null) {
mapper = defaultMapperFactory.apply(beanClass);
}
if (!Modifier.isInterface(beanClass.getModifiers()) && !Modifier.isAbstract(beanClass.getModifiers())) {
realType = beanClass;
}
if (mapper == null && realType != null) {
if (logger.isDebugEnabled() && realType != beanClass) {
logger.debug("use instance {} for {}", realType, beanClass);
}
mapper = new Mapper<>(realType, new DefaultInstanceGetter(realType));
}
if (mapper != null) {
realTypeMapper.put(beanClass, mapper);
}
return mapper;
}
@Override
public <T> T newInstance(Class<T> beanClass) {
return newInstance(beanClass, null);
}
@Override
public <T> T newInstance(Class<T> beanClass, Class<? extends T> defaultClass) {
if (beanClass == null) {
return null;
}
Mapper<T> mapper = realTypeMapper.get(beanClass);
if (mapper != null) {
return mapper.getInstanceGetter().get();
}
mapper = initCache(beanClass);
if (mapper != null) {
return mapper.getInstanceGetter().get();
}
if (defaultClass != null) {
return newInstance(defaultClass);
}
if (Map.class == beanClass) {
return (T) new HashMap<>();
}
if (List.class == beanClass) {
return (T) new ArrayList<>();
}
if (Set.class == beanClass) {
return (T) new HashSet<>();
}
throw new NotFoundException("can't create instance for " + beanClass);
}
@Override
@SuppressWarnings("unchecked")
public <T> Class<T> getInstanceType(Class<T> beanClass) {
Mapper<T> mapper = realTypeMapper.get(beanClass);
if (null != mapper) {
return mapper.getTarget();
}
mapper = initCache(beanClass);
if (mapper != null) {
return mapper.getTarget();
}
return Modifier.isAbstract(beanClass.getModifiers())
|| Modifier.isInterface(beanClass.getModifiers())
? null : beanClass;
}
public void setDefaultMapperFactory(DefaultMapperFactory defaultMapperFactory) {
Objects.requireNonNull(defaultMapperFactory);
this.defaultMapperFactory = defaultMapperFactory;
}
public void setDefaultPropertyCopier(DefaultPropertyCopier defaultPropertyCopier) {
this.defaultPropertyCopier = defaultPropertyCopier;
}
public static class Mapper<T> {
Class<T> target;
Supplier<T> instanceGetter;
public Mapper(Class<T> target, Supplier<T> instanceGetter) {
this.target = target;
this.instanceGetter = instanceGetter;
}
public Class<T> getTarget() {
return target;
}
public Supplier<T> getInstanceGetter() {
return instanceGetter;
}
}
public static <T> Mapper<T> defaultMapper(Class<T> target) {
return new Mapper<>(target, defaultInstanceGetter(target));
}
public static <T> Supplier<T> defaultInstanceGetter(Class<T> clazz) {
return new DefaultInstanceGetter<>(clazz);
}
static class DefaultInstanceGetter<T> implements Supplier<T> {
Class<T> type;
public DefaultInstanceGetter(Class<T> type) {
this.type = type;
}
@Override
@SneakyThrows
public T get() {
return type.newInstance();
}
}
}

View File

@@ -0,0 +1,11 @@
package org.hswebframework.web.crud.entity.factory;
/**
* 属性复制接口,用于自定义属性复制
*
* @author zhouhao
* @since 3.0
*/
public interface PropertyCopier<S, T> {
T copyProperties(S source, T target);
}

View File

@@ -0,0 +1,15 @@
package org.hswebframework.web.crud.events;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
@AllArgsConstructor
@Getter
public class EntityCreatedEvent<E> implements Serializable {
private E entity;
private Class<E> entityType;
}

View File

@@ -0,0 +1,20 @@
package org.hswebframework.web.crud.events;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
@AllArgsConstructor
@Getter
public class EntityModifyEvent<E> implements Serializable{
private static final long serialVersionUID = -7158901204884303777L;
private E before;
private E after;
private Class<E> entityType;
}

View File

@@ -0,0 +1,90 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.ezorm.rdb.mapping.SyncDelete;
import org.hswebframework.ezorm.rdb.mapping.SyncQuery;
import org.hswebframework.ezorm.rdb.mapping.SyncRepository;
import org.hswebframework.ezorm.rdb.mapping.SyncUpdate;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.web.crud.entity.PagerResult;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface CrudService<E, K> {
SyncRepository<E, K> getRepository();
default SyncQuery<E> createQuery() {
return getRepository().createQuery();
}
default SyncUpdate<E> createUpdate() {
return getRepository().createUpdate();
}
default SyncDelete createDelete() {
return getRepository().createDelete();
}
@Transactional(readOnly = true)
default Optional<E> findById(K id) {
return getRepository()
.findById(id);
}
@Transactional(readOnly = true)
default List<E> findById(Collection<K> id) {
return getRepository()
.findById(id);
}
@Transactional
default SaveResult save(E... entityArr) {
return getRepository()
.save(entityArr);
}
@Transactional
default SaveResult save(Collection<E> entityArr) {
return getRepository()
.save(entityArr);
}
@Transactional
default int deleteById(K... idArr) {
return getRepository().deleteById(idArr);
}
@Transactional
default int deleteById(Collection<K> idArr) {
return getRepository().deleteById(idArr);
}
@Transactional(readOnly = true)
default List<E> query(QueryParam queryParam) {
return createQuery().setParam(queryParam).fetch();
}
@Transactional(readOnly = true)
default PagerResult<E> queryPager(QueryParam param) {
int count = count(param);
if (count == 0) {
return PagerResult.empty();
}
param.rePaging(count);
return PagerResult.of(count, query(param), param);
}
@Transactional(readOnly = true)
default Integer count(QueryParam param) {
return getRepository()
.createQuery()
.setParam(param)
.count();
}
}

View File

@@ -0,0 +1,16 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.ezorm.rdb.mapping.SyncRepository;
import org.springframework.beans.factory.annotation.Autowired;
public class DefaultCrudService<E,K> implements CrudService<E,K> {
@Autowired
private SyncRepository<E, K> repository;
@Override
public SyncRepository<E, K> getRepository() {
return repository;
}
}

View File

@@ -1,7 +1,10 @@
package org.hswebframework.web.crud.service;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.ezorm.rdb.mapping.ReactiveDelete;
import org.hswebframework.ezorm.rdb.mapping.ReactiveQuery;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.ezorm.rdb.mapping.ReactiveUpdate;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.web.crud.entity.PagerResult;
import org.reactivestreams.Publisher;
@@ -15,6 +18,18 @@ public interface ReactiveCrudService<E, K> {
ReactiveRepository<E, K> getRepository();
default ReactiveQuery<E> createQuery() {
return getRepository().createQuery();
}
default ReactiveUpdate<E> createUpdate() {
return getRepository().createUpdate();
}
default ReactiveDelete createDelete() {
return getRepository().createDelete();
}
@Transactional(readOnly = true)
default Mono<E> findById(Mono<K> publisher) {
return getRepository().findById(publisher);

View File

@@ -52,7 +52,7 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>

View File

@@ -19,7 +19,7 @@
package org.hswebframework.web.commons.entity.factory;
import lombok.SneakyThrows;
import org.hswebframework.web.NotFoundException;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.bean.BeanFactory;
import org.hswebframework.web.bean.FastBeanCopier;

View File

@@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2019 http://www.hswebframework.org
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~
-->
<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">
<parent>
<artifactId>hsweb-commons</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>通用增删改查-通用model模块</description>
<artifactId>hsweb-commons-model</artifactId>
<dependencies>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-bean</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,29 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package org.hswebframework.web.commons.model;
import org.hswebframework.web.commons.bean.ValidateBean;
import java.io.Serializable;
/**
* @author zhouhao
*/
public interface Model extends ValidateBean {
}

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ /*
~ * Copyright 2019 http://www.hswebframework.org
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
~ */
-->
<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">
<parent>
<artifactId>hsweb-commons-service</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<description>通用增删改查-通用服务接口模块</description>
<artifactId>hsweb-commons-service-api</artifactId>
<dependencies>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-dao-api</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,41 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
/**
* 实体创建服务接口,通过此接口创建实体.在创建实体类时,建议使用此接口进行创建,而不是使用new
* 如:
* <code>
* YourBean bean = service.createEntity();
* </code>
*
* @author zhouhao
* @since 3.0
*/
public interface CreateEntityService<E> extends Service {
/**
* 创建实体
*
* @return 实体
*/
E createEntity();
Class<E> getEntityInstanceType();
}

View File

@@ -1,34 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
/**
* 通用Service,实现增删改查
*
* @author zhouhao
* @since 3.0
*/
public interface CrudService<E, PK> extends
QueryByEntityService<E>,
UpdateService<E,PK>,
InsertService<E, PK>,
DeleteService<E,PK>,
CreateEntityService<E>,
QueryService<E, PK> {
}

View File

@@ -1,33 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
/**
* @author zhouhao
*/
public interface DeleteService<E,PK> extends Service {
/**
* 根据主键删除记录
*
* @param pk 主键
* @return 影响记录数
*/
E deleteByPk(PK pk);
}

View File

@@ -1,33 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
/**
* @author zhouhao
*/
public interface InsertService<E, PK> extends Service {
/**
* 添加一条数据
*
* @param data 要添加的数据
* @return 添加后生成的主键
*/
PK insert(E data);
}

View File

@@ -1,67 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.PagerResult;
import java.util.List;
/**
* 根据实体类参数执行各种查询的通用服务类
*
* @param <E> 实体类型
* @author zhouhao
* @see org.hswebframework.web.commons.entity.param.QueryParamEntity
* @since 3.0
*/
public interface QueryByEntityService<E> extends Service {
/**
* 按分页查询
*
* @param param 参数
* @return 分页查询结果
*/
PagerResult<E> selectPager(Entity param);
/**
* 直接查询
*
* @param param 查询参数
* @return 查询结果
*/
List<E> select(Entity param);
/**
* 查询总数
*
* @param param 查询参数
* @return 总数
*/
int count(Entity param);
/**
* 查询单条数据,如果存在多条数据,则返回第一条
*
* @param param 查询参数
* @return 查询结果
*/
E selectSingle(Entity param);
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.service;
import java.util.List;
/**
* 查询服务接口,提供基本的查询功能
* @author zhouhao
* @since 3.0
* @see QueryByEntityService
*/
public interface QueryService<E, PK> extends Service {
/**
* 根据主键查询
* @param id 主键
* @return 查询结果,无结果时返回{@code null}
*/
E selectByPk(PK id);
/**
* 根据多个主键查询
* @param id 主键集合
* @return 查询结果,如果无结果返回空集合,而不是返回{@code null}
*/
List<E> selectByPk(List<PK> id);
/**
* 查询所有结果
* @return 所有结果,如果无结果则返回空集合,而不是返回{@code null}
*/
List<E> select();
/**
* 查询结果总数
* @return 结果总数
*/
int count();
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.TreeSupportEntity;
import java.util.Collection;
import java.util.List;
/**
* 树结构实体服务,提供对树结果实体的常用操作
*
* @author zhouhao
* @since 3.0
*/
public interface TreeService<E extends TreeSupportEntity, PK> extends Service {
/**
* 查询所有父节点
* @param childId 子节点id
* @return 父节点集合
*/
List<E> selectParentNode(PK childId);
/**
* 根据父节点id获取子节点数据
*
* @param parentId 父节点ID
* @return 子节点数据
*/
List<E> selectChildNode(PK parentId);
/**
* 根据父节点id,获取所有子节点的数据,包含字节点的字节点
*
* @param parentId 父节点ID
* @return 所有子节点的数据
*/
List<E> selectAllChildNode(PK parentId);
/**
* 批量修改数据,如果集合中的数据不存在,则将会进行新增
*
* @param data 数据集合
* @return 修改的数量
*/
int updateBatch(Collection<E> data);
/**
* 批量添加数据
*
* @param data 数据集合
* @return 被添加数据集合的主键
*/
List<PK> insertBatch(Collection<E> data);
}

View File

@@ -1,49 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import java.util.List;
public interface UpdateService<E, PK> extends Service {
/**
* 修改记录信息
*
* @param data 要修改的对象
* @return 影响记录数
*/
int updateByPk(PK id, E data);
/**
* 批量修改记录
*
* @param data 要修改的记录集合
* @return 影响记录数
*/
int updateByPk(List<E> data);
/**
* 保存或修改
*
* @param e 要修改的数据
* @return 影响记录数
*/
PK saveOrUpdate(E e);
}

View File

@@ -1,12 +0,0 @@
package org.hswebframework.web.service;
/**
* @author zhouhao
*/
public interface Validator<T> {
boolean validate(T data);
default String getErrorMessage() {
return "{validation_fail}";
}
}

View File

@@ -1,43 +0,0 @@
# 通用服务类
提供通用增删改查服务
## DSL查改删
查询,实现`DefaultDSLQueryService`接口
```java
// select * from user where name = ? limit 0,1
createQuery().where("name","张三").single();
```
```java
// select * from user where name = ? or name = ?
createQuery().where("name","张三").or().is("name","李四").list();
```
```java
//select * from user where name = ? and (age> ? and age <?)
createQuery().where("name","张三").nest().gt("age",10).or().lt("age",20).end().list();
```
修改,实现`DefaultDSLUpdateService`接口
```java
// update user set ... where name = ?
createUpdate(data).where("name","张三").exec();
//不会修改为null的属性,
```
```java
// update user set name=?,age=? where name = ?
createUpdate(data).include("name","age").where("name","张三").exec();
```
```java
// update user set name=? where name = ?
createUpdate().set("name","新张三").where("name","张三").exec();
```
删除,实现`DefaultDSLDeleteService`接口
```java
//delete from user where name = ?
createDelete().where("name","张三").exec();
```
查改删,条件使用的方式都相同.

View File

@@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ /*
~ * Copyright 2019 http://www.hswebframework.org
~ *
~ * Licensed under the Apache License, Version 2.0 (the "License");
~ * you may not use this file except in compliance with the License.
~ * You may obtain a copy of the License at
~ *
~ * http://www.apache.org/licenses/LICENSE-2.0
~ *
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS,
~ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
~ */
-->
<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">
<parent>
<artifactId>hsweb-commons-service</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-commons-service-simple</artifactId>
<description>通用增删改查-通用服务本地实现模块</description>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-service-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-commons-dao-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,147 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.utils.ClassUtils;
import org.hswebframework.web.NotFoundException;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.factory.EntityFactory;
import org.hswebframework.web.validate.SimpleValidateResults;
import org.hswebframework.web.validate.ValidationException;
import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.util.Set;
import java.util.function.Supplier;
/**
* 抽象服务类,提供通用模板方法、类,如验证器,实体工厂等
*
* @author zhouhao
* @see CreateEntityService
* @see Service
*/
public abstract class AbstractService<E extends Entity, PK> implements CreateEntityService<E>, Service {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
protected Validator validator;
protected EntityFactory entityFactory;
protected LogicPrimaryKeyValidator logicPrimaryKeyValidator;
@Autowired(required = false)
public void setValidator(Validator validator) {
this.validator = validator;
}
@Autowired(required = false)
public void setEntityFactory(EntityFactory entityFactory) {
this.entityFactory = entityFactory;
}
@Autowired(required = false)
public void setLogicPrimaryKeyValidator(LogicPrimaryKeyValidator logicPrimaryKeyValidator) {
this.logicPrimaryKeyValidator = logicPrimaryKeyValidator;
}
protected Class<E> entityType;
protected Class<PK> primaryKeyType;
@SuppressWarnings("unchecked")
public AbstractService() {
primaryKeyType = (Class<PK>) ClassUtils.getGenericType(this.getClass(), 1);
entityType = (Class<E>) ClassUtils.getGenericType(this.getClass(), 0);
}
protected boolean entityFactoryIsEnabled() {
if (entityFactory == null) {
logger.warn("entityFactory is null!");
}
return null != entityFactory;
}
@Override
public Class<E> getEntityInstanceType() {
return entityFactory.getInstanceType(getEntityType());
}
public Class<E> getEntityType() {
return entityType;
}
protected Class<PK> getPrimaryKeyType() {
return primaryKeyType;
}
@Override
public E createEntity() {
if (!entityFactoryIsEnabled()) {
throw new UnsupportedOperationException("{unsupported_operation}");
}
return entityFactory.newInstance(getEntityType());
}
protected <T> void tryValidateProperty(org.hswebframework.web.service.Validator<T> validator, String property, T value) {
if (validator != null) {
if (!validator.validate(value)) {
throw new ValidationException(validator.getErrorMessage(), property);
}
}
}
protected <T> void tryValidateProperty(org.hswebframework.web.service.Validator<T> validator, String property, T value, String message) {
if (validator != null) {
if (!validator.validate(value)) {
throw new ValidationException(message, property);
}
}
}
protected void tryValidateProperty(boolean success, String property, String message) {
if (!success) {
throw new ValidationException(message, property);
}
}
protected void tryValidate(Object data, String property, Class... groups) {
validate(() -> validator.validateProperty(data, property, groups));
}
protected <T> void tryValidate(Class<T> type, String property, Object value, Class... groups) {
validate(() -> validator.validateValue(type, property, value, groups));
}
protected void tryValidate(Object data, Class... groups) {
validate(() -> validator.validate(data, groups));
}
private <T> void validate(Supplier<Set<ConstraintViolation<T>>> validatorSetFunction) {
if (validator == null) {
logger.warn("validator is null!");
return;
}
SimpleValidateResults results = new SimpleValidateResults();
validatorSetFunction.get()
.forEach(violation -> results.addResult(violation.getPropertyPath().toString(), violation.getMessage()));
if (!results.isSuccess()) {
throw new ValidationException(results);
}
}
public static void assertNotNull(Object data) {
assertNotNull(data, "{data_not_found}");
}
public static void assertNotNull(Object data, String message) {
if (null == data) {
throw new NotFoundException(message);
}
}
}

View File

@@ -1,184 +0,0 @@
/*
* Copyright 2019 http://www.hswebframework.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.utils.RandomUtil;
import org.hswebframework.web.commons.entity.TreeSortSupportEntity;
import org.hswebframework.web.commons.entity.TreeSupportEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 抽象树形结构服务类
*
* @author zhouhao
* @see TreeSortSupportEntity
* @since 3.0
*/
public abstract class AbstractTreeSortService<E extends TreeSortSupportEntity<PK>, PK>
extends GenericEntityService<E, PK> implements TreeService<E, PK> {
@Override
@Transactional(readOnly = true)
public List<E> selectParentNode(PK childId) {
assertNotNull(childId);
E old = selectByPk(childId);
if (null == old) {
return new ArrayList<>();
}
return createQuery()
.where()
// where ? like concat(path,'%')
.and("path$like$reverse$startWith", old.getPath())
.listNoPaging();
}
@Override
@Transactional(readOnly = true)
public List<E> selectAllChildNode(PK parentId) {
assertNotNull(parentId);
E old = selectByPk(parentId);
if (null == old) {
return new ArrayList<>();
}
return createQuery()
.where()
.like$(TreeSupportEntity.path, old.getPath())
.listNoPaging();
}
@Override
@Transactional(readOnly = true)
public List<E> selectChildNode(PK parentId) {
assertNotNull(parentId);
return createQuery()
.where(TreeSupportEntity.parentId, parentId)
.listNoPaging();
}
//当父节点不存在时,创建parentId
@SuppressWarnings("unchecked")
protected PK createParentIdOnExists() {
if (getPrimaryKeyType() == String.class) {
return (PK) "-1";
}
return null;
}
protected void applyPath(E entity) {
if (StringUtils.isEmpty(entity.getParentId())) {
if (entity.getSortIndex() == null) {
entity.setSortIndex(0L);
}
entity.setParentId(createParentIdOnExists());
entity.setLevel(0);
entity.setPath(RandomUtil.randomChar(4));
return;
}
if (!StringUtils.isEmpty(entity.getPath())) {
return;
}
TreeSortSupportEntity<PK> parent = selectByPk(entity.getParentId());
if (null == parent) {
if (entity.getSortIndex() == null) {
entity.setSortIndex(0L);
}
entity.setParentId(createParentIdOnExists());
entity.setPath(RandomUtil.randomChar(4));
entity.setLevel(0);
} else {
if (entity.getSortIndex() == null && parent.getSortIndex() != null) {
entity.setSortIndex(parent.getSortIndex() * 10);
}
entity.setPath(parent.getPath() + "-" + RandomUtil.randomChar(4));
entity.setLevel(entity.getPath().split("[-]").length);
}
}
@Override
public PK insert(E entity) {
if (StringUtils.isEmpty(entity.getId())) {
entity.setId(getIDGenerator().generate());
}
applyPath(entity);
List<E> childrenList = new ArrayList<>();
TreeSupportEntity.expandTree2List(entity, childrenList, getIDGenerator());
childrenList.forEach(this::saveOrUpdateForSingle);
return entity.getId();
}
@Override
public List<PK> insertBatch(Collection<E> data) {
return data.stream()
.map(this::insert)
.collect(Collectors.toList());
}
@Override
public int updateBatch(Collection<E> data) {
assertNotNull(data);
return data.stream()
.mapToInt(this::updateByPk)
.sum();
}
@Override
public int updateByPk(E entity) {
assertNotNull(entity);
List<E> childrenList = new ArrayList<>();
TreeSupportEntity.expandTree2List(entity, childrenList, getIDGenerator());
childrenList.forEach(this::saveOrUpdateForSingle);
return childrenList.size() + 1;
}
protected PK saveOrUpdateForSingle(E entity) {
assertNotNull(entity);
PK id = entity.getId();
if (StringUtils.isEmpty(id) || this.selectByPk(id) == null) {
if (StringUtils.isEmpty(id)) {
entity.setId(getIDGenerator().generate());
}
applyPath(entity);
return super.insert(entity);
}
super.updateByPk(entity);
return id;
}
@Override
public E deleteByPk(PK id) {
E old = selectByPk(id);
assertNotNull(old);
if (StringUtils.isEmpty(old.getPath())) {
getDao().deleteByPk(id);
} else {
DefaultDSLDeleteService.createDelete(getDao())
// where path like 'path%'
.where().like$(TreeSupportEntity.path, old.getPath())
.exec();
}
return old;
}
}

View File

@@ -1,56 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.ezorm.core.dsl.Delete;
import org.hswebframework.web.commons.entity.param.DeleteParamEntity;
import org.hswebframework.web.dao.dynamic.DeleteByEntityDao;
/**
* @author zhouhao
*/
public interface DefaultDSLDeleteService<E, PK> extends DefaultDeleteService<E, PK> {
DeleteByEntityDao getDao();
default Delete<DeleteParamEntity> createDelete() {
Delete<DeleteParamEntity> delete = new Delete<>(new DeleteParamEntity());
delete.setExecutor(getDao()::delete);
return delete;
}
static Delete<DeleteParamEntity> createDelete(DeleteByEntityDao deleteDao) {
Delete<DeleteParamEntity> update = new Delete<>(new DeleteParamEntity());
update.setExecutor(deleteDao::delete);
return update;
}
/**
* 自定义一个删除执行器。创建dsl数据删除操作对象
*
* @param executor 执行器
* @return {@link Delete}
* @since 3.0
*/
static Delete<DeleteParamEntity> createDelete(Delete.Executor<DeleteParamEntity> executor) {
Delete<DeleteParamEntity> update = new Delete<>(new DeleteParamEntity());
update.setExecutor(executor);
return update;
}
}

View File

@@ -1,91 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.ezorm.core.dsl.Query;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.dao.dynamic.QueryByEntityDao;
import java.util.List;
public interface DefaultDSLQueryService<E, PK>
extends DefaultQueryByEntityService<E>, QueryService<E, PK> {
@Override
default List<E> select() {
return createQuery().noPaging().list();
}
@Override
default int count() {
return createQuery().total();
}
/**
* 创建本服务的dsl查询操作对象
* 可通过返回的Query对象进行dsl方式操作如:<br>
* <code>
* createQuery().where("id",1).single();
* </code>
*
* @return {@link Query}
* @see Query
* @see org.hswebframework.ezorm.core.Conditional
* @since 3.0
*/
default Query<E, QueryParamEntity> createQuery() {
Query<E, QueryParamEntity> query = Query.empty(new QueryParamEntity());
query.setListExecutor(this::select);
query.setTotalExecutor(this::count);
query.setSingleExecutor(this::selectSingle);
query.noPaging();
return query;
}
/**
* 指定一个dao映射接口,接口需继承{@link QueryByEntityDao}创建dsl数据查询对象<br>
* 可通过返回的Query对象进行dsl方式操作如:<br>
* <code>
* createQuery(userMapper).where("id",1).single();
* </code>
*
* @param dao dao接口
* @param <PO> PO泛型
* @return {@link Query}
* @see Query
* @see org.hswebframework.ezorm.core.Conditional
* @since 3.0
*/
static <PO> Query<PO, QueryParamEntity> createQuery(QueryByEntityDao<PO> dao) {
Query<PO, QueryParamEntity> query = new Query<>(new QueryParamEntity());
query.setListExecutor(dao::query);
query.setTotalExecutor(dao::count);
query.setSingleExecutor((param) -> {
param.doPaging(0, 1);
List<PO> list = dao.query(param);
if (null == list || list.isEmpty()) {
return null;
} else {
return list.get(0);
}
});
query.noPaging();
return query;
}
}

View File

@@ -1,49 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.ezorm.core.dsl.Update;
import org.hswebframework.web.dao.dynamic.UpdateByEntityDao;
import org.hswebframework.web.commons.entity.param.UpdateParamEntity;
/**
* 默认的DSL方式更新服务
*
* @author zhouhao
*/
public interface DefaultDSLUpdateService<E, PK> extends UpdateService<E, PK> {
UpdateByEntityDao getDao();
default Update<E, UpdateParamEntity<E>> createUpdate(E data) {
return createUpdate(getDao(), data);
}
default Update<E, UpdateParamEntity<E>> createUpdate() {
return createUpdate(getDao());
}
static <E> Update<E, UpdateParamEntity<E>> createUpdate(UpdateByEntityDao dao) {
return Update.build(dao::update, new UpdateParamEntity<>());
}
static <E> Update<E, UpdateParamEntity<E>> createUpdate(UpdateByEntityDao dao, E data) {
return Update.build(dao::update, new UpdateParamEntity<>(data));
}
}

View File

@@ -1,33 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
/**
* @author zhouhao
*/
public interface DefaultDeleteService<E,PK> extends DeleteService<E,PK> {
/**
* 根据主键删除记录
*
* @param pk 主键
* @return 影响记录数
*/
@Override
E deleteByPk(PK pk);
}

View File

@@ -1,263 +0,0 @@
package org.hswebframework.web.service;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.core.dsl.Query;
import org.hswebframework.ezorm.core.param.TermType;
import org.hswebframework.web.bean.FastBeanCopier;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.validator.LogicPrimaryKey;
import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SuppressWarnings("all")
@Slf4j
public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator {
private static final Map<Class, Map<Class, Validator>> validatorCache = new HashMap<>();
private static final DefaultLogicPrimaryKeyValidator instrance = new DefaultLogicPrimaryKeyValidator();
protected DefaultLogicPrimaryKeyValidator() {
}
public static DefaultLogicPrimaryKeyValidator getInstrance() {
return instrance;
}
private static final Validator ALWAYS_PASSED_VALIDATOR = bean -> {
return Result.passed();
};
public static <T> void registerQuerySuppiler(Class<T> type, Function<T, Query<T, QueryParamEntity>> querySupplier) {
validatorCache.computeIfAbsent(type, instrance::createValidator)
.values()
.stream()
.filter(DefaultValidator.class::isInstance)
.map(DefaultValidator.class::cast)
.forEach(validator -> validator.querySupplier = querySupplier);
}
@Override
public Result validate(Object bean, Class... groups) {
Class target = ClassUtils.getUserClass(bean);
Result result;
if (null != groups && groups.length > 0) {
result = Arrays.stream(groups)
.map(group ->
validatorCache.computeIfAbsent(target, this::createValidator)
.getOrDefault(group, ALWAYS_PASSED_VALIDATOR)
.doValidate(bean))
.filter(Result::isError)
.findFirst()
.orElseGet(Result::passed);
} else {
result = validatorCache.computeIfAbsent(target, this::createValidator)
.getOrDefault(Void.class, ALWAYS_PASSED_VALIDATOR)
.doValidate(bean);
}
return result;
}
protected Map<Class, Validator> createValidator(Class target) {
//属性名:注解
Map<String, LogicPrimaryKey> keys = new HashMap<>();
ReflectionUtils.doWithFields(target, field -> {
LogicPrimaryKey primaryKey = field.getAnnotation(LogicPrimaryKey.class);
if (primaryKey != null) {
keys.put(field.getName(), primaryKey);
}
});
//获取类上的注解
Class[] tempClass = new Class[]{target};
LogicPrimaryKey classAnn = null;
Class[] empty = new Class[0];
while (tempClass.length != 0) {
for (Class group : tempClass) {
classAnn = AnnotationUtils.findAnnotation(group, LogicPrimaryKey.class);
if (null != classAnn) {
if (classAnn.value().length > 0) {
for (String field : classAnn.value()) {
keys.put(field, classAnn);
}
tempClass = empty;
continue;
} else {
//如果注解没有指定字段则从group中获取
tempClass = classAnn.groups();
if (tempClass.length == 1 && tempClass[0] == Void.class) {
log.warn("类{}的注解{}无效,请设置value属性或者group属性", classAnn, tempClass);
continue;
}
}
} else {
tempClass = empty;
continue;
}
}
}
if (keys.isEmpty()) {
return new java.util.HashMap<>();
}
return keys.entrySet()
.stream()
.flatMap(entry -> Stream.of(entry.getValue().groups())
.flatMap(group -> Optional.ofNullable(entry.getValue().value())
.map(Arrays::asList)
.filter(CollectionUtils::isNotEmpty)
.orElse(Arrays.asList(entry.getKey()))
.stream()
.map(field -> LogicPrimaryKeyField.builder()
.field(field)
.termType(entry.getValue().termType())
.condition(entry.getValue().condition())
.matchNullOrEmpty(entry.getValue().matchNullOrEmpty())
.group(group)
.build())
))
.collect(Collectors.groupingBy(
//按group分组
LogicPrimaryKeyField::getGroup,
//将每一组的集合构造为验证器对象
Collectors.collectingAndThen(
Collectors.mapping(Function.identity(), Collectors.toSet())
, list -> DefaultValidator.builder()
.infos(list)
.targetType(target)
.build())
)
);
}
interface Validator<T> {
Result doValidate(T bean);
}
@Builder
static class DefaultValidator<T> implements Validator<T> {
private Set<LogicPrimaryKeyField> infos = new HashSet<>();
private Class<T> targetType;
private volatile Function<T, Query<T, QueryParamEntity>> querySupplier;
public Result doValidate(T bean) {
if (querySupplier == null) {
log.warn("未设置查询函数," +
"你可以在服务初始化的时候通过调用" +
"DefaultLogicPrimaryKeyValidator" +
".registerQuerySuppiler({},bean -> this.createQuery().not(\"id\", bean.getId()))" +
"进行设置"
, targetType);
return Result.passed();
}
Query<T, QueryParamEntity> query = querySupplier.apply(bean);
//转为map
Map<String, Object> mapBean = FastBeanCopier.copy(bean, new HashMap<>());
Map<String, Object> properties = new HashMap<>();
for (LogicPrimaryKeyField info : infos) {
String field = info.getField();
Object value = mapBean.get(field);
//为空,可能是有字段嵌套,也有可能是真的null
if (value == null) {
String tmpField = field;
Object tmpValue = null;
Map<String, Object> tempMapBean = mapBean;
//嵌套的场景
while (tmpValue == null && tmpField.contains(".")) {
String[] nest = tmpField.split("[.]", 2);
Object nestObject = tempMapBean.get(nest[0]);
if (nestObject == null) {
break;
}
if (nestObject instanceof Map) {
tempMapBean = ((Map) nestObject);
} else {
tempMapBean = FastBeanCopier.copy(nestObject, new HashMap<>());
}
tmpField = nest[1];
tmpValue = tempMapBean.get(tmpField);
}
value = tmpValue;
}
if (StringUtils.isEmpty(value)) {
if (info.matchNullOrEmpty) {
if (value == null) {
query.isNull(info.getField());
} else {
query.isEmpty(info.getField());
}
}
} else {
String termType = StringUtils.isEmpty(info.termType) ? TermType.eq : info.termType;
query.and(info.getField(), termType, value);
}
properties.put(info.getField(), value);
}
T result = query.single();
if (result != null) {
Result validateResult = new Result();
validateResult.setError(true);
validateResult.setData(result);
validateResult.setProperties(properties);
return validateResult;
}
return Result.passed();
}
}
@Getter
@Setter
@Builder
private static class LogicPrimaryKeyField {
private String field;
private String condition;
private boolean matchNullOrEmpty;
private String termType;
private Class group;
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LogicPrimaryKeyField) {
return hashCode() == obj.hashCode();
}
return false;
}
}
}

View File

@@ -1,134 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.ezorm.core.dsl.Query;
import org.hswebframework.web.commons.entity.Entity;
import org.hswebframework.web.commons.entity.PagerResult;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.dao.dynamic.QueryByEntityDao;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface DefaultQueryByEntityService<E>
extends QueryByEntityService<E> {
QueryByEntityDao<E> getDao();
/**
* 分页进行查询数据,查询条件同 {@link DefaultQueryByEntityService#select}
*
* @param param 查询参数
* @return 分页查询结果
* @see QueryParamEntity
* @see QueryParamEntity#newQuery()
*/
@Override
default PagerResult<E> selectPager(Entity param) {
PagerResult<E> pagerResult = new PagerResult<>();
if (param instanceof QueryParamEntity) {
QueryParamEntity entity = ((QueryParamEntity) param);
//不分页,不进行count
if (!entity.isPaging()) {
pagerResult.setData(getDao().query(param));
pagerResult.setTotal(pagerResult.getData().size());
pagerResult.setPageIndex(entity.getThinkPageIndex());
pagerResult.setPageSize(pagerResult.getData().size());
return pagerResult;
}
}
int total = getDao().count(param);
pagerResult.setTotal(total);
//根据实际记录数量重新指定分页参数
if (param instanceof QueryParamEntity) {
QueryParamEntity paramEntity = (QueryParamEntity) param;
paramEntity.rePaging(total);
pagerResult.setPageSize(paramEntity.getPageSize());
pagerResult.setPageIndex(paramEntity.getThinkPageIndex());
}
if (total == 0) {
pagerResult.setData(new java.util.ArrayList<>());
} else {
pagerResult.setData(select(param));
}
return pagerResult;
}
/**
* 根据查询参数进行查询,参数可使用 {@link Query}进行构建
*
* @param param 查询参数
* @return 查询结果
* @see QueryParamEntity
* @see QueryParamEntity#newQuery()
*/
@Override
@Transactional(readOnly = true)
default List<E> select(Entity param) {
if (param == null) {
param = QueryParamEntity.empty();
}
return getDao().query(param);
}
/**
* 查询记录总数,用于分页等操作。查询条件同 {@link DefaultQueryByEntityService#select}
*
* @param param 查询参数
* @return 查询结果实现mapper中的sql应指定默认值否则可能抛出异常
* @see QueryParamEntity
* @see QueryParamEntity#newQuery()
*/
@Override
@Transactional(readOnly = true)
default int count(Entity param) {
if (param == null) {
param = QueryParamEntity.empty();
}
return getDao().count(param);
}
/**
* 查询只返回单个结果,如果有多个结果,只返回第一个
*
* @param param 查询条件
* @return 单个查询结果
* @see QueryParamEntity
* @see QueryParamEntity#newQuery()
*/
@Override
@Transactional(readOnly = true)
default E selectSingle(Entity param) {
if (param instanceof QueryParamEntity) {
((QueryParamEntity) param).doPaging(0, 1);
}
List<E> list = this.select(param);
if (list.isEmpty()) {
return null;
} else {
return list.get(0);
}
}
}

View File

@@ -1,72 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.GenericEntity;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import java.util.List;
/**
* @author zhouhao
* @see org.springframework.cache.annotation.CacheConfig
* @see Cacheable
* @see CacheEvict
* @since 3.0
*/
public abstract class EnableCacheAllEvictGenericEntityService<E extends GenericEntity<PK>, PK> extends GenericEntityService<E, PK> {
@Override
@Cacheable(key = "'id:'+#pk")
public E selectByPk(PK pk) {
return super.selectByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(List<E> data) {
return super.updateByPk(data);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(PK pk, E entity) {
return super.updateByPk(pk, entity);
}
@Override
@CacheEvict(allEntries = true)
public PK insert(E entity) {
return super.insert(entity);
}
@Override
@CacheEvict(allEntries = true)
public E deleteByPk(PK pk) {
return super.deleteByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public PK saveOrUpdate(E entity) {
return super.saveOrUpdate(entity);
}
@Override
@Cacheable(key = "'all'")
public List<E> select() {
return super.select();
}
@Override
@Cacheable(key = "'id-in:'+#id.hashCode()")
public List<E> selectByPk(List<PK> id) {
return super.selectByPk(id);
}
@Override
@Cacheable(key = "'count'")
public int count() {
return super.count();
}
}

View File

@@ -1,94 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.TreeSortSupportEntity;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import java.util.Collection;
import java.util.List;
/**
* @author zhouhao
* @since 3.0
*/
public abstract class EnableCacheAllEvictTreeSortService<E extends TreeSortSupportEntity<PK>, PK>
extends AbstractTreeSortService<E, PK> {
@Override
@Cacheable(key = "'parent:'+#parentId")
public List<E> selectParentNode(PK parentId) {
return super.selectParentNode(parentId);
}
@Override
@Cacheable(key = "'chidlren:'+#parentId")
public List<E> selectChildNode(PK parentId) {
return super.selectChildNode(parentId);
}
@Override
@Cacheable(key = "'all-chidlren:'+#parentId")
public List<E> selectAllChildNode(PK parentId) {
return super.selectAllChildNode(parentId);
}
@Override
@CacheEvict(allEntries = true)
public int updateBatch(Collection<E> data) {
return super.updateBatch(data);
}
@Override
@Cacheable(key = "'id:'+#pk")
public E selectByPk(PK pk) {
return super.selectByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(List<E> data) {
return super.updateByPk(data);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(PK pk, E entity) {
return super.updateByPk(pk, entity);
}
@Override
@CacheEvict(allEntries = true)
public PK insert(E entity) {
return super.insert(entity);
}
@Override
@CacheEvict(allEntries = true)
public E deleteByPk(PK pk) {
return super.deleteByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public PK saveOrUpdate(E entity) {
return super.saveOrUpdate(entity);
}
@Override
@Cacheable(key = "'id-in:'+#id.hashCode()")
public List<E> selectByPk(List<PK> id) {
return super.selectByPk(id);
}
@Override
@Cacheable(key = "'all'")
public List<E> select() {
return super.select();
}
@Override
@Cacheable(key = "'count'")
public int count() {
return super.count();
}
}

View File

@@ -1,93 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.GenericEntity;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import java.util.List;
/**
* 启用缓冲的通用实体曾删改查服务,继承此类
* 在类上注解{@link org.springframework.cache.annotation.CacheConfig}即可
*
* @author zhouhao
* @see org.springframework.cache.annotation.CacheConfig
* @see Cacheable
* @see CacheEvict
* @since 3.0
*/
public abstract class EnableCacheGenericEntityService<E extends GenericEntity<PK>, PK> extends GenericEntityService<E, PK> {
@Override
@Cacheable(key = "'id:'+#pk")
public E selectByPk(PK pk) {
return super.selectByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(List<E> data) {
return super.updateByPk(data);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#pk"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public int updateByPk(PK pk, E entity) {
return super.updateByPk(pk, entity);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#result"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public PK insert(E entity) {
return super.insert(entity);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#pk"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public E deleteByPk(PK pk) {
return super.deleteByPk(pk);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#result"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public PK saveOrUpdate(E entity) {
return super.saveOrUpdate(entity);
}
@Override
@Cacheable(key = "'all'")
public List<E> select() {
return super.select();
}
@Override
@Cacheable(key = "'count'")
public int count() {
return super.count();
}
}

View File

@@ -1,95 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.TreeSortSupportEntity;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import java.util.Collection;
import java.util.List;
/**
* @author zhouhao
* @since 3.0
*/
public abstract class EnableCacheTreeSortService<E extends TreeSortSupportEntity<PK>, PK>
extends AbstractTreeSortService<E, PK> {
@Override
@CacheEvict(allEntries = true)
public int updateBatch(Collection<E> data) {
return super.updateBatch(data);
}
@Override
@Cacheable(key = "'id:'+#pk")
public E selectByPk(PK pk) {
return super.selectByPk(pk);
}
@Override
@CacheEvict(allEntries = true)
public int updateByPk(List<E> data) {
return super.updateByPk(data);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#pk"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public int updateByPk(PK pk, E entity) {
return super.updateByPk(pk, entity);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#result"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public PK insert(E entity) {
return super.insert(entity);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#pk"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public E deleteByPk(PK pk) {
return super.deleteByPk(pk);
}
@Override
@Caching(
evict = {
@CacheEvict(key = "'id:'+#result"),
@CacheEvict(key = "'all'"),
@CacheEvict(key = "'count'")
}
)
public PK saveOrUpdate(E entity) {
return super.saveOrUpdate(entity);
}
@Override
@Cacheable(key = "'all'")
public List<E> select() {
return super.select();
}
@Override
@Cacheable(key = "'count'")
public int count() {
return super.count();
}
}

View File

@@ -1,209 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.GenericEntity;
import org.hswebframework.web.commons.entity.LogicalDeleteEntity;
import org.hswebframework.web.commons.entity.RecordCreationEntity;
import org.hswebframework.web.commons.entity.RecordModifierEntity;
import org.hswebframework.web.commons.entity.events.EntityCreatedEvent;
import org.hswebframework.web.commons.entity.events.EntityModifyEvent;
import org.hswebframework.web.id.IDGenerator;
import org.hswebframework.web.validator.group.CreateGroup;
import org.hswebframework.web.validator.group.UpdateGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* 通用实体服务类,提供增删改查的默认实现
*
* @author zhouhao
*/
@Transactional(rollbackFor = Throwable.class)
public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
extends AbstractService<E, PK>
implements GenericService<E, PK> {
@SuppressWarnings("unchecked")
public GenericEntityService() {
super();
}
/**
* 获取ID生成器,在insert的时候如果ID为空,则调用生成器进行生成
*
* @return IDGenerator
* @see IDGenerator
*/
protected abstract IDGenerator<PK> getIDGenerator();
protected ApplicationEventPublisher eventPublisher;
@Autowired(required = false)
public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@PostConstruct
public void init() {
if (logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) {
DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(getEntityInstanceType(), bean -> this.createQuery().not("id", bean.getId()));
}
}
@Override
public E deleteByPk(PK pk) {
E old = selectByPk(pk);
if (old == null) {
return null;
}
if (old instanceof LogicalDeleteEntity) {
LogicalDeleteEntity deleteEntity = (LogicalDeleteEntity) old;
deleteEntity.setDeleted(true);
deleteEntity.setDeleteTime(System.currentTimeMillis());
createUpdate()
.set(deleteEntity::getDeleted)
.set(deleteEntity::getDeleteTime)
.where(GenericEntity.id, pk)
.exec();
} else {
if (!physicalDeleteByPk(pk)) {
logger.warn("物理删除数据失败,主键:{}", pk);
}
}
return old;
}
protected boolean physicalDeleteByPk(PK pk) {
//createDelete().where(GenericEntity.id,pk).exec()>0;
return getDao().deleteByPk(pk) > 0;
}
protected boolean pushModifyEvent() {
return RecordModifierEntity.class.isAssignableFrom(entityType);
}
protected boolean pushCreatedEvent() {
return RecordCreationEntity.class.isAssignableFrom(entityType);
}
@Override
public int updateByPk(PK pk, E entity) {
Assert.notNull(pk, "primary key can not be null");
Assert.hasText(String.valueOf(pk), "primary key can not be null");
Assert.notNull(entity, "entity can not be null");
entity.setId(pk);
tryValidate(entity, UpdateGroup.class);
//尝试推送 EntityModifyEvent 事件.
if (eventPublisher != null && pushModifyEvent()) {
E old = selectByPk(pk);
eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityModifyEvent<>(old, entity, getEntityType()), getEntityType()));
}
return createUpdate(entity)
//如果是RecordCreationEntity则不修改creator_id和creator_time
.when(entity instanceof RecordCreationEntity,
update -> update.and().excludes(((RecordCreationEntity) entity).getCreatorIdProperty(), RecordCreationEntity.createTime))
.where(GenericEntity.id, pk)
.exec();
}
protected int updateByPk(E entity) {
return updateByPk(entity.getId(), entity);
}
@Override
public int updateByPk(List<E> data) {
return data.stream()
.map(this::updateByPk)
.reduce(Math::addExact)
.orElse(0);
}
@Override
public PK saveOrUpdate(E entity) {
if (dataExisted(entity)) {
updateByPk(entity);
} else {
insert(entity);
}
return entity.getId();
}
@SuppressWarnings("unchecked")
protected boolean dataExisted(E entity) {
if (null != logicPrimaryKeyValidator) {
logicPrimaryKeyValidator
.validate(entity)
.ifError(result -> entity.setId(result.getData().getId()));
}
return null != entity.getId() && null != selectByPk(entity.getId());
}
@Override
public PK insert(E entity) {
if (!StringUtils.isEmpty(entity.getId())) {
if ((entity.getId() instanceof String) && !StringUtils.isEmpty(entity.getId())) {
tryValidateProperty(entity.getId().toString().matches("[a-zA-Z0-9_\\-]+"), "id", "只能由数字,字母,下划线,和-组成");
}
tryValidateProperty(selectByPk(entity.getId()) == null, "id", entity.getId() + "已存在");
}
if (StringUtils.isEmpty(entity.getId()) && getIDGenerator() != null) {
entity.setId(getIDGenerator().generate());
}
if (entity instanceof RecordCreationEntity) {
((RecordCreationEntity) entity).setCreateTimeNow();
}
tryValidate(entity, CreateGroup.class);
getDao().insert(entity);
if (eventPublisher != null && pushCreatedEvent()) {
eventPublisher.publishEvent(new GenericsPayloadApplicationEvent<>(this, new EntityCreatedEvent<>(entity, getEntityType()), getEntityType()));
}
return entity.getId();
}
@Override
@Transactional(readOnly = true)
public E selectByPk(PK pk) {
if (StringUtils.isEmpty(pk)) {
return null;
}
return createQuery().where(GenericEntity.id, pk).single();
}
@Override
@Transactional(readOnly = true)
public List<E> selectByPk(List<PK> id) {
if (CollectionUtils.isEmpty(id)) {
return new ArrayList<>();
}
return createQuery().where().in(GenericEntity.id, id).listNoPaging();
}
}

View File

@@ -1,39 +0,0 @@
/*
*
* * Copyright 2019 http://www.hswebframework.org
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package org.hswebframework.web.service;
import org.hswebframework.web.dao.CrudDao;
/**
*
* @author zhouhao
* @see DefaultDSLQueryService
* @see DefaultDSLUpdateService
* @see DefaultDSLDeleteService
* @see CrudService
* @see CrudDao
*/
public interface GenericService<E, PK> extends
DefaultDSLQueryService<E, PK>,
DefaultDSLUpdateService<E,PK>,
DefaultDSLDeleteService<E,PK>,
CrudService<E, PK> {
@Override
CrudDao<E, PK> getDao();
}

View File

@@ -1,49 +0,0 @@
package org.hswebframework.web.service;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
/**
* 动态泛型事件,用于动态发布支持泛型的事件
* <pre>
* //相当于发布事件: EntityModifyEvent&lt;UserEntity&gt;
* eventPublisher
* .publishEvent(new GenericsPayloadApplicationEvent&lt;&gt;(this, new EntityModifyEvent<>(oldEntity, newEntity), UserEntity.class));
*
* //只监听相同泛型事件
* &#064;EventListener
* public handleEvent(EntityModifyEvent&lt;UserEntity&gt; event){
*
* }
* </pre>
*
* @author zhouhao
* @since 3.0.7
*/
public class GenericsPayloadApplicationEvent<E> extends PayloadApplicationEvent<E> {
private static final long serialVersionUID = 3745888943307798710L;
//泛型列表
private transient Class[] generics;
//事件类型
private transient Class eventType;
/**
* @param source 事件源
* @param payload 事件,不能使用匿名内部类
* @param generics 泛型列表
*/
public GenericsPayloadApplicationEvent(Object source, E payload, Class... generics) {
super(source, payload);
this.generics = generics;
this.eventType = payload.getClass();
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class
, ResolvableType.forClassWithGenerics(eventType, generics));
}
}

View File

@@ -1,8 +0,0 @@
{
"groupId": "${project.groupId}",
"artifactId": "${project.artifactId}",
"path": "hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple",
"gitCommitHash": "${git.commit.hash}",
"gitRepository": "http://github.com/hs-web/hsweb-framework",
"version": "${project.version}"
}

View File

@@ -1,99 +0,0 @@
package org.hswebframework.web.service;
import com.alibaba.fastjson.JSON;
import org.hswebframework.web.commons.entity.factory.MapperEntityFactory;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.dao.CrudDao;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import javax.validation.Validation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
/**
* @author zhouhao
* @since 3.0
*/
@RunWith(MockitoJUnitRunner.class)
public class AbstractTreeSortServiceTests {
@InjectMocks
private TestTreeEntityService entityService = new TestTreeEntityService();
@Mock
private CrudDao<TestEntity, String> dao;
private List<TestEntity> entities = new ArrayList<>();
@Before
public void init() {
entityService.setEntityFactory(new MapperEntityFactory());
TestEntity entity = TestEntity.builder()
.age((byte) 10)
.enabled(true)
.name("test")
.build();
entity.setId("testId");
when(dao.query(any()))
.thenReturn(new ArrayList<>());
when(dao.count(any())).thenReturn(1);
// when(dao.update(any())).thenReturn(1);
when(dao.delete(any())).thenReturn(1);
when(dao.deleteByPk("test")).thenReturn(1);
// doNothing().when(dao).insert(anyObject());
doAnswer(invocationOnMock -> {
TestEntity t = invocationOnMock.getArgumentAt(0, TestEntity.class);
entities.add(t);
return t.getId();
}).when(dao).insert(anyObject());
doAnswer(invocationOnMock -> {
TestEntity t = invocationOnMock.getArgumentAt(0, TestEntity.class);
entities.add(t);
return t.getId();
}).when(dao).update(anyObject());
}
@Test
public void testBatchInsert() {
String treeJson = "{'id':'1','parentId':'-1','name':'父节点','children':[" +
"{'id':'101','parentId':'1','name':'子节点1'}," +
"{'id':'102','parentId':'1','name':'子节点2'}" +
"]}";
TestEntity entity = JSON.parseObject(treeJson, TestEntity.class);
entityService.insert(entity);
Assert.assertEquals(entities.size(), 3);
entities.clear();
entityService.updateByPk(entity);
Assert.assertEquals(entities.size(), 3);
entities.clear();
entityService.updateBatch(Arrays.asList(entity));
Assert.assertEquals(entities.size(), 3);
}
}

View File

@@ -1,145 +0,0 @@
package org.hswebframework.web.service;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.ezorm.core.dsl.Query;
import org.hswebframework.web.commons.entity.param.QueryParamEntity;
import org.hswebframework.web.validator.LogicPrimaryKey;
import org.hswebframework.web.validator.group.CreateGroup;
import org.junit.Assert;
import org.junit.Test;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
public class DefaultLogicPrimaryKeyValidatorTest {
DefaultLogicPrimaryKeyValidator validator = new DefaultLogicPrimaryKeyValidator();
@Test
public void testSimple() {
DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(TestBean.class, bean ->
Query.<TestBean, QueryParamEntity>empty(QueryParamEntity.empty())
.setSingleExecutor(param -> {
Assert.assertNotNull(param.getTerms());
Assert.assertEquals(param.getTerms().size(), 2);
return new TestBean("test", "1");
}));
TestBean bean = new TestBean("test", "1");
Assert.assertTrue(validator.validate(bean).isError());
}
@Test
public void testClassAnn() {
DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(ClassAnnTestBean.class, bean ->
Query.<ClassAnnTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
.setSingleExecutor(param -> {
Assert.assertNotNull(param.getTerms());
Assert.assertEquals(param.getTerms().size(), 2);
return new ClassAnnTestBean("test", "1");
}));
ClassAnnTestBean bean = new ClassAnnTestBean("test", "1");
Assert.assertTrue(validator.validate(bean).isError());
}
@Test
public void testGroupAnn() {
DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(GroupAnnTestBean.class, bean ->
Query.<GroupAnnTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
.setSingleExecutor(param -> {
Assert.assertNotNull(param.getTerms());
Assert.assertEquals(param.getTerms().size(), 2);
return new GroupAnnTestBean("test", "1");
}));
GroupAnnTestBean bean = new GroupAnnTestBean("test", "1");
Assert.assertTrue(validator.validate(bean).isPassed());
Assert.assertTrue(validator.validate(bean, TestGroup.class).isError());
}
@Test
public void testNestProperty() {
NestTestBean nestTestBean=new NestTestBean(new TestBean("test","1"),"test");
DefaultLogicPrimaryKeyValidator.registerQuerySuppiler(NestTestBean.class, bean ->
Query.<NestTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
.setSingleExecutor(param -> {
Assert.assertNotNull(param.getTerms());
Assert.assertEquals(param.getTerms().size(), 2);
return nestTestBean;
}));
Assert.assertTrue(validator.validate(nestTestBean).isError());
}
@Getter
@Setter
@AllArgsConstructor
@LogicPrimaryKey(groups = TestGroup.class)
public class GroupAnnTestBean {
private String name;
private String org;
}
@Getter
@Setter
@AllArgsConstructor
@LogicPrimaryKey({"name", "org"})
public class ClassAnnTestBean {
private String name;
private String org;
}
@Getter
@Setter
@AllArgsConstructor
public class GroupTestBean {
@LogicPrimaryKey(groups = CreateGroup.class)
private String name;
@LogicPrimaryKey
private String org;
}
@Getter
@Setter
@AllArgsConstructor
public class NestTestBean {
@LogicPrimaryKey("nest.name")
private TestBean nest;
@LogicPrimaryKey
private String org;
}
@Getter
@Setter
@AllArgsConstructor
public class TestBean {
@LogicPrimaryKey
private String name;
@LogicPrimaryKey
private String org;
}
@LogicPrimaryKey(value = {"name", "org"}, groups = TestGroup.class)
public interface TestGroup {
}
}

View File

@@ -1,29 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.dao.CrudDao;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.cache.annotation.CacheConfig;
/**
* @author zhouhao
* @since 1.0
*/
@CacheConfig(cacheNames = "test-2-entity")
public class EnableCacheAllEvictTestService extends EnableCacheAllEvictGenericEntityService<TestEntity, String> {
private CrudDao<TestEntity, String> dao;
@Override
protected IDGenerator<String> getIDGenerator() {
return IDGenerator.MD5;
}
@Override
public CrudDao<TestEntity, String> getDao() {
return dao;
}
public void setDao(CrudDao<TestEntity, String> dao) {
this.dao = dao;
}
}

View File

@@ -1,29 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.dao.CrudDao;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.cache.annotation.CacheConfig;
/**
* @author zhouhao
* @since 1.0
*/
@CacheConfig(cacheNames = "test-2-tree-entity")
public class EnableCacheAllEvictTreeTestService extends EnableCacheAllEvictTreeSortService<TestEntity, String> {
private CrudDao<TestEntity, String> dao;
@Override
protected IDGenerator<String> getIDGenerator() {
return IDGenerator.MD5;
}
@Override
public CrudDao<TestEntity, String> getDao() {
return dao;
}
public void setDao(CrudDao<TestEntity, String> dao) {
this.dao = dao;
}
}

View File

@@ -1,28 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.dao.CrudDao;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.cache.annotation.CacheConfig;
/**
* @author zhouhao
* @since 1.0
*/
@CacheConfig(cacheNames = "test-entity")
public class EnableCacheTestService extends EnableCacheGenericEntityService<TestEntity, String> {
private CrudDao<TestEntity, String> dao;
@Override
protected IDGenerator<String> getIDGenerator() {
return IDGenerator.MD5;
}
@Override
public CrudDao<TestEntity, String> getDao() {
return dao;
}
public void setDao(CrudDao<TestEntity, String> dao) {
this.dao = dao;
}
}

View File

@@ -1,150 +0,0 @@
package org.hswebframework.web.service;
import org.hswebframework.web.commons.entity.factory.MapperEntityFactory;
import org.hswebframework.web.dao.CrudDao;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.theories.suppliers.TestedOn;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
import javax.validation.Validation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author zhouhao
* @since 3.0
*/
@SpringBootTest(classes = SpringTestApplication.class)
@RunWith(value = SpringRunner.class)
public class EnableCacheTests {
@Autowired
private EnableCacheTestService enableCacheTestService;
@Autowired
private EnableCacheAllEvictTestService enableCacheAllEvictTestService;
@Autowired
private EnableCacheTreeTestService enableCacheTreeTestService;
@Autowired
private EnableCacheAllEvictTreeTestService enableCacheAllEvictTreeTestService;
private AtomicInteger counter = new AtomicInteger();
@Before
public void init() {
CrudDao<TestEntity, String> dao = Mockito.mock(CrudDao.class);
enableCacheTestService.setEntityFactory(new MapperEntityFactory());
TestEntity entity = TestEntity.builder()
.age((byte) 10)
.enabled(true)
.name("test")
.build();
entity.setId("testId");
when(dao.query(any()))
.then((Answer<List<TestEntity>>) invocationOnMock -> {
//模拟命中数据库
counter.incrementAndGet();
return new ArrayList<>(Arrays.asList(entity));
});
when(dao.count(any())).then((Answer<Integer>) invocationOnMock -> {
//模拟命中数据库
counter.incrementAndGet();
return 1;
});
when(dao.update(any())).thenReturn(1);
when(dao.delete(any())).thenReturn(1);
when(dao.deleteByPk(anyString())).thenReturn(1);
doNothing().when(dao).insert(anyObject());
enableCacheTestService.setDao(dao);
enableCacheAllEvictTestService.setDao(dao);
enableCacheTreeTestService.setDao(dao);
enableCacheAllEvictTreeTestService.setDao(dao);
}
@Test
public void testSimpleCacheEnableService() {
doTest(enableCacheTestService);
}
@Test
public void testEnableCacheAllEvictTestService() {
doTest(enableCacheAllEvictTestService);
}
@Test
public void testEnableCacheTreeTestService() {
doTest(enableCacheTreeTestService);
}
@Test
public void testEnableCacheAllEvictTreeTestService() {
doTest(enableCacheAllEvictTreeTestService);
}
public void doTest(CrudService<TestEntity, String> service) {
service.selectByPk("testId"); //db 1
service.selectByPk("testId");//cache
Assert.assertEquals(counter.get(), 1);
service.select(); //db 2
service.select();//cache
Assert.assertEquals(counter.get(), 2);
service.count(); //db 3
service.count(); //cache
Assert.assertEquals(counter.get(), 3);
service.updateByPk("testId", new TestEntity()); //evict cache
service.select(); //db 4
service.selectByPk("testId");//db 5
service.count();//db 6
service.select(); //cache
service.selectByPk("testId");//cache
service.count();//cache
Assert.assertEquals(counter.get(), 6);
service.deleteByPk("testId"); //evict cache
//删除前会查询
counter.decrementAndGet();
service.select(); //db 7
service.selectByPk("testId");//db 8
service.count();//db 9
service.select(); //cache
service.selectByPk("testId");//cache
service.count();//cache
Assert.assertEquals(counter.get(), 9);
}
}

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