初始化项目,集成简单的东西
This commit is contained in:
parent
866aec160f
commit
0cbf0bbe24
83
.gitignore
vendored
83
.gitignore
vendored
@ -1,23 +1,70 @@
|
||||
# Compiled class file
|
||||
*.class
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
**/*.iml
|
||||
**/.idea/**
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
# Generated files
|
||||
.idea
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
target
|
||||
*.iml
|
||||
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# FlyFish快速开发框架
|
||||
经过长期积累后实现的快速开发框架,可用于任何业务快速开发上线
|
||||
兼容各种场景和复杂的封装
|
||||
|
||||
# 框架兼容性
|
||||
基于JDK1.8 +
|
||||
基于MongoDB(可动态切换Mysql)
|
||||
基于模块化CRUD
|
64
flyfish-common/pom.xml
Normal file
64
flyfish-common/pom.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?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>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-common</artifactId>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<!--commons工具类-->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,35 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/8/23.
|
||||
*/
|
||||
public class BaseResponse {
|
||||
private int status = 200;
|
||||
private String message;
|
||||
|
||||
public BaseResponse(int status, String message) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BaseResponse() {
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
/**
|
||||
* ${DESCRIPTION}
|
||||
*
|
||||
* @author wanghaobin
|
||||
* @create 2017-06-09 7:32
|
||||
*/
|
||||
public class ListRestResponse<T> {
|
||||
String msg;
|
||||
T result;
|
||||
int count;
|
||||
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(T result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public ListRestResponse count(int count) {
|
||||
this.setCount(count);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListRestResponse count(Long count) {
|
||||
this.setCount(count.intValue());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListRestResponse msg(String msg) {
|
||||
this.setMsg(msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListRestResponse result(T result) {
|
||||
this.setResult(result);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
/**
|
||||
* Created by Wangyu on 2017/6/11.
|
||||
*/
|
||||
public class ObjectRestResponse<T> extends BaseResponse {
|
||||
|
||||
T data;
|
||||
boolean rel;
|
||||
|
||||
public boolean isRel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
public void setRel(boolean rel) {
|
||||
this.rel = rel;
|
||||
}
|
||||
|
||||
|
||||
public ObjectRestResponse rel(boolean rel) {
|
||||
this.setRel(rel);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public ObjectRestResponse data(T data) {
|
||||
this.setData(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
|
||||
/**
|
||||
* 分页实体的bean
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
public class PageBean<T> {
|
||||
|
||||
private Integer page;
|
||||
|
||||
private Long totalCount;
|
||||
|
||||
private Integer totalPages;
|
||||
|
||||
private Integer size;
|
||||
|
||||
public PageBean(Page<T> page) {
|
||||
// 页大小
|
||||
this.size = page.getSize();
|
||||
// 页码
|
||||
this.page = page.getNumber() + 1;
|
||||
this.totalCount = page.getTotalElements();
|
||||
this.totalPages = page.getTotalPages();
|
||||
}
|
||||
|
||||
public Integer getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(Integer page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Long getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public void setTotalCount(Long totalCount) {
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
|
||||
public Integer getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public void setTotalPages(Integer totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
}
|
||||
|
||||
public Integer getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(Integer size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用来描述分页数据的bean
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public class PageResult<T> extends Result<List<T>> {
|
||||
|
||||
public static final Pageable DEFAULT_PAGE = PageRequest.of(0, 20);
|
||||
|
||||
private PageBean page;
|
||||
|
||||
private Integer count;
|
||||
|
||||
public PageResult(String errCode, String errMsg, Page<T> data) {
|
||||
super(errCode, errMsg, data.getContent());
|
||||
this.page = new PageBean<>(data);
|
||||
this.count = (int) data.getTotalElements();
|
||||
}
|
||||
|
||||
public PageResult(String errCode, String errMsg) {
|
||||
super(errCode, errMsg);
|
||||
}
|
||||
|
||||
public PageBean getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(PageBean page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public Integer getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Integer count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 请求结果
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Result<T> {
|
||||
|
||||
public static final String SUCCESS = "0";
|
||||
|
||||
public static final String FAIL = "-1";
|
||||
|
||||
public static final String MSG_SUCCESS = "请求成功";
|
||||
|
||||
public static final String MSG_FAIL = "系统异常!";
|
||||
|
||||
public static final String MSG_NO_DATA = "未查询到任何数据!";
|
||||
|
||||
private T data;
|
||||
|
||||
private String code;
|
||||
|
||||
private String msg;
|
||||
|
||||
public Result(String code, String msg, T data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Result(String code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = (T) Collections.emptyMap();
|
||||
}
|
||||
|
||||
public static <T> Result<T> accept(T data) {
|
||||
if (null == data) {
|
||||
return new Result<>(FAIL, MSG_NO_DATA);
|
||||
}
|
||||
return new Result<>(SUCCESS, MSG_SUCCESS, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个元素,附带分页信息,分页数据写死
|
||||
*
|
||||
* @param data 数据
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T> Result<List<T>> single(T data) {
|
||||
Page<T> page = new PageImpl<>(Collections.singletonList(data), PageResult.DEFAULT_PAGE, 1);
|
||||
return accept(page);
|
||||
}
|
||||
|
||||
public static <T> Result<List<T>> accept(List<T> data, Pageable pageable, int total) {
|
||||
Page<T> page = new PageImpl<>(data, pageable, total);
|
||||
return accept(page);
|
||||
}
|
||||
|
||||
public static <T> Result<List<T>> accept(Page<T> page) {
|
||||
if (null == page) {
|
||||
return new PageResult<>(FAIL, MSG_NO_DATA);
|
||||
}
|
||||
return new PageResult<>(SUCCESS, MSG_SUCCESS, page);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error() {
|
||||
return new Result<>(FAIL, MSG_FAIL, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok() {
|
||||
return empty();
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(T data) {
|
||||
return new Result<>(SUCCESS, MSG_SUCCESS, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> empty() {
|
||||
return new Result<>(SUCCESS, MSG_SUCCESS, null);
|
||||
}
|
||||
|
||||
public static <T> Result<List<T>> emptyList() {
|
||||
return new Result<>(SUCCESS, MSG_SUCCESS, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static <T> Result<List<T>> emptyPage() {
|
||||
return new PageResult<>(SUCCESS, MSG_SUCCESS, Page.empty());
|
||||
}
|
||||
|
||||
public static <T> Result<T> notFound() {
|
||||
return new Result<>(FAIL, MSG_NO_DATA, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String errMsg) {
|
||||
return new Result<>(FAIL, errMsg, null);
|
||||
}
|
||||
|
||||
public Result<Void> toVoid() {
|
||||
return (Result<Void>) this;
|
||||
}
|
||||
|
||||
public Optional<T> optional() {
|
||||
return isSuccess() ? Optional.ofNullable(data) : Optional.empty();
|
||||
}
|
||||
|
||||
public <R> Result<R> map(Function<T, R> function) {
|
||||
if (this.data != null) {
|
||||
this.data = (T) function.apply(this.data);
|
||||
return (Result<R>) this;
|
||||
}
|
||||
return (Result<R>) this;
|
||||
}
|
||||
|
||||
public Result<T> orElse(Result<T> other) {
|
||||
if (null == this.data) {
|
||||
return other;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result<T> filter(Function<T, Boolean> function) {
|
||||
if (!function.apply(this.data)) {
|
||||
this.data = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isSuccess() {
|
||||
return SUCCESS.equals(this.code);
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ${DESCRIPTION}
|
||||
*
|
||||
* @author wanghaobin
|
||||
* @create 2017-06-14 22:40
|
||||
*/
|
||||
public class TableResultResponse<T> extends BaseResponse {
|
||||
|
||||
TableData<T> data;
|
||||
|
||||
public TableResultResponse(long total, List<T> rows) {
|
||||
this.data = new TableData<T>(total, rows);
|
||||
}
|
||||
|
||||
public TableResultResponse() {
|
||||
this.data = new TableData<T>();
|
||||
}
|
||||
|
||||
TableResultResponse<T> total(int total) {
|
||||
this.data.setTotal(total);
|
||||
return this;
|
||||
}
|
||||
|
||||
TableResultResponse<T> total(List<T> rows) {
|
||||
this.data.setRows(rows);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TableData<T> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(TableData<T> data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
class TableData<T> {
|
||||
long total;
|
||||
List<T> rows;
|
||||
|
||||
public TableData(long total, List<T> rows) {
|
||||
this.total = total;
|
||||
this.rows = rows;
|
||||
}
|
||||
|
||||
public TableData() {
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public List<T> getRows() {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public void setRows(List<T> rows) {
|
||||
this.rows = rows;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Wangyu on 2017/6/12.
|
||||
*/
|
||||
public class TreeNode {
|
||||
protected int id;
|
||||
protected int parentId;
|
||||
List<TreeNode> children = new ArrayList<TreeNode>();
|
||||
|
||||
public List<TreeNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
public void setChildren(List<TreeNode> children) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public int getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
public void setParentId(int parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
public void add(TreeNode node) {
|
||||
children.add(node);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.flyfish.framework.bean.auth;
|
||||
|
||||
import com.flyfish.framework.bean.BaseResponse;
|
||||
import com.flyfish.framework.constant.RestCodeConstants;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/8/23.
|
||||
*/
|
||||
public class TokenErrorResponse extends BaseResponse {
|
||||
public TokenErrorResponse(String message) {
|
||||
super(RestCodeConstants.TOKEN_ERROR_CODE, message);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.flyfish.framework.bean.auth;
|
||||
|
||||
import com.flyfish.framework.bean.BaseResponse;
|
||||
import com.flyfish.framework.constant.RestCodeConstants;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/8/25.
|
||||
*/
|
||||
public class TokenForbiddenResponse extends BaseResponse {
|
||||
public TokenForbiddenResponse(String message) {
|
||||
super(RestCodeConstants.TOKEN_FORBIDDEN_CODE, message);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.flyfish.framework.constant;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/8/29.
|
||||
*/
|
||||
public class CommonConstants {
|
||||
public final static String RESOURCE_TYPE_MENU = "menu";
|
||||
public final static String RESOURCE_TYPE_BTN = "button";
|
||||
// 用户token异常
|
||||
public static final Integer EX_USER_INVALID_CODE = 40101;
|
||||
public static final Integer EX_USER_PASS_INVALID_CODE = 40001;
|
||||
// 客户端token异常
|
||||
public static final Integer EX_CLIENT_INVALID_CODE = 40301;
|
||||
public static final Integer EX_CLIENT_FORBIDDEN_CODE = 40331;
|
||||
public static final Integer EX_OTHER_CODE = 500;
|
||||
public static final String CONTEXT_KEY_USER_ID = "currentUserId";
|
||||
public static final String CONTEXT_KEY_USERNAME = "currentUserName";
|
||||
public static final String CONTEXT_KEY_USER_NAME = "currentUser";
|
||||
public static final String CONTEXT_KEY_USER_TOKEN = "currentUserToken";
|
||||
public static final String JWT_KEY_USER_ID = "userId";
|
||||
public static final String JWT_KEY_NAME = "name";
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.constant;
|
||||
|
||||
/**
|
||||
* 异步接口常量
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface ReactiveConstants {
|
||||
|
||||
String USE_REACTIVE = "Reactive=1";
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.flyfish.framework.constant;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/8/23.
|
||||
*/
|
||||
public class RestCodeConstants {
|
||||
|
||||
public static final int TOKEN_ERROR_CODE = 40101;
|
||||
public static final int TOKEN_FORBIDDEN_CODE = 40301;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.constant;
|
||||
|
||||
/**
|
||||
* ${DESCRIPTION}
|
||||
*
|
||||
* @author wanghaobin
|
||||
* @create 2017-06-14 8:36
|
||||
*/
|
||||
public class UserConstant {
|
||||
public static int PW_ENCORDER_SALT = 12;
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.flyfish.framework.context;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于ThreadLocal的时间仓库,提高线程性能
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Component
|
||||
public class DateContext {
|
||||
|
||||
private static DateContext instance;
|
||||
private ThreadLocal<DateFormat> formatThreadLocal = new ThreadLocal<>();
|
||||
private ThreadLocal<Map<String, DateFormat>> mapThreadLocal = new ThreadLocal<>();
|
||||
|
||||
public DateContext() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static DateContext getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static DateFormat simpleFormat() {
|
||||
DateFormat format = instance.formatThreadLocal.get();
|
||||
if (format == null) {
|
||||
format = new SimpleDateFormat("yyyy-MM-dd");
|
||||
instance.formatThreadLocal.set(format);
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置的解析,避免使用
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 结果
|
||||
*/
|
||||
public static Date parse(Object date) {
|
||||
if (date instanceof String) {
|
||||
try {
|
||||
return simpleFormat().parse((String) date);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置解析
|
||||
*
|
||||
* @param date 日期
|
||||
* @param pattern 格式
|
||||
* @return 结果
|
||||
*/
|
||||
public static Date parse(Object date, String pattern) {
|
||||
if (date instanceof String) {
|
||||
try {
|
||||
return dateFormat(pattern).parse((String) date);
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DateFormat dateFormat(String pattern) {
|
||||
return instance.getDateFormat(pattern);
|
||||
}
|
||||
|
||||
public static String formatDate(Date date, String pattern) {
|
||||
return dateFormat(pattern).format(date);
|
||||
}
|
||||
|
||||
public DateFormat getDateFormat(String pattern) {
|
||||
Map<String, DateFormat> formatMap = mapThreadLocal.get();
|
||||
if (null == formatMap) {
|
||||
formatMap = new HashMap<>();
|
||||
mapThreadLocal.set(formatMap);
|
||||
}
|
||||
DateFormat format = formatMap.compute(pattern, (key, value) ->
|
||||
value == null ? new SimpleDateFormat(key) : value);
|
||||
return format;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.flyfish.framework.enums;
|
||||
|
||||
/**
|
||||
* 基于标识码的枚举基类
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public interface CodeBasedEnum {
|
||||
|
||||
/**
|
||||
* 获取枚举对象标识码
|
||||
*
|
||||
* @return 枚举标识
|
||||
*/
|
||||
int getCode();
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.flyfish.framework.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleType {
|
||||
|
||||
PC("PC端"), MOBILE("移动端");
|
||||
|
||||
private String name;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.flyfish.framework.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*
|
||||
* @author wangyu
|
||||
* 根据用户状态标识用户
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum UserStatus {
|
||||
|
||||
NORMAL("正常状态"), LOCKED("已锁定"), DISABLED("已禁用"), EXPIRED("已过期");
|
||||
|
||||
private String name;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.flyfish.framework.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 用户类型枚举
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public enum UserType {
|
||||
|
||||
SUPER_ADMIN("超级管理员"), ADMIN("管理员"), VIP("会员"), USER("非会员");
|
||||
|
||||
private static final Map<String, UserType> aliasMap;
|
||||
|
||||
static {
|
||||
aliasMap = new HashMap<>();
|
||||
aliasMap.put("vip", VIP);
|
||||
aliasMap.put("user", USER);
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
||||
public static UserType getByAlias(String alias) {
|
||||
return aliasMap.getOrDefault(alias, USER);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.flyfish.framework.exception;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/8.
|
||||
*/
|
||||
public class BaseException extends RuntimeException {
|
||||
|
||||
private int status = 200;
|
||||
|
||||
public BaseException() {
|
||||
}
|
||||
|
||||
public BaseException(String message, int status) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public BaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BaseException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BaseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.flyfish.framework.exception.auth;
|
||||
|
||||
|
||||
import com.flyfish.framework.constant.CommonConstants;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/12.
|
||||
*/
|
||||
public class ClientForbiddenException extends BaseException {
|
||||
public ClientForbiddenException(String message) {
|
||||
super(message, CommonConstants.EX_CLIENT_FORBIDDEN_CODE);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.exception.auth;
|
||||
|
||||
|
||||
import com.flyfish.framework.constant.CommonConstants;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/10.
|
||||
*/
|
||||
public class ClientInvalidException extends BaseException {
|
||||
public ClientInvalidException(String message) {
|
||||
super(message, CommonConstants.EX_CLIENT_INVALID_CODE);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.exception.auth;
|
||||
|
||||
|
||||
import com.flyfish.framework.constant.CommonConstants;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/10.
|
||||
*/
|
||||
public class ClientTokenException extends BaseException {
|
||||
public ClientTokenException(String message) {
|
||||
super(message, CommonConstants.EX_CLIENT_INVALID_CODE);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.exception.auth;
|
||||
|
||||
|
||||
import com.flyfish.framework.constant.CommonConstants;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/8.
|
||||
*/
|
||||
public class UserInvalidException extends BaseException {
|
||||
public UserInvalidException(String message) {
|
||||
super(message, CommonConstants.EX_USER_PASS_INVALID_CODE);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.exception.auth;
|
||||
|
||||
|
||||
import com.flyfish.framework.constant.CommonConstants;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/8.
|
||||
*/
|
||||
public class UserTokenException extends BaseException {
|
||||
public UserTokenException(String message) {
|
||||
super(message, CommonConstants.EX_USER_INVALID_CODE);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package com.flyfish.framework.exception.biz;
|
||||
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 数据缺失异常,会抛出404
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class DataLackException extends BaseException {
|
||||
|
||||
private static final long serialVersionUID = -2048295927616443440L;
|
||||
|
||||
private int status = 404;
|
||||
|
||||
public DataLackException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public static DataLackException msg(String message) {
|
||||
return new DataLackException(message);
|
||||
}
|
||||
|
||||
public static Supplier<DataLackException> supplier(String message) {
|
||||
return () -> msg(message);
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package com.flyfish.framework.exception.biz;
|
||||
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
|
||||
/**
|
||||
* 业务限制异常
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class InvalidBusinessException extends BaseException {
|
||||
|
||||
private int status = 400;
|
||||
|
||||
public InvalidBusinessException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
@ -0,0 +1,430 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import com.flyfish.framework.exception.biz.InvalidBusinessException;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.join;
|
||||
|
||||
/**
|
||||
* 断言工具
|
||||
*
|
||||
* @author Mr.Wang
|
||||
* 提供断言,快速抛出业务异常,降低代码重复
|
||||
*/
|
||||
public abstract class Assert {
|
||||
|
||||
/**
|
||||
* Assert a boolean expression, throwing {@code QQWBusinessException}
|
||||
* if the test result is {@code false}.
|
||||
* <pre class="code">Assert.isTrue(i > 0, "The value must be greater than zero");</pre>
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if expression is {@code false}
|
||||
*/
|
||||
public static void isTrue(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert a boolean expression, throwing {@code QQWBusinessException}
|
||||
* if the test result is {@code false}.
|
||||
* <pre class="code">Assert.isTrue(i > 0);</pre>
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @throws InvalidBusinessException if expression is {@code false}
|
||||
*/
|
||||
public static void isTrue(boolean expression) {
|
||||
isTrue(expression, "[Assertion failed] - this expression must be true");
|
||||
}
|
||||
|
||||
public static void isFalse(boolean expression) {
|
||||
isTrue(!expression);
|
||||
}
|
||||
|
||||
public static void isFalse(boolean expression, String message) {
|
||||
isTrue(!expression, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an object is {@code null} .
|
||||
* <pre class="code">Assert.isNull(value, "The value must be null");</pre>
|
||||
*
|
||||
* @param object the object to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the object is not {@code null}
|
||||
*/
|
||||
public static void isNull(Object object, String message) {
|
||||
if (object != null) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an object is {@code null} .
|
||||
* <pre class="code">Assert.isNull(value);</pre>
|
||||
*
|
||||
* @param object the object to check
|
||||
* @throws InvalidBusinessException if the object is not {@code null}
|
||||
*/
|
||||
public static void isNull(Object object) {
|
||||
isNull(object, "[Assertion failed] - the object argument must be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an object is not {@code null} .
|
||||
* <pre class="code">Assert.notNull(clazz, "The class must not be null");</pre>
|
||||
*
|
||||
* @param object the object to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the object is {@code null}
|
||||
*/
|
||||
public static void notNull(Object object, String message) {
|
||||
if (object == null) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an object is not {@code null} .
|
||||
* <pre class="code">Assert.notNull(clazz);</pre>
|
||||
*
|
||||
* @param object the object to check
|
||||
* @throws InvalidBusinessException if the object is {@code null}
|
||||
*/
|
||||
public static void notNull(Object object) {
|
||||
notNull(object, "[Assertion failed] - this argument is required; it must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String is not empty; that is,
|
||||
* it must not be {@code null} and not the empty String.
|
||||
* <pre class="code">Assert.hasLength(name, "Name must not be empty");</pre>
|
||||
*
|
||||
* @param text the String to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the text is empty
|
||||
* @see StringUtils#hasLength
|
||||
*/
|
||||
public static void hasLength(String text, String message) {
|
||||
if (!StringUtils.hasLength(text)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String is not empty; that is,
|
||||
* it must not be {@code null} and not the empty String.
|
||||
* <pre class="code">Assert.hasLength(name);</pre>
|
||||
*
|
||||
* @param text the String to check
|
||||
* @throws InvalidBusinessException if the text is empty
|
||||
* @see StringUtils#hasLength
|
||||
*/
|
||||
public static void hasLength(String text) {
|
||||
hasLength(text,
|
||||
"[Assertion failed] - this String argument must have length; it must not be null or empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String has valid text content; that is, it must not
|
||||
* be {@code null} and must contain at least one non-whitespace character.
|
||||
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
|
||||
*
|
||||
* @param text the String to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the text does not contain valid text content
|
||||
* @see StringUtils#hasText
|
||||
*/
|
||||
public static void hasText(String text, String message) {
|
||||
if (!StringUtils.hasText(text)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given String has valid text content; that is, it must not
|
||||
* be {@code null} and must contain at least one non-whitespace character.
|
||||
* <pre class="code">Assert.hasText(name, "'name' must not be empty");</pre>
|
||||
*
|
||||
* @param text the String to check
|
||||
* @throws InvalidBusinessException if the text does not contain valid text content
|
||||
* @see StringUtils#hasText
|
||||
*/
|
||||
public static void hasText(String text) {
|
||||
hasText(text,
|
||||
"[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not contain the given substring.
|
||||
* <pre class="code">Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");</pre>
|
||||
*
|
||||
* @param textToSearch the text to search
|
||||
* @param substring the substring to find within the text
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the text contains the substring
|
||||
*/
|
||||
public static void doesNotContain(String textToSearch, String substring, String message) {
|
||||
if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) &&
|
||||
textToSearch.contains(substring)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not contain the given substring.
|
||||
* <pre class="code">Assert.doesNotContain(name, "rod");</pre>
|
||||
*
|
||||
* @param textToSearch the text to search
|
||||
* @param substring the substring to find within the text
|
||||
* @throws InvalidBusinessException if the text contains the substring
|
||||
*/
|
||||
public static void doesNotContain(String textToSearch, String substring) {
|
||||
doesNotContain(textToSearch, substring,
|
||||
"[Assertion failed] - this String argument must not contain the substring [" + substring + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an array has elements; that is, it must not be
|
||||
* {@code null} and must have at least one element.
|
||||
* <pre class="code">Assert.notEmpty(array, "The array must have elements");</pre>
|
||||
*
|
||||
* @param array the array to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the object array is {@code null} or has no elements
|
||||
*/
|
||||
public static void notEmpty(Object[] array, String message) {
|
||||
if (ObjectUtils.isEmpty(array)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an array has elements; that is, it must not be
|
||||
* {@code null} and must have at least one element.
|
||||
* <pre class="code">Assert.notEmpty(array);</pre>
|
||||
*
|
||||
* @param array the array to check
|
||||
* @throws InvalidBusinessException if the object array is {@code null} or has no elements
|
||||
*/
|
||||
public static void notEmpty(Object[] array) {
|
||||
notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an array has no null elements.
|
||||
* Note: Does not complain if the array is empty!
|
||||
* <pre class="code">Assert.noNullElements(array, "The array must have non-null elements");</pre>
|
||||
*
|
||||
* @param array the array to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the object array contains a {@code null} element
|
||||
*/
|
||||
public static void noNullElements(Object[] array, String message) {
|
||||
if (array != null) {
|
||||
for (Object element : array) {
|
||||
if (element == null) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an array has no null elements.
|
||||
* Note: Does not complain if the array is empty!
|
||||
* <pre class="code">Assert.noNullElements(array);</pre>
|
||||
*
|
||||
* @param array the array to check
|
||||
* @throws InvalidBusinessException if the object array contains a {@code null} element
|
||||
*/
|
||||
public static void noNullElements(Object[] array) {
|
||||
noNullElements(array, "[Assertion failed] - this array must not contain any null elements");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a collection has elements; that is, it must not be
|
||||
* {@code null} and must have at least one element.
|
||||
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
|
||||
*
|
||||
* @param collection the collection to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the collection is {@code null} or has no elements
|
||||
*/
|
||||
public static void notEmpty(Collection<?> collection, String message) {
|
||||
if (CollectionUtils.isEmpty(collection)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a collection has elements; that is, it must not be
|
||||
* {@code null} and must have at least one element.
|
||||
* <pre class="code">Assert.notEmpty(collection, "Collection must have elements");</pre>
|
||||
*
|
||||
* @param collection the collection to check
|
||||
* @throws InvalidBusinessException if the collection is {@code null} or has no elements
|
||||
*/
|
||||
public static void notEmpty(Collection<?> collection) {
|
||||
notEmpty(collection,
|
||||
"[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a Map has entries; that is, it must not be {@code null}
|
||||
* and must have at least one entry.
|
||||
* <pre class="code">Assert.notEmpty(map, "Map must have entries");</pre>
|
||||
*
|
||||
* @param map the map to check
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if the map is {@code null} or has no entries
|
||||
*/
|
||||
public static void notEmpty(Map<?, ?> map, String message) {
|
||||
if (CollectionUtils.isEmpty(map)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a Map has entries; that is, it must not be {@code null}
|
||||
* and must have at least one entry.
|
||||
* <pre class="code">Assert.notEmpty(map);</pre>
|
||||
*
|
||||
* @param map the map to check
|
||||
* @throws InvalidBusinessException if the map is {@code null} or has no entries
|
||||
*/
|
||||
public static void notEmpty(Map<?, ?> map) {
|
||||
notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the provided object is an instance of the provided class.
|
||||
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
|
||||
*
|
||||
* @param clazz the required class
|
||||
* @param obj the object to check
|
||||
* @throws InvalidBusinessException if the object is not an instance of clazz
|
||||
* @see Class#isInstance
|
||||
*/
|
||||
public static void isInstanceOf(Class<?> clazz, Object obj) {
|
||||
isInstanceOf(clazz, obj, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the provided object is an instance of the provided class.
|
||||
* <pre class="code">Assert.instanceOf(Foo.class, foo);</pre>
|
||||
*
|
||||
* @param type the type to check against
|
||||
* @param obj the object to check
|
||||
* @param message a message which will be prepended to the message produced by
|
||||
* the function itself, and which may be used to provide context. It should
|
||||
* normally end in ":" or "." so that the generated message looks OK when
|
||||
* appended to it.
|
||||
* @throws InvalidBusinessException if the object is not an instance of clazz
|
||||
* @see Class#isInstance
|
||||
*/
|
||||
public static void isInstanceOf(Class<?> type, Object obj, String message) {
|
||||
notNull(type, "Type to check against must not be null");
|
||||
if (!type.isInstance(obj)) {
|
||||
throw new InvalidBusinessException(
|
||||
(StringUtils.hasLength(message) ? message + " " : "") +
|
||||
"Object of class [" + (obj != null ? obj.getClass().getName() : "null") +
|
||||
"] must be an instance of " + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
|
||||
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
|
||||
*
|
||||
* @param superType the super type to check
|
||||
* @param subType the sub type to check
|
||||
* @throws InvalidBusinessException if the classes are not assignable
|
||||
*/
|
||||
public static void isAssignable(Class<?> superType, Class<?> subType) {
|
||||
isAssignable(superType, subType, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that {@code superType.isAssignableFrom(subType)} is {@code true}.
|
||||
* <pre class="code">Assert.isAssignable(Number.class, myClass);</pre>
|
||||
*
|
||||
* @param superType the super type to check against
|
||||
* @param subType the sub type to check
|
||||
* @param message a message which will be prepended to the message produced by
|
||||
* the function itself, and which may be used to provide context. It should
|
||||
* normally end in ":" or "." so that the generated message looks OK when
|
||||
* appended to it.
|
||||
* @throws InvalidBusinessException if the classes are not assignable
|
||||
*/
|
||||
public static void isAssignable(Class<?> superType, Class<?> subType, String message) {
|
||||
notNull(superType, "Type to check against must not be null");
|
||||
if (subType == null || !superType.isAssignableFrom(subType)) {
|
||||
throw new InvalidBusinessException((StringUtils.hasLength(message) ? message + " " : "") +
|
||||
subType + " is not assignable to " + superType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert a boolean expression, throwing {@code QQWBusinessException}
|
||||
* if the test result is {@code false}. Call isTrue if you wish to
|
||||
* throw QQWBusinessException on an assertion failure.
|
||||
* <pre class="code">Assert.state(id == null, "The id property must not already be initialized");</pre>
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @param message the exception message to use if the assertion fails
|
||||
* @throws InvalidBusinessException if expression is {@code false}
|
||||
*/
|
||||
public static void state(boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert a boolean expression, throwing {@link InvalidBusinessException}
|
||||
* if the test result is {@code false}.
|
||||
* <p>Call {@link #isTrue(boolean)} if you wish to
|
||||
* throw {@link InvalidBusinessException} on an assertion failure.
|
||||
* <pre class="code">Assert.state(id == null);</pre>
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @throws InvalidBusinessException if the supplied expression is {@code false}
|
||||
*/
|
||||
public static void state(boolean expression) {
|
||||
state(expression, "[Assertion failed] - this state invariant must be true");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件类型
|
||||
*
|
||||
* @param filename 文件
|
||||
* @param filePatterns 文件类型
|
||||
*/
|
||||
public static void isFilePatterns(String filename, String[] filePatterns) {
|
||||
if (Stream.of(filePatterns).noneMatch(filename::endsWith)) {
|
||||
throw new InvalidBusinessException("文件格式不正确,必须是" + join(filePatterns, ",") + "中的一种!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件类型
|
||||
*
|
||||
* @param filename 文件
|
||||
* @param filePatterns 文件类型
|
||||
*/
|
||||
public static void isFilePatterns(String filename, String[] filePatterns, String message) {
|
||||
if (Stream.of(filePatterns).noneMatch(filename::endsWith)) {
|
||||
throw new InvalidBusinessException(message);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
/**
|
||||
* 实体类相关工具类
|
||||
* 解决问题: 1、快速对实体的常驻字段,如:crtUser、crtHost、updUser等值快速注入
|
||||
*
|
||||
* @author Wangyu
|
||||
* @version 1.0
|
||||
* @date 2016年4月18日
|
||||
* @since 1.7
|
||||
*/
|
||||
public class EntityUtils {
|
||||
|
||||
/**
|
||||
* 依据对象的属性数组和值数组对对象的属性进行赋值
|
||||
*
|
||||
* @param entity 对象
|
||||
* @param fields 属性数组
|
||||
* @param value 值数组
|
||||
* @author 王浩彬
|
||||
*/
|
||||
private static <T> void setDefaultValues(T entity, String[] fields, Object[] value) {
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
String field = fields[i];
|
||||
if (ReflectionUtils.hasField(entity, field)) {
|
||||
ReflectionUtils.invokeSetter(entity, field, value[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键属性,判断主键是否值为空
|
||||
*
|
||||
* @param entity
|
||||
* @param field
|
||||
* @return 主键为空,则返回false;主键有值,返回true
|
||||
* @author 王浩彬
|
||||
* @date 2016年4月28日
|
||||
*/
|
||||
public static <T> boolean isPKNotNull(T entity, String field) {
|
||||
if (!ReflectionUtils.hasField(entity, field)) {
|
||||
return false;
|
||||
}
|
||||
Object value = ReflectionUtils.getFieldValue(entity, field);
|
||||
return value != null && !"".equals(value);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
|
||||
import com.flyfish.framework.enums.CodeBasedEnum;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* 枚举工具类
|
||||
*
|
||||
* @author Liu.xT
|
||||
*/
|
||||
public class EnumUtils {
|
||||
/**
|
||||
* 根据枚举标识码获取枚举对象
|
||||
*
|
||||
* @param code 枚举标识
|
||||
* @param enumClass 枚举Class类型
|
||||
* @param <E> 枚举泛型
|
||||
* @return 枚举对象
|
||||
* @throws NoSuchElementException 找不到指定元素
|
||||
*/
|
||||
public static <E extends Enum<?> & CodeBasedEnum> E getByCode(int code, Class<E> enumClass) throws NoSuchElementException {
|
||||
E[] enumConstants = enumClass.getEnumConstants();
|
||||
for (E e : enumConstants) {
|
||||
if (e.getCode() == code) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
throw new NoSuchElementException("找不到标识码[" + code + "]对应的枚举对象: [" + enumClass.getSimpleName() + "]");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Jackson序列化工具类
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public final class JacksonUtil {
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
// =========================================================================
|
||||
// SerializationFeature for changing how JSON is written
|
||||
|
||||
// to enable standard indentation ("pretty-printing"):
|
||||
// mapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
// to allow serialization of "empty" POJOs (no properties to serialize)
|
||||
// (without this setting, an exception is thrown in those cases)
|
||||
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
|
||||
// to write java.util.Date, Calendar as number (timestamp):
|
||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
// DeserializationFeature for changing how JSON is read as POJOs:
|
||||
|
||||
// to prevent exception when encountering unknown property:
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
// to allow coercion of JSON empty String ("") to null Object value:
|
||||
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
|
||||
|
||||
// =========================================================================
|
||||
// JsonParser.Feature for configuring parsing settings:
|
||||
|
||||
// to allow C/C++ style comments in JSON (non-standard, disabled by default)
|
||||
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
// (note: with Jackson 2.5, there is also `mapper.enable(feature)` / `mapper.disable(feature)`)
|
||||
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
|
||||
// to allow (non-standard) unquoted field names in JSON:
|
||||
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
|
||||
// to allow use of apostrophes (single quotes), non standard
|
||||
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
|
||||
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
// JsonGenerator.Feature for configuring low-level JSON generation:
|
||||
|
||||
// to force escaping of non-ASCII characters:
|
||||
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json) {
|
||||
return readValue(json, new TypeReference<T>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json, TypeReference<T> reference) {
|
||||
return readValue(json, reference);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(final String json, Class<T> clazz) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, clazz);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// public static Map json2Map(final String json) {
|
||||
// return fromJson(json, Map.class);
|
||||
// }
|
||||
|
||||
public static <K, V> Map<K, V> json2Map(String json) {
|
||||
return readValue(json, new TypeReference<Map<K, V>>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> List<T> json2List(final String json) {
|
||||
return readValue(json, new TypeReference<List<T>>() {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> List<T> json2List(final String json, Class<T> clazz) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static Optional<String> toJson(final Object obj) {
|
||||
try {
|
||||
return Optional.of(mapper.writeValueAsString(obj));
|
||||
} catch (JsonProcessingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static <T> T readValue(String json, TypeReference<T> valueTypeRef) {
|
||||
if (null == json || "".equals(json)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(json, valueTypeRef);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,338 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
|
||||
/**
|
||||
* 反射工具类.
|
||||
* 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数.
|
||||
*
|
||||
* @author calvin
|
||||
* @version 2013-01-15
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Slf4j
|
||||
public class ReflectionUtils {
|
||||
|
||||
private static final String SETTER_PREFIX = "set";
|
||||
|
||||
private static final String GETTER_PREFIX = "get";
|
||||
|
||||
private static final String CGLIB_CLASS_SEPARATOR = "$$";
|
||||
|
||||
/**
|
||||
* 调用Getter方法.
|
||||
* 支持多级,如:对象名.对象名.方法
|
||||
*/
|
||||
public static Object invokeGetter(Object obj, String propertyName) {
|
||||
Object object = obj;
|
||||
for (String name : StringUtils.split(propertyName, ".")) {
|
||||
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
|
||||
object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{});
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用Setter方法, 仅匹配方法名。
|
||||
* 支持多级,如:对象名.对象名.方法
|
||||
*/
|
||||
public static void invokeSetter(Object obj, String propertyName, Object value) {
|
||||
Object object = obj;
|
||||
String[] names = StringUtils.split(propertyName, ".");
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
if (i < names.length - 1) {
|
||||
String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
|
||||
object = invokeMethod(object, getterMethodName, new Class[]{}, new Object[]{});
|
||||
} else {
|
||||
String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
|
||||
invokeMethodByName(object, setterMethodName, new Object[]{value});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
|
||||
*/
|
||||
public static Object getFieldValue(final Object obj, final String fieldName) {
|
||||
Field field = getAccessibleField(obj, fieldName);
|
||||
|
||||
if (field == null) {
|
||||
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
try {
|
||||
result = field.get(obj);
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("不可能抛出的异常{}", e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
|
||||
*/
|
||||
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
|
||||
Field field = getAccessibleField(obj, fieldName);
|
||||
|
||||
if (field == null) {
|
||||
log.error("Could not find field [" + fieldName + "] on target [" + obj + "]");
|
||||
return;
|
||||
//throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
|
||||
}
|
||||
try {
|
||||
field.set(obj, convert(value, field.getType()));
|
||||
} catch (IllegalAccessException e) {
|
||||
log.error("不可能抛出的异常:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static Object convert(Object object, Class<?> type) {
|
||||
if (object instanceof Number) {
|
||||
Number number = (Number) object;
|
||||
if (type.equals(byte.class) || type.equals(Byte.class)) {
|
||||
return number.byteValue();
|
||||
}
|
||||
if (type.equals(short.class) || type.equals(Short.class)) {
|
||||
return number.shortValue();
|
||||
}
|
||||
if (type.equals(int.class) || type.equals(Integer.class)) {
|
||||
return number.intValue();
|
||||
}
|
||||
if (type.equals(long.class) || type.equals(Long.class)) {
|
||||
return number.longValue();
|
||||
}
|
||||
if (type.equals(float.class) || type.equals(Float.class)) {
|
||||
return number.floatValue();
|
||||
}
|
||||
if (type.equals(double.class) || type.equals(Double.class)) {
|
||||
return number.doubleValue();
|
||||
}
|
||||
}
|
||||
if (type.equals(String.class)) {
|
||||
return object == null ? "" : object.toString();
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接调用对象方法, 无视private/protected修饰符.
|
||||
* 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
|
||||
* 同时匹配方法名+参数类型,
|
||||
*/
|
||||
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes,
|
||||
final Object[] args) {
|
||||
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
|
||||
}
|
||||
|
||||
try {
|
||||
return method.invoke(obj, args);
|
||||
} catch (Exception e) {
|
||||
throw convertReflectionExceptionToUnchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接调用对象方法, 无视private/protected修饰符,
|
||||
* 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
|
||||
* 只匹配函数名,如果有多个同名函数调用第一个。
|
||||
*/
|
||||
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args) {
|
||||
Method method = getAccessibleMethodByName(obj, methodName);
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
|
||||
}
|
||||
|
||||
try {
|
||||
return method.invoke(obj, args);
|
||||
} catch (Exception e) {
|
||||
throw convertReflectionExceptionToUnchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
|
||||
* <p>
|
||||
* 如向上转型到Object仍无法找到, 返回null.
|
||||
*/
|
||||
public static Field getAccessibleField(final Object obj, final String fieldName) {
|
||||
Validate.notNull(obj, "object can't be null");
|
||||
Validate.notBlank(fieldName, "fieldName can't be blank");
|
||||
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
|
||||
try {
|
||||
Field field = superClass.getDeclaredField(fieldName);
|
||||
makeAccessible(field);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {//NOSONAR
|
||||
// Field不在当前类定义,继续向上转型
|
||||
continue;// new add
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
|
||||
* 如向上转型到Object仍无法找到, 返回null.
|
||||
* 匹配函数名+参数类型。
|
||||
* <p>
|
||||
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
|
||||
*/
|
||||
public static Method getAccessibleMethod(final Object obj, final String methodName,
|
||||
final Class<?>... parameterTypes) {
|
||||
Validate.notNull(obj, "object can't be null");
|
||||
Validate.notBlank(methodName, "methodName can't be blank");
|
||||
|
||||
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
|
||||
try {
|
||||
Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
|
||||
makeAccessible(method);
|
||||
return method;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Method不在当前类定义,继续向上转型
|
||||
continue;// new add
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
|
||||
* 如向上转型到Object仍无法找到, 返回null.
|
||||
* 只匹配函数名。
|
||||
* <p>
|
||||
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
|
||||
*/
|
||||
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
|
||||
Validate.notNull(obj, "object can't be null");
|
||||
Validate.notBlank(methodName, "methodName can't be blank");
|
||||
|
||||
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
|
||||
Method[] methods = searchType.getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getName().equals(methodName)) {
|
||||
makeAccessible(method);
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
|
||||
*/
|
||||
public static void makeAccessible(Method method) {
|
||||
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
|
||||
&& !method.isAccessible()) {
|
||||
method.setAccessible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
|
||||
*/
|
||||
public static void makeAccessible(Field field) {
|
||||
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
|
||||
.isFinal(field.getModifiers())) && !field.isAccessible()) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处
|
||||
* 如无法找到, 返回Object.class.
|
||||
* eg.
|
||||
* public UserDao extends HibernateDao<User>
|
||||
*
|
||||
* @param clazz The class to introspect
|
||||
* @return the first generic declaration, or Object.class if cannot be determined
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Class<T> getClassGenricType(final Class clazz) {
|
||||
return getClassGenricType(clazz, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
|
||||
* 如无法找到, 返回Object.class.
|
||||
* <p>
|
||||
* 如public UserDao extends HibernateDao<User,Long>
|
||||
*
|
||||
* @param clazz clazz The class to introspect
|
||||
* @param index the Index of the generic ddeclaration,start from 0.
|
||||
* @return the index generic declaration, or Object.class if cannot be determined
|
||||
*/
|
||||
public static Class getClassGenricType(final Class clazz, final int index) {
|
||||
|
||||
Type genType = clazz.getGenericSuperclass();
|
||||
|
||||
if (!(genType instanceof ParameterizedType)) {
|
||||
log.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
|
||||
|
||||
if (index >= params.length || index < 0) {
|
||||
log.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
|
||||
+ params.length);
|
||||
return Object.class;
|
||||
}
|
||||
if (!(params[index] instanceof Class)) {
|
||||
log.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
return (Class) params[index];
|
||||
}
|
||||
|
||||
public static Class<?> getUserClass(Object instance) {
|
||||
Assert.notNull(instance, "Instance must not be null");
|
||||
Class clazz = instance.getClass();
|
||||
if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass != null && !Object.class.equals(superClass)) {
|
||||
return superClass;
|
||||
}
|
||||
}
|
||||
return clazz;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 将反射时的checked exception转换为unchecked exception.
|
||||
*/
|
||||
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
|
||||
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|
||||
|| e instanceof NoSuchMethodException) {
|
||||
return new IllegalArgumentException(e);
|
||||
} else if (e instanceof InvocationTargetException) {
|
||||
return new RuntimeException(((InvocationTargetException) e).getTargetException());
|
||||
} else if (e instanceof RuntimeException) {
|
||||
return (RuntimeException) e;
|
||||
}
|
||||
return new RuntimeException("Unexpected Checked Exception.", e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某个对象是否拥有某个属性
|
||||
*
|
||||
* @param obj 对象
|
||||
* @param fieldName 属性名
|
||||
* @return 有属性返回true
|
||||
* 无属性返回false
|
||||
*/
|
||||
public static boolean hasField(final Object obj, final String fieldName) {
|
||||
Field field = getAccessibleField(obj, fieldName);
|
||||
return field != null;
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/10.
|
||||
*/
|
||||
public class StringHelper {
|
||||
public static String getObjectValue(Object obj) {
|
||||
return obj == null ? "" : obj.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
|
||||
import com.flyfish.framework.bean.TreeNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Wangyu on 2017/6/12.
|
||||
*/
|
||||
public class TreeUtil {
|
||||
/**
|
||||
* 两层循环实现建树
|
||||
*
|
||||
* @param treeNodes 传入的树节点列表
|
||||
* @return
|
||||
*/
|
||||
public static <T extends TreeNode> List<T> bulid(List<T> treeNodes, Object root) {
|
||||
|
||||
List<T> trees = new ArrayList<T>();
|
||||
|
||||
for (T treeNode : treeNodes) {
|
||||
|
||||
if (root.equals(treeNode.getParentId())) {
|
||||
trees.add(treeNode);
|
||||
}
|
||||
|
||||
for (T it : treeNodes) {
|
||||
if (it.getParentId() == treeNode.getId()) {
|
||||
if (treeNode.getChildren() == null) {
|
||||
treeNode.setChildren(new ArrayList<TreeNode>());
|
||||
}
|
||||
treeNode.add(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用递归方法建树
|
||||
*
|
||||
* @param treeNodes
|
||||
* @return
|
||||
*/
|
||||
public static <T extends TreeNode> List<T> buildByRecursive(List<T> treeNodes, Object root) {
|
||||
List<T> trees = new ArrayList<T>();
|
||||
for (T treeNode : treeNodes) {
|
||||
if (root.equals(treeNode.getParentId())) {
|
||||
trees.add(findChildren(treeNode, treeNodes));
|
||||
}
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归查找子节点
|
||||
*
|
||||
* @param treeNodes
|
||||
* @return
|
||||
*/
|
||||
public static <T extends TreeNode> T findChildren(T treeNode, List<T> treeNodes) {
|
||||
for (T it : treeNodes) {
|
||||
if (treeNode.getId() == it.getParentId()) {
|
||||
if (treeNode.getChildren() == null) {
|
||||
treeNode.setChildren(new ArrayList<TreeNode>());
|
||||
}
|
||||
treeNode.add(findChildren(it, treeNodes));
|
||||
}
|
||||
}
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/27.
|
||||
*/
|
||||
public class UUIDUtils {
|
||||
public static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
|
||||
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
|
||||
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
|
||||
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
|
||||
"W", "X", "Y", "Z"};
|
||||
|
||||
|
||||
public static String generateShortUuid() {
|
||||
StringBuffer shortBuffer = new StringBuffer();
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
String str = uuid.substring(i * 4, i * 4 + 4);
|
||||
int x = Integer.parseInt(str, 16);
|
||||
shortBuffer.append(chars[x % 0x3E]);
|
||||
}
|
||||
return shortBuffer.toString();
|
||||
|
||||
}
|
||||
}
|
32
flyfish-data/pom.xml
Normal file
32
flyfish-data/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?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>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-mongodb</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,20 @@
|
||||
package com.flyfish.framework.auditor;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
|
||||
/**
|
||||
* 负责补全或者生成默认值(仅针对新建)
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface BeanAuditor<T extends Domain> {
|
||||
|
||||
/**
|
||||
* 对实体进行审查,并补全相关字段
|
||||
*
|
||||
* @param data 原数据
|
||||
* @return 结果
|
||||
*/
|
||||
void audit(T data);
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.flyfish.framework.auditor;
|
||||
|
||||
import com.flyfish.framework.context.UserContext;
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OperationAuditor implements BeanAuditor<AuditDomain> {
|
||||
|
||||
private final UserContext userContext;
|
||||
|
||||
/**
|
||||
* 对实体进行审查,并补全相关字段
|
||||
*
|
||||
* @param data 原数据
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public void audit(AuditDomain data) {
|
||||
User currentUser = userContext.currentUser();
|
||||
if (null != currentUser) {
|
||||
if (StringUtils.isNotBlank(data.getId())) {
|
||||
data.setModifierId(currentUser.getId());
|
||||
data.setModifier(currentUser.getModifier());
|
||||
} else {
|
||||
data.setCreatorId(currentUser.getId());
|
||||
data.setCreator(currentUser.getName());
|
||||
data.setModifierId(currentUser.getModifierId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.flyfish.framework.auditor;
|
||||
|
||||
import com.flyfish.framework.context.UserContext;
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户上下文审查工具
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class UserAuditor implements AuditorAware<String> {
|
||||
|
||||
private final UserContext userContext;
|
||||
|
||||
/**
|
||||
* 在异步上下文中获取结果
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Optional<String> getCurrentAuditor() {
|
||||
return Optional.ofNullable(userContext.currentUser())
|
||||
.map(Domain::getName);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
package com.flyfish.framework.builder;
|
||||
|
||||
import com.flyfish.framework.context.DateContext;
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 查询构建器
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public final class CriteriaBuilder<T extends Domain> {
|
||||
|
||||
private Qo<T> qo;
|
||||
|
||||
private Map<String, BiFunction<Criteria, Object, Criteria>> functionMap = new HashMap<>();
|
||||
|
||||
private Map<String, String> keyMapper = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 构造器,接受一个qo
|
||||
*
|
||||
* @param qo 查询实体
|
||||
* @param <T> 泛型,实体
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T extends Domain> CriteriaBuilder<T> accept(Qo<T> qo) {
|
||||
CriteriaBuilder<T> builder = new CriteriaBuilder<>();
|
||||
builder.qo = qo;
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加策略
|
||||
*
|
||||
* @param key 键
|
||||
* @param function 方法
|
||||
* @param field 域的键值
|
||||
* @return 结果
|
||||
*/
|
||||
public CriteriaBuilder<T> with(String key, String field, BiFunction<Criteria, Object, Criteria> function) {
|
||||
this.functionMap.putIfAbsent(key, function);
|
||||
keyMapper.putIfAbsent(key, field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加策略
|
||||
*
|
||||
* @param key 键
|
||||
* @param function 方法
|
||||
* @return 结果
|
||||
*/
|
||||
public CriteriaBuilder<T> with(String key, BiFunction<Criteria, Object, Criteria> function) {
|
||||
this.functionMap.putIfAbsent(key, function);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加策略
|
||||
*
|
||||
* @param key 键
|
||||
* @return 结果
|
||||
*/
|
||||
public CriteriaBuilder<T> with(String... key) {
|
||||
Arrays.stream(key).forEach(item -> this.functionMap.putIfAbsent(item, Criteria::is));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量导入规则
|
||||
*
|
||||
* @param criteriaDescriptors 规则集合
|
||||
* @return 结果
|
||||
*/
|
||||
public CriteriaBuilder<T> with(List<CriteriaDescriptor> criteriaDescriptors) {
|
||||
criteriaDescriptors.forEach(criteriaDescriptor ->
|
||||
this.with(criteriaDescriptor.getKey(), criteriaDescriptor.getMapper()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Criteria build() {
|
||||
if (!CollectionUtils.isEmpty(functionMap)) {
|
||||
// 键集合
|
||||
Set<String> keySet = functionMap.keySet();
|
||||
// 建立查询器
|
||||
Criteria[] criteria = Arrays.stream(BeanUtils.getPropertyDescriptors(qo.getClass()))
|
||||
.filter(propertyDescriptor -> keySet.contains(propertyDescriptor.getName()))
|
||||
.map(propertyDescriptor -> {
|
||||
try {
|
||||
String key = propertyDescriptor.getName();
|
||||
// 值
|
||||
Object value = propertyDescriptor.getReadMethod().invoke(qo);
|
||||
// 值非空予以过滤
|
||||
if (!ObjectUtils.isEmpty(value)) {
|
||||
String field = keyMapper.getOrDefault(key, key);
|
||||
return functionMap.getOrDefault(propertyDescriptor.getName(), Criteria::is).apply(
|
||||
Criteria.where(field), fixValue(field, value));
|
||||
}
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(Criteria[]::new);
|
||||
return combine(criteria);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修正查询最终值,主要是ObjectId
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object fixValue(String field, Object value) {
|
||||
if (field.contains(".$id")) {
|
||||
if (value instanceof Collection) {
|
||||
Collection<Object> collection = (Collection<Object>) value;
|
||||
return collection.stream()
|
||||
.map(item -> (item instanceof ObjectId ? item : createObjectId((String) item)))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return createObjectId((String) value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Object createObjectId(String value) {
|
||||
try {
|
||||
return new ObjectId(value);
|
||||
} catch (Exception e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 整合最后的结果
|
||||
*
|
||||
* @param criteria 结果
|
||||
* @return 返回
|
||||
*/
|
||||
private Criteria combine(Criteria[] criteria) {
|
||||
if (criteria.length == 0) {
|
||||
return new Criteria();
|
||||
}
|
||||
if (criteria.length == 1) {
|
||||
return criteria[0];
|
||||
}
|
||||
return new Criteria().andOperator(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配策略
|
||||
*/
|
||||
public interface Builders {
|
||||
|
||||
// 模糊匹配
|
||||
BiFunction<Criteria, Object, Criteria> LIKE = (criteria, o) -> criteria.regex((String) o);
|
||||
|
||||
// 精确匹配
|
||||
BiFunction<Criteria, Object, Criteria> IS = Criteria::is;
|
||||
|
||||
// 不等
|
||||
BiFunction<Criteria, Object, Criteria> NE = Criteria::ne;
|
||||
|
||||
// 包含匹配
|
||||
BiFunction<Criteria, Object, Criteria> IN = (criteria, o) -> o instanceof Collection ? criteria.in(((Collection) o)) : criteria.in(o);
|
||||
|
||||
// 不包含匹配
|
||||
BiFunction<Criteria, Object, Criteria> NIN = (criteria, o) -> o instanceof Collection ? criteria.nin(((Collection) o)) : criteria.nin(o);
|
||||
|
||||
// 不为空
|
||||
BiFunction<Criteria, Object, Criteria> NOT_NULL = ((criteria, o) -> criteria.ne(null));
|
||||
|
||||
// 范围匹配
|
||||
BiFunction<Criteria, Object, Criteria> RANGE = (criteria, o) -> {
|
||||
if (o instanceof List) {
|
||||
List list = (List) o;
|
||||
return criteria.gte(list.get(0)).lte(list.get(1));
|
||||
}
|
||||
return criteria;
|
||||
};
|
||||
|
||||
// 时间起始
|
||||
BiFunction<Criteria, Object, Criteria> DATE_GTE = (criteria, o) -> criteria.gte(DateContext.parse(o));
|
||||
|
||||
// 时间中止
|
||||
BiFunction<Criteria, Object, Criteria> DATE_LTE = (criteria, o) -> criteria.lte(DateContext.parse(o));
|
||||
|
||||
// 日期范围包含
|
||||
@SuppressWarnings("unchecked")
|
||||
BiFunction<Criteria, Object, Criteria> DATE_RANGE = (criteria, o) -> {
|
||||
if (o instanceof List) {
|
||||
List<String> list = (List<String>) o;
|
||||
if (list.size() == 2) {
|
||||
criteria.gte(DateContext.parse(list.get(0)))
|
||||
.lte(DateContext.parse(list.get(1)));
|
||||
}
|
||||
} else if (o instanceof String[]) {
|
||||
String[] array = (String[]) o;
|
||||
if (array.length == 2) {
|
||||
criteria.gte(DateContext.parse(array[0]))
|
||||
.lte(DateContext.parse(array[1]));
|
||||
}
|
||||
}
|
||||
return criteria;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询描述器
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
protected static class CriteriaDescriptor {
|
||||
|
||||
private String key;
|
||||
|
||||
private BiFunction<Criteria, Object, Criteria> mapper;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.flyfish.framework.context;
|
||||
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户上下文,用于创建时使用的上下文
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Component
|
||||
public final class UserContext {
|
||||
|
||||
private static UserContext instance;
|
||||
private ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
|
||||
|
||||
public UserContext() {
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static Optional<UserContext> sharedContext() {
|
||||
return Optional.ofNullable(instance);
|
||||
}
|
||||
|
||||
public User currentUser() {
|
||||
return userThreadLocal.get();
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
userThreadLocal.set(user);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
userThreadLocal.remove();
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.CreatedBy;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedBy;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 需要审查的domain(修改和更新指定人)
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Document
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class AuditDomain extends Domain {
|
||||
|
||||
/**
|
||||
* 创建日期
|
||||
*/
|
||||
@CreatedDate
|
||||
protected Date createTime;
|
||||
|
||||
/**
|
||||
* 修改日期
|
||||
*/
|
||||
@LastModifiedDate
|
||||
protected Date modifyTime;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@CreatedBy
|
||||
protected String creator;
|
||||
|
||||
/**
|
||||
* 创建人id
|
||||
*/
|
||||
protected String creatorId;
|
||||
|
||||
/**
|
||||
* 修改者
|
||||
*/
|
||||
@LastModifiedBy
|
||||
protected String modifier;
|
||||
|
||||
/**
|
||||
* 修改人id
|
||||
*/
|
||||
protected String modifierId;
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.flyfish.framework.builder.CriteriaBuilder;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基本的查询实体
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public class BaseQo<T extends Domain> implements Qo<T> {
|
||||
|
||||
protected Pageable pageable;
|
||||
|
||||
protected List<T> result;
|
||||
|
||||
protected User user;
|
||||
|
||||
public Qo<T> accept(List<T> result, Pageable pageable) {
|
||||
this.pageable = pageable;
|
||||
this.result = result;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Qo<T> accept(Pageable pageable) {
|
||||
this.pageable = pageable;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pageable getPageable() {
|
||||
return pageable;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public void setPageable(Pageable pageable) {
|
||||
this.pageable = pageable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前用户
|
||||
*
|
||||
* @param user 用户数据
|
||||
*/
|
||||
@Override
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置结果集,用于单例部署时快速封装
|
||||
*
|
||||
* @param result 查询结果集
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public void setResult(List<T> result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义查询规则的方法
|
||||
*
|
||||
* @return 包含匹配规则的example
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Example<T> getExample() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取data-mongo的对象Criteria
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Criteria getCriteria() {
|
||||
CriteriaBuilder<T> criteriaBuilder = criteriaBuilder();
|
||||
if (null != criteriaBuilder) {
|
||||
return criteriaBuilder.build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内置的builder,重写后自动合并结果
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public CriteriaBuilder<T> criteriaBuilder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 让值全部包含在Pojo里
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> pojoType() {
|
||||
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
|
||||
return (Class<T>) type.getActualTypeArguments()[0];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Sort sorts() {
|
||||
return Sort.by(Sort.Order.desc("createTime"));
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Document
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class Domain implements Po, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8288256526019424379L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@Id
|
||||
protected String id;
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
protected String code;
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
@Indexed
|
||||
protected String name;
|
||||
/**
|
||||
* 上下文冗余,利用内存缓存上下文
|
||||
*/
|
||||
@Transient
|
||||
private User currentUser;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (id != null) {
|
||||
return id.hashCode();
|
||||
}
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof Domain) {
|
||||
Domain comparing = (Domain) obj;
|
||||
if (this.getId() == null || comparing.getId() == null) {
|
||||
return false;
|
||||
}
|
||||
return this.getId().equals(comparing.getId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
/**
|
||||
* 基础的DTO
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
public interface Dto<T> {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.flyfish.framework.domain.po.Department;
|
||||
import com.flyfish.framework.domain.po.Role;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.enums.UserStatus;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public interface IUser {
|
||||
|
||||
String getId();
|
||||
|
||||
void setId(String id);
|
||||
|
||||
String getCode();
|
||||
|
||||
void setCode(String code);
|
||||
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
UserType getUserType();
|
||||
|
||||
void setUserType(UserType userType);
|
||||
|
||||
UserStatus getUserStatus();
|
||||
|
||||
void setUserStatus(UserStatus userStatus);
|
||||
|
||||
String getPhone();
|
||||
|
||||
void setPhone(String phone);
|
||||
|
||||
String getUsername();
|
||||
|
||||
void setUsername(String username);
|
||||
|
||||
String getPassword();
|
||||
|
||||
void setPassword(String password);
|
||||
|
||||
Boolean getEnable();
|
||||
|
||||
void setEnable(Boolean enable);
|
||||
|
||||
Boolean getApp();
|
||||
|
||||
void setApp(Boolean app);
|
||||
|
||||
Date getValidDate();
|
||||
|
||||
void setValidDate(Date validDate);
|
||||
|
||||
List<Department> getDepartments();
|
||||
|
||||
void setDepartments(List<Department> departments);
|
||||
|
||||
List<Role> getRoles();
|
||||
|
||||
void setRoles(List<Role> roles);
|
||||
|
||||
String getOpenId();
|
||||
|
||||
void setOpenId(String openId);
|
||||
|
||||
Object getDetail();
|
||||
|
||||
void setDetail(Object detail);
|
||||
|
||||
User toUser();
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
/**
|
||||
* 持久层模型
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public interface Po {
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 查询模型
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
public interface Qo<T> {
|
||||
|
||||
/**
|
||||
* 获取分页对象
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
Pageable getPageable();
|
||||
|
||||
/**
|
||||
* 设置分页对象
|
||||
*
|
||||
* @param pageable 分页对象
|
||||
*/
|
||||
@JsonIgnore
|
||||
void setPageable(Pageable pageable);
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
User getUser();
|
||||
|
||||
/**
|
||||
* 当前用户
|
||||
*/
|
||||
void setUser(User user);
|
||||
|
||||
/**
|
||||
* 获取结果集,用于单例部署时快速封装
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> getResult();
|
||||
|
||||
/**
|
||||
* 设置结果集,用于单例部署时快速封装
|
||||
*
|
||||
* @param result 查询结果集
|
||||
*/
|
||||
@JsonIgnore
|
||||
void setResult(List<T> result);
|
||||
|
||||
/**
|
||||
* 获取jpa的example对象
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@JsonIgnore
|
||||
Example<T> getExample();
|
||||
|
||||
/**
|
||||
* 获取data-mongo的对象Predicate
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@JsonIgnore
|
||||
Criteria getCriteria();
|
||||
|
||||
/**
|
||||
* 让值全部包含在Pojo里
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
Class<T> pojoType();
|
||||
|
||||
/**
|
||||
* 排序字段,默认createTime
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
Sort sorts();
|
||||
|
||||
/**
|
||||
* 判断查询是否为空
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
default boolean isEmpty() {
|
||||
Criteria criteria = getCriteria();
|
||||
Example<T> example = getExample();
|
||||
return example == null && (null == criteria.getCriteriaObject() || criteria.getCriteriaObject().size() == 0);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public abstract class TreeDomain<T extends TreeDomain<T>> extends AuditDomain {
|
||||
|
||||
// 父id,顶级是0
|
||||
private String parentId;
|
||||
|
||||
// 深度,遍历标识
|
||||
private Integer depth;
|
||||
|
||||
// 冗余的字段,用来放儿子
|
||||
@Transient
|
||||
private List<T> children;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.flyfish.framework.domain.base;
|
||||
|
||||
/**
|
||||
* 基础的视图模型
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
public interface Vo<T> {
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.flyfish.framework.domain.po;
|
||||
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import lombok.*;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* 部门
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Document
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class Department extends AuditDomain {
|
||||
|
||||
/**
|
||||
* 默认选中
|
||||
*/
|
||||
private Boolean primary;
|
||||
|
||||
/**
|
||||
* 负责人
|
||||
*/
|
||||
private String manager;
|
||||
|
||||
/**
|
||||
* 联系电话
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 层级
|
||||
*/
|
||||
private Integer level;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 父节点,根节点0
|
||||
*/
|
||||
private String parentId;
|
||||
|
||||
/**
|
||||
* 是否是部门(叶子节点)
|
||||
*/
|
||||
private Boolean leaf;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.flyfish.framework.domain.po;
|
||||
|
||||
import com.flyfish.framework.domain.base.TreeDomain;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* 权限
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Document
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Permission extends TreeDomain<Permission> {
|
||||
|
||||
public static final Permission ROOT;
|
||||
|
||||
static {
|
||||
ROOT = new Permission();
|
||||
ROOT.setId("0");
|
||||
ROOT.setDepth(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是管理员权限
|
||||
*/
|
||||
private boolean admin;
|
||||
|
||||
/**
|
||||
* 是否是叶子节点
|
||||
*/
|
||||
private Boolean leaf;
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.flyfish.framework.domain.po;
|
||||
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import com.flyfish.framework.enums.RoleType;
|
||||
import lombok.*;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色(权限)
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Document
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class Role extends AuditDomain {
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否管理员
|
||||
*/
|
||||
private Boolean admin;
|
||||
|
||||
/**
|
||||
* 是否系统内置
|
||||
*/
|
||||
private boolean system;
|
||||
|
||||
/**
|
||||
* 所属平台
|
||||
*/
|
||||
private RoleType type;
|
||||
|
||||
/**
|
||||
* 角色拥有的权限
|
||||
*/
|
||||
@DBRef
|
||||
private List<Permission> permissions;
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.flyfish.framework.domain.po;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.flyfish.framework.domain.base.AuditDomain;
|
||||
import com.flyfish.framework.domain.base.IUser;
|
||||
import com.flyfish.framework.enums.UserStatus;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.DBRef;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Document
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class User extends AuditDomain implements IUser {
|
||||
|
||||
private static final long serialVersionUID = -960011918745179950L;
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private UserType userType;
|
||||
|
||||
/**
|
||||
* 用户状态
|
||||
*/
|
||||
private UserStatus userStatus;
|
||||
|
||||
/**
|
||||
* 冗余的电话号码
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@Indexed(unique = true)
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean enable;
|
||||
|
||||
/**
|
||||
* 能否登录移动端
|
||||
*/
|
||||
private Boolean app;
|
||||
|
||||
/**
|
||||
* 有效期
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
private Date validDate;
|
||||
|
||||
/**
|
||||
* 可操作校区
|
||||
*/
|
||||
@DBRef
|
||||
private List<Department> departments;
|
||||
|
||||
/**
|
||||
* 所属角色
|
||||
*/
|
||||
@DBRef
|
||||
private List<Role> roles;
|
||||
|
||||
/**
|
||||
* 微信openId
|
||||
*/
|
||||
@Indexed(unique = true)
|
||||
private String openId;
|
||||
|
||||
/**
|
||||
* 查询冗余,标记用户信息
|
||||
*/
|
||||
@Transient
|
||||
private Object detail;
|
||||
|
||||
@Override
|
||||
public User toUser() {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
|
||||
|
||||
/**
|
||||
* 默认的持久层dao
|
||||
*
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface DefaultReactiveRepository<T> extends ReactiveMongoRepository<T, String> {
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
|
||||
/**
|
||||
* 默认的仓库
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface DefaultRepository<T extends Domain> extends MongoRepository<T, String>, QueryModelExecutor<T> {
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.flyfish.framework.repository;
|
||||
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 查询模型支持
|
||||
*
|
||||
* @author wangyu
|
||||
* 基于repo的公共扩展
|
||||
*/
|
||||
public interface QueryModelExecutor<T> {
|
||||
|
||||
/**
|
||||
* 通过名称查找一个
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<T> findByName(String name);
|
||||
|
||||
/**
|
||||
* Returns a single entity matching the given {@link Qo} or {@link Optional#empty()} if none was found.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a single entity matching the given {@link Qo} or {@link Optional#empty()} if none was found.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if the Qo yields more than one
|
||||
* result.
|
||||
*/
|
||||
Optional<T> findOne(Qo<T> query);
|
||||
|
||||
/**
|
||||
* Returns all entities matching the given {@link Qo}. In case no match could be found an empty
|
||||
* {@link Iterable} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return all entities matching the given {@link Qo}.
|
||||
*/
|
||||
Iterable<T> findAll(Qo<T> query);
|
||||
|
||||
/**
|
||||
* Returns all entities matching the given {@link Qo} applying the given {@link Sort}. In case no match could
|
||||
* be found an empty {@link Iterable} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param sort the {@link Sort} specification to sort the results by, may be {@link Sort#empty()}, must not be
|
||||
* {@literal null}.
|
||||
* @return all entities matching the given {@link Qo}.
|
||||
* @since 1.10
|
||||
*/
|
||||
Iterable<T> findAll(Qo<T> query, Sort sort);
|
||||
|
||||
/**
|
||||
* Returns a {@link Page} of entities matching the given {@link Qo}. In case no match could be found, an empty
|
||||
* {@link Page} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param pageable may be {@link Pageable#unpaged()}, must not be {@literal null}.
|
||||
* @return a {@link Page} of entities matching the given {@link Qo}.
|
||||
*/
|
||||
Page<T> findAll(Qo<T> query, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Returns the number of instances matching the given {@link Qo}.
|
||||
*
|
||||
* @param query the {@link Qo} to count instances for, must not be {@literal null}.
|
||||
* @return the number of instances matching the {@link Qo}.
|
||||
*/
|
||||
long count(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 通过特定键的集合查询
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> findAllByValues(String key, List<Object> values);
|
||||
|
||||
/**
|
||||
* Checks whether the data store contains elements that match the given {@link Qo}.
|
||||
*
|
||||
* @param query the {@link Qo} to use for the existence check, must not be {@literal null}.
|
||||
* @return {@literal true} if the data store contains elements that match the given {@link Qo}.
|
||||
*/
|
||||
boolean exists(Qo<T> query);
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.flyfish.framework.repository.impl;
|
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
|
||||
public class DefaultRepositoryFactory extends MongoRepositoryFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoRepositoryFactory} with the given {@link MongoOperations}.
|
||||
*
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
*/
|
||||
public DefaultRepositoryFactory(MongoOperations mongoOperations) {
|
||||
super(mongoOperations);
|
||||
this.setRepositoryBaseClass(DefaultRepositoryFactory.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
|
||||
return DefaultRepositoryFactory.class;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.flyfish.framework.repository.impl;
|
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
|
||||
public class DefaultRepositoryFactoryBean<T extends Repository<S, java.lang.String>, S>
|
||||
extends MongoRepositoryFactoryBean<T, S, String> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link MongoRepositoryFactoryBean} for the given repository interface.
|
||||
*
|
||||
* @param repositoryInterface must not be {@literal null}.
|
||||
*/
|
||||
public DefaultRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
|
||||
super(repositoryInterface);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
|
||||
return new DefaultRepositoryFactory(operations);
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
package com.flyfish.framework.repository.impl;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.repository.DefaultRepository;
|
||||
import com.flyfish.framework.utils.CopyUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.MongoOperations;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
|
||||
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
|
||||
import org.springframework.data.repository.support.PageableExecutionUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 查询模型支持
|
||||
*
|
||||
* @author wangyu
|
||||
* 基于repo的公共扩展
|
||||
*/
|
||||
public class DefaultRepositoryImpl<T extends Domain> extends SimpleMongoRepository<T, String>
|
||||
implements DefaultRepository<T> {
|
||||
|
||||
private MongoOperations mongoOperations;
|
||||
private MongoEntityInformation<T, String> entityInformation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SimpleMongoRepository} for the given {@link MongoEntityInformation} and {@link MongoTemplate}.
|
||||
*
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param mongoOperations must not be {@literal null}.
|
||||
*/
|
||||
public DefaultRepositoryImpl(MongoEntityInformation<T, String> metadata, MongoOperations mongoOperations) {
|
||||
super(metadata, mongoOperations);
|
||||
this.mongoOperations = mongoOperations;
|
||||
this.entityInformation = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过名称查找一个
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> findByName(String name) {
|
||||
if (StringUtils.isNotBlank(name)) {
|
||||
return Optional.ofNullable(mongoOperations.findOne(Query.query(Criteria.where("name").is(name)),
|
||||
entityInformation.getJavaType(), entityInformation.getCollectionName()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single entity matching the given {@link Qo} or {@link Optional#empty()} if none was found.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return a single entity matching the given {@link Qo} or {@link Optional#empty()} if none was found.
|
||||
* @throws IncorrectResultSizeDataAccessException if the Qo yields more than one
|
||||
* result.
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> findOne(Qo<T> query) {
|
||||
Query querying = getQuery(query);
|
||||
if (null != querying) {
|
||||
return Optional
|
||||
.ofNullable(mongoOperations.findOne(querying,
|
||||
entityInformation.getJavaType(),
|
||||
entityInformation.getCollectionName()));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entities matching the given {@link Qo}. In case no match could be found an empty
|
||||
* {@link Iterable} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @return all entities matching the given {@link Qo}.
|
||||
*/
|
||||
@Override
|
||||
public Iterable<T> findAll(Qo<T> query) {
|
||||
Query querying = getQuery(query);
|
||||
if (null != querying) {
|
||||
return mongoOperations.find(querying,
|
||||
entityInformation.getJavaType(),
|
||||
entityInformation.getCollectionName());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all entities matching the given {@link Qo} applying the given {@link Sort}. In case no match could
|
||||
* be found an empty {@link Iterable} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param sort the {@link Sort} specification to sort the results by, may be {@link Sort#empty()}, must not be
|
||||
* {@literal null}.
|
||||
* @return all entities matching the given {@link Qo}.
|
||||
* @since 1.10
|
||||
*/
|
||||
@Override
|
||||
public Iterable<T> findAll(Qo<T> query, Sort sort) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Page} of entities matching the given {@link Qo}. In case no match could be found, an empty
|
||||
* {@link Page} is returned.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param pageable may be {@link Pageable#unpaged()}, must not be {@literal null}.
|
||||
* @return a {@link Page} of entities matching the given {@link Qo}.
|
||||
*/
|
||||
@Override
|
||||
public Page<T> findAll(Qo<T> query, Pageable pageable) {
|
||||
Query querying = getQuery(query);
|
||||
if (null != querying) {
|
||||
querying.with(pageable);
|
||||
List<T> queryResult = mongoOperations.find(querying,
|
||||
entityInformation.getJavaType(), entityInformation.getCollectionName());
|
||||
return PageableExecutionUtils.getPage(queryResult, pageable, () -> count(query));
|
||||
}
|
||||
return Page.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of instances matching the given {@link Qo}.
|
||||
*
|
||||
* @param query the {@link Qo} to count instances for, must not be {@literal null}.
|
||||
* @return the number of instances matching the {@link Qo}.
|
||||
*/
|
||||
@Override
|
||||
public long count(Qo<T> query) {
|
||||
Query q = getQuery(query);
|
||||
return this.mongoOperations.count(q, entityInformation.getJavaType(), entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过特定键的集合查询
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 集合
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAllByValues(String key, List<Object> values) {
|
||||
Criteria criteria = Criteria.where(key).in(values);
|
||||
Query query = new Query(criteria);
|
||||
return mongoOperations.find(query,
|
||||
entityInformation.getJavaType(),
|
||||
entityInformation.getCollectionName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the data store contains elements that match the given {@link Qo}.
|
||||
*
|
||||
* @param query the {@link Qo} to use for the existence check, must not be {@literal null}.
|
||||
* @return {@literal true} if the data store contains elements that match the given {@link Qo}.
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(Qo<T> query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从查询实体抽取内部查询信息
|
||||
*
|
||||
* @param qo 查询实体
|
||||
* @return 结果
|
||||
*/
|
||||
private Query getQuery(Qo<T> qo) {
|
||||
Criteria criteria = null;
|
||||
if (null != qo.getCriteria()) {
|
||||
criteria = qo.getCriteria();
|
||||
} else if (null != qo.getExample()) {
|
||||
criteria = new Criteria().alike(qo.getExample());
|
||||
} else {
|
||||
Class<T> type = qo.pojoType();
|
||||
if (null != type && !Object.class.equals(type)) {
|
||||
try {
|
||||
T pojo = CopyUtils.copyQueryProps(qo, qo.pojoType().newInstance());
|
||||
criteria = new Criteria().alike(Example.of(pojo));
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (criteria != null) {
|
||||
// 针对删除状态全局筛选
|
||||
return new Query(Criteria.where("delete").ne(true).andOperator(criteria))
|
||||
.with(qo.sorts());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class CopyUtils {
|
||||
|
||||
/**
|
||||
* 从查询实体拷贝基本数据
|
||||
* 只匹配名称相同的
|
||||
*
|
||||
* @param query 查询实体
|
||||
* @param destination 目标
|
||||
* @param <T> 泛型
|
||||
* @param <Q> 泛型(查询)
|
||||
* @return 结果
|
||||
*/
|
||||
public static <T, Q extends Qo<T>> T copyQueryProps(Q query, T destination) {
|
||||
Map<String, PropertyDescriptor> getters = Arrays.stream(BeanUtils.getPropertyDescriptors(query.getClass()))
|
||||
.collect(Collectors.toMap(PropertyDescriptor::getName, p -> p));
|
||||
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(destination.getClass());
|
||||
if (descriptors.length != 0) {
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
// javaBean属性名
|
||||
String propertyName = descriptor.getName();
|
||||
if (!"class".equals(propertyName) && getters.containsKey(propertyName)) {
|
||||
try {
|
||||
Object value = getters.get(propertyName).getReadMethod().invoke(query);
|
||||
// 过滤空值,节约带宽
|
||||
if (value != null) {
|
||||
// javaBean属性值
|
||||
descriptor.getWriteMethod().invoke(destination, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝参数
|
||||
* entity类型必须和当前类型一致,否则报错
|
||||
*
|
||||
* @param source 要拷贝的实体
|
||||
* @param destination 目标实体
|
||||
* @param <T> 泛型
|
||||
*/
|
||||
public static <T, K> K copyProps(T source, K destination) {
|
||||
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(source.getClass());
|
||||
if (descriptors.length != 0) {
|
||||
// 是否可赋值
|
||||
boolean assignable = source.getClass().isAssignableFrom(destination.getClass());
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
// javaBean属性名
|
||||
String propertyName = descriptor.getName();
|
||||
if (!"class".equals(propertyName)) {
|
||||
try {
|
||||
Object value = descriptor.getReadMethod().invoke(source);
|
||||
// 过滤空值,节约带宽
|
||||
if (value != null) {
|
||||
// javaBean属性值
|
||||
Method writeMethod = assignable ? descriptor.getWriteMethod() :
|
||||
getWriteMethod(destination, propertyName).orElse(null);
|
||||
if (writeMethod != null) {
|
||||
writeMethod.invoke(destination, value);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static Optional<Method> getWriteMethod(Object target, String propertyName) {
|
||||
return Optional.ofNullable(BeanUtils.getPropertyDescriptor(target.getClass(), propertyName))
|
||||
.map(PropertyDescriptor::getWriteMethod);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
|
||||
/**
|
||||
* 查询工具嘞
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public final class CriteriaUtils {
|
||||
|
||||
/**
|
||||
* 判断是否非空
|
||||
*
|
||||
* @param criteria 查询
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isNotEmpty(Criteria criteria) {
|
||||
return !isEmpty(criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空
|
||||
*
|
||||
* @param criteria 查询
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isEmpty(Criteria criteria) {
|
||||
return null == criteria.getCriteriaObject() || criteria.getCriteriaObject().size() == 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
|
||||
/**
|
||||
* 数据存储使用工具
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public final class DataUtils {
|
||||
|
||||
/**
|
||||
* 自增
|
||||
*
|
||||
* @param count 数量
|
||||
* @return 结果
|
||||
*/
|
||||
public static int increment(Integer count) {
|
||||
if (null == count) {
|
||||
return 1;
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ref对象
|
||||
*
|
||||
* @param clazz 类
|
||||
* @param id 主键
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static <T extends Domain> T ref(Class<T> clazz, String id) {
|
||||
try {
|
||||
T instance = clazz.newInstance();
|
||||
instance.setId(id);
|
||||
return instance;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建ref对象
|
||||
*
|
||||
* @param instance 实例
|
||||
* @param <T> 泛型
|
||||
* @return 结果
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Domain> T ref(T instance) {
|
||||
Class<T> tClass = (Class<T>) instance.getClass();
|
||||
return ref(tClass, instance.getId());
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 日期区间工具类
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
@Slf4j
|
||||
public final class DateRangeUtil {
|
||||
|
||||
/**
|
||||
* 获取日期区间
|
||||
*
|
||||
* @param start 开始日期
|
||||
* @param end 截止日期
|
||||
* @param weeks 日期
|
||||
* @return 结果
|
||||
*/
|
||||
public static Map<Byte, List<Date>> getDates(Date start, Date end, Set<Byte> weeks) {
|
||||
// 获取日历
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(start);
|
||||
// 构建结果集
|
||||
Map<Byte, List<Date>> resultMap = new HashMap<>();
|
||||
Date current;
|
||||
// 开始循环查找
|
||||
while (!(current = calendar.getTime()).after(end)) {
|
||||
// 获得周数字
|
||||
byte week = (byte) calendar.get(Calendar.DAY_OF_WEEK);
|
||||
if (weeks.contains(week)) {
|
||||
List<Date> group = resultMap.computeIfAbsent(week, key -> new ArrayList<>());
|
||||
group.add(current);
|
||||
}
|
||||
// 增加
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的开始时间
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 日期的开始时间
|
||||
*/
|
||||
public static Date getBeginTimeOfDate(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
// 取当天的开始时间
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期的结束时间
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 日期的结束时间
|
||||
*/
|
||||
public static Date getEndTimeOfDate(Date date) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(date);
|
||||
// 取当天的结束时间
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 23);
|
||||
calendar.set(Calendar.MINUTE, 59);
|
||||
calendar.set(Calendar.SECOND, 59);
|
||||
calendar.set(Calendar.MILLISECOND, 999);
|
||||
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间段内所有的年月集合
|
||||
*
|
||||
* @param minDate 最小时间 2017-01
|
||||
* @param maxDate 最大时间 2017-10
|
||||
* @return 日期集合 格式为 年-月
|
||||
*/
|
||||
public static List<String> getMonthBetween(String minDate, String maxDate) {
|
||||
try {
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
SimpleDateFormat month = new SimpleDateFormat("yyyy-MM");//格式化为年月
|
||||
|
||||
Calendar min = Calendar.getInstance();
|
||||
Calendar max = Calendar.getInstance();
|
||||
|
||||
min.setTime(month.parse(minDate));
|
||||
min.set(min.get(Calendar.YEAR), min.get(Calendar.MONTH), 1);
|
||||
|
||||
max.setTime(month.parse(maxDate));
|
||||
max.set(max.get(Calendar.YEAR), max.get(Calendar.MONTH), 2);
|
||||
|
||||
while (min.before(max)) {
|
||||
result.add(month.format(min.getTime()));
|
||||
min.add(Calendar.MONTH, 1);
|
||||
}
|
||||
return result;
|
||||
} catch (ParseException e) {
|
||||
log.error("日期格式错误!", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getRangeOfMonth(String month) {
|
||||
try {
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
|
||||
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(sdf.parse(month));
|
||||
|
||||
sdf.applyPattern("yyyy-MM-dd");
|
||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
||||
result.add(sdf.format(getBeginTimeOfDate(cal.getTime())));
|
||||
cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
result.add(sdf.format(getEndTimeOfDate(cal.getTime())));
|
||||
|
||||
return result;
|
||||
} catch (ParseException e) {
|
||||
log.error("日期格式错误!", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 字段工具类
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public final class FieldUtils {
|
||||
|
||||
public static <T> T complete(T value, Supplier<T> supplier) {
|
||||
return ObjectUtils.isEmpty(value) ? supplier.get() : value;
|
||||
}
|
||||
|
||||
public static <T> T complete(T value, T defaultValue) {
|
||||
return ObjectUtils.isEmpty(value) ? defaultValue : value;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 查询参数
|
||||
*/
|
||||
public class Query extends LinkedHashMap<String, Object> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
//当前页码
|
||||
private int page = 1;
|
||||
//每页条数
|
||||
private int limit = 10;
|
||||
|
||||
public Query(Map<String, Object> params) {
|
||||
this.putAll(params);
|
||||
//分页参数
|
||||
if (params.get("page") != null) {
|
||||
this.page = Integer.parseInt(params.get("page").toString());
|
||||
}
|
||||
if (params.get("limit") != null) {
|
||||
this.limit = Integer.parseInt(params.get("limit").toString());
|
||||
}
|
||||
this.remove("page");
|
||||
this.remove("limit");
|
||||
}
|
||||
|
||||
|
||||
public int getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public void setPage(int page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.flyfish.framework.utils;
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/10.
|
||||
*/
|
||||
public class StringHelper {
|
||||
public static String getObjectValue(Object obj) {
|
||||
return obj == null ? "" : obj.toString();
|
||||
}
|
||||
}
|
81
flyfish-web/pom.xml
Normal file
81
flyfish-web/pom.xml
Normal file
@ -0,0 +1,81 @@
|
||||
<?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>flyfish-framework</artifactId>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>flyfish-web</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flyfish.framework</groupId>
|
||||
<artifactId>flyfish-data</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,29 @@
|
||||
package com.flyfish.framework.bean;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 同步vo
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Builder
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SyncVo<T extends Domain> {
|
||||
|
||||
private int success;
|
||||
|
||||
private int failed;
|
||||
|
||||
private int updated;
|
||||
|
||||
private List<T> list;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package com.flyfish.framework.beans.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* RestBean扫描注解
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RestBean {
|
||||
|
||||
/**
|
||||
* 生成的REST资源名称
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.flyfish.framework.beans.annotations;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 组合注解,便于声明controller
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@RestController
|
||||
@RequestMapping
|
||||
public @interface RestMapping {
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#name}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#value}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#path}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] path() default {};
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#params}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] params() default {};
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#headers}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] headers() default {};
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#consumes}.
|
||||
*
|
||||
* @since 4.3.5
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] consumes() default {};
|
||||
|
||||
/**
|
||||
* Alias for {@link RequestMapping#produces}.
|
||||
*/
|
||||
@AliasFor(annotation = RequestMapping.class)
|
||||
String[] produces() default {};
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.flyfish.framework.beans.resolver;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 动态Bean生成解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Component
|
||||
public class DynamicRestBeanResolver {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.flyfish.framework.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
|
||||
/**
|
||||
* 默认初始化的Bean配置
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class BeanConfig {
|
||||
|
||||
/**
|
||||
* 配置支持context-path
|
||||
*
|
||||
* @param serverProperties 服务器参数
|
||||
* @return 结果
|
||||
*/
|
||||
@Bean
|
||||
public WebFilter contextPathWebFilter(ServerProperties serverProperties) {
|
||||
String contextPath = serverProperties.getServlet().getContextPath();
|
||||
return (exchange, chain) -> {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
if (request.getURI().getPath().startsWith(contextPath)) {
|
||||
return chain.filter(
|
||||
exchange.mutate()
|
||||
.request(request.mutate().contextPath(contextPath).build())
|
||||
.build());
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package com.flyfish.framework.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.flyfish.framework.utils.RedisOperations;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
return new StringRedisTemplate(lettuceConnectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisTemplate配置
|
||||
*
|
||||
* @param lettuceConnectionFactory
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
|
||||
// 设置序列化
|
||||
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
jackson2JsonRedisSerializer.setObjectMapper(om);
|
||||
// 配置redisTemplate
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
|
||||
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
|
||||
redisTemplate.setKeySerializer(stringSerializer);// key序列化
|
||||
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
|
||||
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
|
||||
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisOperations redisOperations(LettuceConnectionFactory lettuceConnectionFactory, StringRedisTemplate stringRedisTemplate) {
|
||||
return new RedisOperations(redisTemplate(lettuceConnectionFactory), stringRedisTemplate);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.flyfish.framework.configuration;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.flyfish.framework.configuration.resolver.PageQueryArgumentResolver;
|
||||
import com.flyfish.framework.configuration.resolver.UserArgumentResolver;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.http.codec.json.Jackson2JsonDecoder;
|
||||
import org.springframework.http.codec.json.Jackson2JsonEncoder;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.web.reactive.config.EnableWebFlux;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* token拦截器配置
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebFlux
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class WebfluxConfig implements WebFluxConfigurer {
|
||||
|
||||
// @Resource
|
||||
// private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
|
||||
|
||||
/**
|
||||
* Configure resolvers for custom {@code @RequestMapping} method arguments.
|
||||
*
|
||||
* @param configurer to configurer to use
|
||||
*/
|
||||
@Override
|
||||
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
|
||||
ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance();
|
||||
configurer.addCustomResolver(new PageQueryArgumentResolver(registry));
|
||||
configurer.addCustomResolver(new UserArgumentResolver(registry));
|
||||
// configurer.addCustomResolver(new RequestContextBodyArgumentResolver(
|
||||
// requestMappingHandlerAdapter.getMessageReaders(), registry));
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置jackson
|
||||
*
|
||||
* @param configurer 配置器
|
||||
*/
|
||||
@Override
|
||||
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
|
||||
// 设置时区
|
||||
TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));
|
||||
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
|
||||
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
|
||||
.simpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
.timeZone("GMT+8")
|
||||
.serializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.build();
|
||||
defaults.jackson2JsonDecoder(
|
||||
new Jackson2JsonDecoder(mapper));
|
||||
defaults.jackson2JsonEncoder(
|
||||
new Jackson2JsonEncoder(mapper));
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.flyfish.framework.configuration.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 当前Session取出注解
|
||||
* 按需注入,提高性能,降低请求开销
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CurrentUser {
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.flyfish.framework.configuration.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 分页参数解析
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface PagedQuery {
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.flyfish.framework.configuration.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 包含请求上下文的body
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RequestContextBody {
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.flyfish.framework.configuration.jwt;
|
||||
|
||||
import com.flyfish.framework.service.MongoUserDetailsService;
|
||||
import com.flyfish.framework.utils.RedisOperations;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.net.URI;
|
||||
|
||||
@Slf4j
|
||||
public class JwtSecurityContextRepository implements ServerSecurityContextRepository {
|
||||
|
||||
@Resource
|
||||
private MongoUserDetailsService userDetailsService;
|
||||
@Resource
|
||||
private TokenProvider tokenProvider;
|
||||
@Resource
|
||||
private RedisOperations redisOperations;
|
||||
|
||||
@Override
|
||||
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
|
||||
ServerHttpRequest request = serverWebExchange.getRequest();
|
||||
String jwt = tokenProvider.retrieveToken(serverWebExchange).orElse(null);
|
||||
URI requestURI = request.getURI();
|
||||
// 存在jwt时,校验jwt。redis也需要存在
|
||||
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt) && redisOperations.hasKey(jwt)) {
|
||||
Authentication authentication = tokenProvider.getAuthentication(jwt);
|
||||
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI);
|
||||
return userDetailsService.findByUsername(authentication.getName())
|
||||
.flatMap(userDetails -> userDetailsService.loadContext(userDetails));
|
||||
} else {
|
||||
log.debug("no valid JWT token found, uri: {}", requestURI);
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有登录会调用这个
|
||||
*
|
||||
* @param webExchange 交换信息
|
||||
* @param securityContext 上下文
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> save(ServerWebExchange webExchange, SecurityContext securityContext) {
|
||||
// 添加jwtToken
|
||||
tokenProvider.addToken(webExchange, securityContext.getAuthentication());
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
package com.flyfish.framework.configuration.jwt;
|
||||
|
||||
import com.flyfish.framework.domain.base.IUser;
|
||||
import com.flyfish.framework.enums.UserType;
|
||||
import com.flyfish.framework.utils.RedisOperations;
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.io.DecodingException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.security.Key;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* token提供者,负责生成token
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Slf4j
|
||||
public class TokenProvider implements InitializingBean {
|
||||
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
private static final String AUTHORITIES_KEY = "auth";
|
||||
private static List<UserType> TOKEN_USER_TYPES = Arrays.asList(UserType.USER, UserType.VIP);
|
||||
private final String base64Secret;
|
||||
private final long tokenValidityInMilliseconds;
|
||||
private final long tokenValidityInMillisecondsForRememberMe;
|
||||
|
||||
@Resource
|
||||
private RedisOperations redisOperations;
|
||||
|
||||
private Key key;
|
||||
|
||||
public TokenProvider(String base64Secret, long tokenValidityInSeconds, long tokenValidityInSecondsForRememberMe) {
|
||||
this.base64Secret = base64Secret;
|
||||
this.tokenValidityInMilliseconds = tokenValidityInSeconds * 1000;
|
||||
this.tokenValidityInMillisecondsForRememberMe = tokenValidityInSecondsForRememberMe * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(base64Secret);
|
||||
this.key = Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从上下文获取token
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public Optional<String> retrieveToken(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
String bearerToken = request.getHeaders().getFirst(AUTHORIZATION_HEADER);
|
||||
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return Optional.of(bearerToken.substring(7));
|
||||
}
|
||||
// 从cookies里头找
|
||||
if (MapUtils.isNotEmpty(request.getCookies())) {
|
||||
return request.getCookies().keySet().stream().filter(name -> name.equals(AUTHORIZATION_HEADER))
|
||||
.findFirst().flatMap(name -> request.getCookies().get(name).stream()
|
||||
.filter(value -> StringUtils.isNotBlank(value.getValue()) && value.getValue().startsWith("Bearer-"))
|
||||
.findFirst()
|
||||
).map(cookie -> cookie.getValue().substring(7));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void addToken(ServerWebExchange exchange, Authentication authentication) {
|
||||
IUser user = (IUser) authentication.getPrincipal();
|
||||
String token = createToken(authentication, true);
|
||||
HttpHeaders headers = exchange.getResponse().getHeaders();
|
||||
// app用户从头部返回,方便获取
|
||||
if (TOKEN_USER_TYPES.contains(user.getUserType())) {
|
||||
headers.add("Token", token);
|
||||
}
|
||||
// token在web端的时间较短,不允许记住,所以使用短期
|
||||
exchange.getResponse().addCookie(ResponseCookie.from(AUTHORIZATION_HEADER, "Bearer-" + token).
|
||||
httpOnly(true).maxAge(tokenValidityInMilliseconds).build());
|
||||
// redis存储时间长
|
||||
redisOperations.set(token, true, tokenValidityInMillisecondsForRememberMe);
|
||||
}
|
||||
|
||||
public void removeToken(ServerWebExchange exchange) {
|
||||
retrieveToken(exchange).ifPresent(token -> redisOperations.del(token));
|
||||
}
|
||||
|
||||
public String createToken(Authentication authentication, boolean rememberMe) {
|
||||
String authorities = authentication.getAuthorities().stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.collect(Collectors.joining(","));
|
||||
|
||||
long now = (new Date()).getTime();
|
||||
Date validity;
|
||||
if (rememberMe) {
|
||||
validity = new Date(now + this.tokenValidityInMillisecondsForRememberMe);
|
||||
} else {
|
||||
validity = new Date(now + this.tokenValidityInMilliseconds);
|
||||
}
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(authentication.getName())
|
||||
.claim(AUTHORITIES_KEY, authorities)
|
||||
.signWith(key, SignatureAlgorithm.HS512)
|
||||
.setExpiration(validity)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public Authentication getAuthentication(String token) {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
Collection<? extends GrantedAuthority> authorities =
|
||||
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
User principal = new User(claims.getSubject(), "", authorities);
|
||||
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
|
||||
}
|
||||
|
||||
public boolean validateToken(String authToken) {
|
||||
try {
|
||||
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(authToken);
|
||||
return true;
|
||||
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
|
||||
log.info("Invalid JWT signature.");
|
||||
log.trace("Invalid JWT signature trace: {}", e, e);
|
||||
} catch (ExpiredJwtException e) {
|
||||
log.info("Expired JWT token.");
|
||||
log.trace("Expired JWT token trace: {}", e, e);
|
||||
} catch (UnsupportedJwtException e) {
|
||||
log.info("Unsupported JWT token.");
|
||||
log.trace("Unsupported JWT token trace: {}", e, e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("JWT token compact of handler are invalid.");
|
||||
log.trace("JWT token compact of handler are invalid trace: {}", e, e);
|
||||
} catch (DecodingException e) {
|
||||
log.info("Token解析失败!");
|
||||
log.trace("Token解析失败: {}", e, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.*;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.MonoProcessor;
|
||||
|
||||
import java.beans.ConstructorProperties;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ModelAttributeFilter {
|
||||
|
||||
private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
public ReactiveAdapterRegistry adapterRegistry;
|
||||
|
||||
public ModelAttributeFilter(ReactiveAdapterRegistry reactiveAdapterRegistry) {
|
||||
this.adapterRegistry = reactiveAdapterRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the model attribute name for the given method parameter based on
|
||||
* a {@code @ModelAttribute} parameter annotation (if present) or falling
|
||||
* back on parameter type based conventions.
|
||||
*
|
||||
* @param parameter a descriptor for the method parameter
|
||||
* @return the derived name
|
||||
* @see Conventions#getVariableNameForParameter(MethodParameter)
|
||||
*/
|
||||
public static String getNameForParameter(MethodParameter parameter) {
|
||||
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
|
||||
String name = (ann != null ? ann.value() : null);
|
||||
return (StringUtils.hasText(name) ? name : Conventions.getVariableNameForParameter(parameter));
|
||||
}
|
||||
|
||||
public ReactiveAdapterRegistry getAdapterRegistry() {
|
||||
return adapterRegistry;
|
||||
}
|
||||
|
||||
private Mono<?> prepareAttributeMono(String attributeName, ResolvableType attributeType,
|
||||
BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
Object attribute = context.getModel().asMap().get(attributeName);
|
||||
|
||||
if (attribute == null) {
|
||||
attribute = findAndRemoveReactiveAttribute(context.getModel(), attributeName);
|
||||
}
|
||||
|
||||
if (attribute == null) {
|
||||
Class<?> attributeClass = attributeType.getRawClass();
|
||||
attributeClass = attributeClass == null ? attributeType.resolve() : attributeClass;
|
||||
Assert.state(attributeClass != null, "No attribute class");
|
||||
return createAttribute(attributeName, attributeClass, context, exchange);
|
||||
}
|
||||
|
||||
ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute);
|
||||
if (adapterFrom != null) {
|
||||
Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding only supports single-value async types");
|
||||
return Mono.from(adapterFrom.toPublisher(attribute));
|
||||
} else {
|
||||
return Mono.justOrEmpty(attribute);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object findAndRemoveReactiveAttribute(Model model, String attributeName) {
|
||||
return model.asMap().entrySet().stream()
|
||||
.filter(entry -> {
|
||||
if (!entry.getKey().startsWith(attributeName)) {
|
||||
return false;
|
||||
}
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, entry.getValue());
|
||||
if (adapter == null) {
|
||||
return false;
|
||||
}
|
||||
String name = attributeName + ClassUtils.getShortName(adapter.getReactiveType());
|
||||
return entry.getKey().equals(name);
|
||||
})
|
||||
.findFirst()
|
||||
.map(entry -> {
|
||||
// Remove since we will be re-inserting the resolved attribute value
|
||||
model.asMap().remove(entry.getKey());
|
||||
return entry.getValue();
|
||||
})
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private Mono<?> createAttribute(
|
||||
String attributeName, Class<?> clazz, BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(clazz);
|
||||
if (ctor == null) {
|
||||
Constructor<?>[] ctors = clazz.getConstructors();
|
||||
if (ctors.length == 1) {
|
||||
ctor = ctors[0];
|
||||
} else {
|
||||
try {
|
||||
ctor = clazz.getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException ex) {
|
||||
throw new IllegalStateException("No primary or default constructor found for " + clazz, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return constructAttribute(ctor, attributeName, context, exchange);
|
||||
}
|
||||
|
||||
private Mono<?> constructAttribute(Constructor<?> ctor, String attributeName,
|
||||
BindingContext context, ServerWebExchange exchange) {
|
||||
|
||||
if (ctor.getParameterCount() == 0) {
|
||||
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
||||
return Mono.just(BeanUtils.instantiateClass(ctor));
|
||||
}
|
||||
|
||||
// A single data class constructor -> resolve constructor arguments from request parameters.
|
||||
return WebExchangeDataBinder.extractValuesToBind(exchange).map(bindValues -> {
|
||||
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
|
||||
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
|
||||
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
|
||||
Class<?>[] paramTypes = ctor.getParameterTypes();
|
||||
Assert.state(paramNames.length == paramTypes.length,
|
||||
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
WebDataBinder binder = context.createDataBinder(exchange, null, attributeName);
|
||||
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
|
||||
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
|
||||
for (int i = 0; i < paramNames.length; i++) {
|
||||
String paramName = paramNames[i];
|
||||
Class<?> paramType = paramTypes[i];
|
||||
Object value = bindValues.get(paramName);
|
||||
if (value == null) {
|
||||
if (fieldDefaultPrefix != null) {
|
||||
value = bindValues.get(fieldDefaultPrefix + paramName);
|
||||
}
|
||||
if (value == null && fieldMarkerPrefix != null) {
|
||||
if (bindValues.get(fieldMarkerPrefix + paramName) != null) {
|
||||
value = binder.getEmptyValue(paramType);
|
||||
}
|
||||
}
|
||||
}
|
||||
value = (value instanceof List ? ((List<?>) value).toArray() : value);
|
||||
MethodParameter methodParam = new MethodParameter(ctor, i);
|
||||
if (value == null && methodParam.isOptional()) {
|
||||
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
|
||||
} else {
|
||||
args[i] = binder.convertIfNecessary(value, paramTypes[i], methodParam);
|
||||
}
|
||||
}
|
||||
return BeanUtils.instantiateClass(ctor, args);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean hasErrorsArgument(MethodParameter parameter) {
|
||||
int i = parameter.getParameterIndex();
|
||||
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
|
||||
return (paramTypes.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
|
||||
}
|
||||
|
||||
private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) {
|
||||
for (Annotation ann : parameter.getParameterAnnotations()) {
|
||||
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
|
||||
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
|
||||
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
|
||||
if (hints != null) {
|
||||
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
|
||||
binder.validate(validationHints);
|
||||
} else {
|
||||
binder.validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<Object> filter(MethodParameter parameter, BindingContext context, ServerWebExchange exchange) {
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
Class<?> resolvedType = type.resolve();
|
||||
ReactiveAdapter adapter = (resolvedType != null ? getAdapterRegistry().getAdapter(resolvedType) : null);
|
||||
ResolvableType valueType = (adapter != null ? type.getGeneric() : type);
|
||||
|
||||
Assert.state(adapter == null || !adapter.isMultiValue(),
|
||||
() -> getClass().getSimpleName() + " does not support multi-value reactive type wrapper: " +
|
||||
parameter.getGenericParameterType());
|
||||
|
||||
String name = getNameForParameter(parameter);
|
||||
Mono<?> valueMono = prepareAttributeMono(name, valueType, context, exchange);
|
||||
|
||||
Map<String, Object> model = context.getModel().asMap();
|
||||
MonoProcessor<BindingResult> bindingResultMono = MonoProcessor.create();
|
||||
model.put(BindingResult.MODEL_KEY_PREFIX + name, bindingResultMono);
|
||||
|
||||
return valueMono.flatMap(value -> {
|
||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
|
||||
return binder.bind(exchange)
|
||||
.doOnError(bindingResultMono::onError)
|
||||
.doOnSuccess(aVoid -> {
|
||||
validateIfApplicable(binder, parameter);
|
||||
BindingResult errors = binder.getBindingResult();
|
||||
model.put(BindingResult.MODEL_KEY_PREFIX + name, errors);
|
||||
model.put(name, value);
|
||||
bindingResultMono.onNext(errors);
|
||||
})
|
||||
.then(Mono.fromCallable(() -> {
|
||||
BindingResult errors = binder.getBindingResult();
|
||||
if (adapter != null) {
|
||||
return adapter.fromPublisher(errors.hasErrors() ?
|
||||
Mono.error(new WebExchangeBindException(parameter, errors)) :
|
||||
valueMono);
|
||||
} else {
|
||||
if (errors.hasErrors() && !hasErrorsArgument(parameter)) {
|
||||
throw new WebExchangeBindException(parameter, errors);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* 分页参数过滤器
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public class PageParameterFilter implements ParameterFilter<Pageable> {
|
||||
|
||||
static final Pageable DEFAULT_PAGE_REQUEST = PageRequest.of(0, 20);
|
||||
private static final SortHandlerMethodArgumentResolver DEFAULT_SORT_RESOLVER = new SortHandlerMethodArgumentResolver();
|
||||
private static final String INVALID_DEFAULT_PAGE_SIZE = "Invalid default page size configured for method %s! Must not be less than one!";
|
||||
private static final String DEFAULT_PAGE_PARAMETER = "page";
|
||||
private static final String DEFAULT_SIZE_PARAMETER = "size";
|
||||
private static final String DEFAULT_PREFIX = "";
|
||||
private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
|
||||
private static final int DEFAULT_MAX_PAGE_SIZE = 2000;
|
||||
private Pageable fallbackPageable = DEFAULT_PAGE_REQUEST;
|
||||
private SortParameterFilter filter = new SortParameterFilter();
|
||||
private String pageParameterName = DEFAULT_PAGE_PARAMETER;
|
||||
private String sizeParameterName = DEFAULT_SIZE_PARAMETER;
|
||||
private String prefix = DEFAULT_PREFIX;
|
||||
private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;
|
||||
private int maxPageSize = DEFAULT_MAX_PAGE_SIZE;
|
||||
private boolean oneIndexedParameters = true;
|
||||
|
||||
public static Pageable filter(Pageable pageable) {
|
||||
int page = pageable.getPageNumber() != 0 ? pageable.getPageNumber() - 1 : 0;
|
||||
return PageRequest.of(page, pageable.getPageSize(), pageable.getSort());
|
||||
}
|
||||
|
||||
private static Pageable getDefaultPageRequestFrom(MethodParameter parameter, PageableDefault defaults) {
|
||||
|
||||
Integer defaultPageNumber = defaults.page();
|
||||
Integer defaultPageSize = getSpecificPropertyOrDefaultFromValue(defaults, "size");
|
||||
|
||||
if (defaultPageSize < 1) {
|
||||
Method annotatedMethod = parameter.getMethod();
|
||||
throw new IllegalStateException(String.format(INVALID_DEFAULT_PAGE_SIZE, annotatedMethod));
|
||||
}
|
||||
|
||||
if (defaults.sort().length == 0) {
|
||||
return PageRequest.of(defaultPageNumber, defaultPageSize);
|
||||
}
|
||||
|
||||
return PageRequest.of(defaultPageNumber, defaultPageSize, defaults.direction(), defaults.sort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the given specific property of the given annotation. If the value of that property is the
|
||||
* properties default, we fall back to the value of the {@code value} attribute.
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @param property must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getSpecificPropertyOrDefaultFromValue(Annotation annotation, String property) {
|
||||
|
||||
Object propertyDefaultValue = AnnotationUtils.getDefaultValue(annotation, property);
|
||||
Object propertyValue = AnnotationUtils.getValue(annotation, property);
|
||||
|
||||
Object result = ObjectUtils.nullSafeEquals(propertyDefaultValue, propertyValue) //
|
||||
? AnnotationUtils.getValue(annotation) //
|
||||
: propertyValue;
|
||||
|
||||
if (result == null) {
|
||||
throw new IllegalStateException("Exepected to be able to look up an annotation property value but failed!");
|
||||
}
|
||||
|
||||
return (T) result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts uniqueness of all {@link Pageable} parameters of the method of the given {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
public static void assertPageableUniqueness(MethodParameter parameter) {
|
||||
|
||||
Method method = parameter.getMethod();
|
||||
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException(String.format("Method parameter %s is not backed by a method.", parameter));
|
||||
}
|
||||
|
||||
if (containsMoreThanOnePageableParameter(method)) {
|
||||
Annotation[][] annotations = method.getParameterAnnotations();
|
||||
assertQualifiersFor(method.getParameterTypes(), annotations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that every {@link Pageable} parameter of the given parameters carries an {@link Qualifier} annotation to
|
||||
* distinguish them from each other.
|
||||
*
|
||||
* @param parameterTypes must not be {@literal null}.
|
||||
* @param annotations must not be {@literal null}.
|
||||
*/
|
||||
public static void assertQualifiersFor(Class<?>[] parameterTypes, Annotation[][] annotations) {
|
||||
|
||||
Set<String> values = new HashSet<>();
|
||||
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
|
||||
if (Pageable.class.equals(parameterTypes[i])) {
|
||||
|
||||
Qualifier qualifier = findAnnotation(annotations[i]);
|
||||
|
||||
if (null == qualifier) {
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
|
||||
}
|
||||
|
||||
if (values.contains(qualifier.value())) {
|
||||
throw new IllegalStateException("Values of the user Qualifiers must be unique!");
|
||||
}
|
||||
|
||||
values.add(qualifier.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Qualifier} annotation from the given array of {@link Annotation}s. Returns {@literal null} if the
|
||||
* array does not contain a {@link Qualifier} annotation.
|
||||
*
|
||||
* @param annotations must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private static Qualifier findAnnotation(Annotation[] annotations) {
|
||||
|
||||
for (Annotation annotation : annotations) {
|
||||
if (annotation instanceof Qualifier) {
|
||||
return (Qualifier) annotation;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link Method} has more than one {@link Pageable} parameter.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean containsMoreThanOnePageableParameter(Method method) {
|
||||
|
||||
boolean pageableFound = false;
|
||||
|
||||
for (Class<?> type : method.getParameterTypes()) {
|
||||
|
||||
if (pageableFound && type.equals(Pageable.class)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.equals(Pageable.class)) {
|
||||
pageableFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Pageable getDefaultFromAnnotationOrFallback(MethodParameter methodParameter) {
|
||||
|
||||
PageableDefault defaults = methodParameter.getParameterAnnotation(PageableDefault.class);
|
||||
|
||||
if (defaults != null) {
|
||||
return getDefaultPageRequestFrom(methodParameter, defaults);
|
||||
}
|
||||
|
||||
return fallbackPageable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the request parameter to find the {@link Pageable} information in. Inspects the given
|
||||
* {@link MethodParameter} for {@link Qualifier} present and prefixes the given source parameter name with it.
|
||||
*
|
||||
* @param source the basic parameter name.
|
||||
* @param parameter the {@link MethodParameter} potentially qualified.
|
||||
* @return the name of the request parameter.
|
||||
*/
|
||||
protected String getParameterNameToUse(String source, @Nullable MethodParameter parameter) {
|
||||
|
||||
StringBuilder builder = new StringBuilder(prefix);
|
||||
|
||||
Qualifier qualifier = parameter == null ? null : parameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (qualifier != null) {
|
||||
builder.append(qualifier.value());
|
||||
builder.append(qualifierDelimiter);
|
||||
}
|
||||
|
||||
return builder.append(source).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse the given {@link String} into an integer and applies the given boundaries. Will return 0 if the
|
||||
* {@link String} cannot be parsed.
|
||||
*
|
||||
* @param parameter the parameter value.
|
||||
* @param upper the upper bound to be applied.
|
||||
* @param shiftIndex whether to shift the index if {@link #oneIndexedParameters} is set to true.
|
||||
* @return
|
||||
*/
|
||||
private Optional<Integer> parseAndApplyBoundaries(@Nullable String parameter, int upper, boolean shiftIndex) {
|
||||
|
||||
if (!StringUtils.hasText(parameter)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
int parsed = Integer.parseInt(parameter) - (oneIndexedParameters && shiftIndex ? 1 : 0);
|
||||
return Optional.of(parsed < 0 ? 0 : parsed > upper ? upper : parsed);
|
||||
} catch (NumberFormatException e) {
|
||||
return Optional.of(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pageable filter(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
assertPageableUniqueness(parameter);
|
||||
|
||||
Optional<Pageable> defaultOrFallback = getDefaultFromAnnotationOrFallback(parameter).toOptional();
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
MultiValueMap<String, String> params = request.getQueryParams();
|
||||
String pageString = params.getFirst(getParameterNameToUse(pageParameterName, parameter));
|
||||
String pageSizeString = params.getFirst(getParameterNameToUse(sizeParameterName, parameter));
|
||||
|
||||
Optional<Integer> page = parseAndApplyBoundaries(pageString, Integer.MAX_VALUE, true);
|
||||
Optional<Integer> pageSize = parseAndApplyBoundaries(pageSizeString, maxPageSize, false);
|
||||
|
||||
if (!(page.isPresent() && pageSize.isPresent()) && !defaultOrFallback.isPresent()) {
|
||||
return Pageable.unpaged();
|
||||
}
|
||||
|
||||
int p = page
|
||||
.orElseGet(() -> defaultOrFallback.map(Pageable::getPageNumber).orElseThrow(IllegalStateException::new));
|
||||
int ps = pageSize
|
||||
.orElseGet(() -> defaultOrFallback.map(Pageable::getPageSize).orElseThrow(IllegalStateException::new));
|
||||
|
||||
// Limit lower bound
|
||||
ps = ps < 1 ? defaultOrFallback.map(Pageable::getPageSize).orElseThrow(IllegalStateException::new) : ps;
|
||||
// Limit upper bound
|
||||
ps = ps > maxPageSize ? maxPageSize : ps;
|
||||
|
||||
Sort sort = filter.filter(parameter, bindingContext, exchange);
|
||||
|
||||
return PageRequest.of(p, ps,
|
||||
sort.isSorted() ? sort : defaultOrFallback.map(Pageable::getSort).orElseGet(Sort::unsorted));
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import com.flyfish.framework.configuration.annotations.PagedQuery;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.utils.UserUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 解析分页请求参数为需要的bean,比map更语义化
|
||||
*
|
||||
* @author Mr.Wang
|
||||
*/
|
||||
public class PageQueryArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
||||
|
||||
/**
|
||||
* 这是spirng-data-jpa自带的分页参数解析器,用于在本解析器里解析分页参数
|
||||
*/
|
||||
private ParameterFilter<Pageable> pageableParameterFilter;
|
||||
|
||||
/**
|
||||
* 这是spring原生自带的servlet请求数据解析器,用于解析请求参数到bean
|
||||
*/
|
||||
private ModelAttributeFilter modelAttributeFilter;
|
||||
|
||||
public PageQueryArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
|
||||
super(adapterRegistry);
|
||||
this.pageableParameterFilter = new PageParameterFilter();
|
||||
this.modelAttributeFilter = new ModelAttributeFilter(adapterRegistry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter methodParameter) {
|
||||
return Qo.class.isAssignableFrom(methodParameter.getParameterType()) &&
|
||||
methodParameter.hasParameterAnnotation(PagedQuery.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析结果
|
||||
*
|
||||
* @param parameter 方法参数
|
||||
* @param bindingContext 上下文
|
||||
* @param exchange 交换数据
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
// 使用spring的hack来解析pageable
|
||||
Pageable pageable = pageableParameterFilter.filter(parameter, bindingContext, exchange);
|
||||
// 使用spring的默认解析器解析所有变量
|
||||
return modelAttributeFilter.filter(parameter,
|
||||
bindingContext, exchange).flatMap(qo -> {
|
||||
if (qo instanceof Qo) {
|
||||
Qo query = (Qo) qo;
|
||||
query.setPageable(pageable);
|
||||
return ReactiveSecurityContextHolder.getContext().map(securityContext -> {
|
||||
query.setUser(UserUtils.extractUser(securityContext));
|
||||
return query;
|
||||
}).defaultIfEmpty(query);
|
||||
}
|
||||
return Mono.just(qo);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface ParameterFilter<T> {
|
||||
|
||||
/**
|
||||
* 从已有的拦截器解析数据
|
||||
*
|
||||
* @param parameter 方法参数
|
||||
* @param bindingContext 上下文
|
||||
* @param exchange web数据
|
||||
* @return 结果
|
||||
*/
|
||||
T filter(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange);
|
||||
|
||||
default Mono<T> filterMono(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
return Mono.empty();
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import com.flyfish.framework.configuration.annotations.RequestContextBody;
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.utils.UserUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 附带上下文你的请求参数解析器
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class RequestContextBodyArgumentResolver extends AbstractMessageReaderArgumentResolver {
|
||||
|
||||
public RequestContextBodyArgumentResolver(List<HttpMessageReader<?>> readers,
|
||||
ReactiveAdapterRegistry registry) {
|
||||
|
||||
super(readers, registry);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.hasParameterAnnotation(RequestContextBody.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> resolveArgument(
|
||||
MethodParameter param, BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
|
||||
RequestBody ann = param.getParameterAnnotation(RequestBody.class);
|
||||
Assert.state(ann != null, "No RequestBody annotation");
|
||||
return readBody(param, ann.required(), bindingContext, exchange)
|
||||
.flatMap(object -> {
|
||||
if (object instanceof Domain) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(UserUtils::extractUser)
|
||||
.map(user -> {
|
||||
Domain entity = (Domain) object;
|
||||
entity.setCurrentUser(user);
|
||||
return object;
|
||||
})
|
||||
.defaultIfEmpty(object);
|
||||
}
|
||||
return Mono.just(object);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SortParameterFilter implements ParameterFilter<Sort> {
|
||||
|
||||
private static final String DEFAULT_PARAMETER = "sort";
|
||||
private static final String DEFAULT_PROPERTY_DELIMITER = ",";
|
||||
private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
|
||||
private static final Sort DEFAULT_SORT = Sort.unsorted();
|
||||
|
||||
private static final String SORT_DEFAULTS_NAME = SortDefault.SortDefaults.class.getSimpleName();
|
||||
private static final String SORT_DEFAULT_NAME = SortDefault.class.getSimpleName();
|
||||
|
||||
private Sort fallbackSort = DEFAULT_SORT;
|
||||
private String sortParameter = DEFAULT_PARAMETER;
|
||||
private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
|
||||
private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;
|
||||
|
||||
private static Optional<Sort.Order> toOrder(String property, Optional<Sort.Direction> direction) {
|
||||
|
||||
if (!StringUtils.hasText(property)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(direction.map(it -> new Sort.Order(it, property)).orElseGet(() -> Sort.Order.by(property)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sort filter(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
|
||||
List<String> directionParameter = exchange.getRequest().getQueryParams().get(getSortParameter(parameter));
|
||||
|
||||
// No parameter
|
||||
if (directionParameter == null) {
|
||||
return getDefaultFromAnnotationOrFallback(parameter);
|
||||
}
|
||||
|
||||
// Single empty parameter, e.g "sort="
|
||||
if (directionParameter.size() == 1 && !StringUtils.hasText(directionParameter.get(0))) {
|
||||
return getDefaultFromAnnotationOrFallback(parameter);
|
||||
}
|
||||
|
||||
return parseParameterIntoSort(directionParameter, propertyDelimiter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the default {@link Sort} to be used from the given {@link MethodParameter}. Rejects the parameter if both an
|
||||
* {@link SortDefault.SortDefaults} and {@link SortDefault} annotation is found as we cannot build a reliable {@link Sort}
|
||||
* instance then (property ordering).
|
||||
*
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @return the default {@link Sort} instance derived from the parameter annotations or the configured fallback-sort
|
||||
* .
|
||||
*/
|
||||
private Sort getDefaultFromAnnotationOrFallback(MethodParameter parameter) {
|
||||
|
||||
SortDefault.SortDefaults annotatedDefaults = parameter.getParameterAnnotation(SortDefault.SortDefaults.class);
|
||||
SortDefault annotatedDefault = parameter.getParameterAnnotation(SortDefault.class);
|
||||
|
||||
if (annotatedDefault != null && annotatedDefaults != null) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Cannot use both @%s and @%s on parameter %s! Move %s into %s to define sorting order!",
|
||||
SORT_DEFAULTS_NAME, SORT_DEFAULT_NAME, parameter.toString(), SORT_DEFAULT_NAME, SORT_DEFAULTS_NAME));
|
||||
}
|
||||
|
||||
if (annotatedDefault != null) {
|
||||
return appendOrCreateSortTo(annotatedDefault, Sort.unsorted());
|
||||
}
|
||||
|
||||
if (annotatedDefaults != null) {
|
||||
|
||||
Sort sort = Sort.unsorted();
|
||||
|
||||
for (SortDefault currentAnnotatedDefault : annotatedDefaults.value()) {
|
||||
sort = appendOrCreateSortTo(currentAnnotatedDefault, sort);
|
||||
}
|
||||
|
||||
return sort;
|
||||
}
|
||||
|
||||
return fallbackSort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Sort} instance from the given {@link SortDefault} or appends it to the given {@link Sort}
|
||||
* instance if it's not {@literal null}.
|
||||
*
|
||||
* @param sortDefault
|
||||
* @param sortOrNull
|
||||
* @return
|
||||
*/
|
||||
private Sort appendOrCreateSortTo(SortDefault sortDefault, Sort sortOrNull) {
|
||||
|
||||
String[] fields = PageParameterFilter.getSpecificPropertyOrDefaultFromValue(sortDefault, "sort");
|
||||
|
||||
if (fields.length == 0) {
|
||||
return Sort.unsorted();
|
||||
}
|
||||
|
||||
return sortOrNull.and(Sort.by(sortDefault.direction(), fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sort parameter to be looked up from the request. Potentially applies qualifiers to it.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected String getSortParameter(@Nullable MethodParameter parameter) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
Qualifier qualifier = parameter != null ? parameter.getParameterAnnotation(Qualifier.class) : null;
|
||||
|
||||
if (qualifier != null) {
|
||||
builder.append(qualifier.value()).append(qualifierDelimiter);
|
||||
}
|
||||
|
||||
return builder.append(sortParameter).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given sort expressions into a {@link Sort} instance. The implementation expects the sources to be a
|
||||
* concatenation of Strings using the given delimiter. If the last element can be parsed into a {@link Sort.Direction} it's
|
||||
* considered a {@link Sort.Direction} and a simple property otherwise.
|
||||
*
|
||||
* @param source will never be {@literal null}.
|
||||
* @param delimiter the delimiter to be used to split up the source elements, will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Sort parseParameterIntoSort(List<String> source, String delimiter) {
|
||||
|
||||
List<Sort.Order> allOrders = new ArrayList<>();
|
||||
|
||||
for (String part : source) {
|
||||
|
||||
if (part == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] elements = part.split(delimiter);
|
||||
|
||||
Optional<Sort.Direction> direction = elements.length == 0 ? Optional.empty()
|
||||
: Sort.Direction.fromOptionalString(elements[elements.length - 1]);
|
||||
|
||||
int lastIndex = direction.map(it -> elements.length - 1).orElseGet(() -> elements.length);
|
||||
|
||||
for (int i = 0; i < lastIndex; i++) {
|
||||
toOrder(elements[i], direction).ifPresent(allOrders::add);
|
||||
}
|
||||
}
|
||||
|
||||
return allOrders.isEmpty() ? Sort.unsorted() : Sort.by(allOrders);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.flyfish.framework.configuration.resolver;
|
||||
|
||||
import com.flyfish.framework.configuration.annotations.CurrentUser;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.utils.UserUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.web.reactive.BindingContext;
|
||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 用户数据解析器
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public class UserArgumentResolver extends HandlerMethodArgumentResolverSupport {
|
||||
|
||||
public UserArgumentResolver(ReactiveAdapterRegistry adapterRegistry) {
|
||||
super(adapterRegistry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter methodParameter) {
|
||||
return User.class.isAssignableFrom(methodParameter.getParameterType()) &&
|
||||
methodParameter.hasParameterAnnotation(CurrentUser.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> resolveArgument(MethodParameter methodParameter, BindingContext bindingContext, ServerWebExchange serverWebExchange) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(UserUtils::extractUser);
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.bean.SyncVo;
|
||||
import com.flyfish.framework.configuration.annotations.CurrentUser;
|
||||
import com.flyfish.framework.configuration.annotations.PagedQuery;
|
||||
import com.flyfish.framework.constant.ReactiveConstants;
|
||||
import com.flyfish.framework.context.UserContext;
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.domain.po.User;
|
||||
import com.flyfish.framework.service.BaseReactiveService;
|
||||
import com.flyfish.framework.service.BaseService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用RESTful的控制器,用于动态提供基础的RESTful接口
|
||||
*
|
||||
* @author wangyu
|
||||
* @create 2017-06-15 8:48
|
||||
*/
|
||||
public abstract class BaseController<T extends Domain, Q extends Qo<T>> {
|
||||
|
||||
@Autowired
|
||||
protected BaseService<T> service;
|
||||
@Autowired(required = false)
|
||||
protected BaseReactiveService<T> reactiveService;
|
||||
@Autowired
|
||||
protected UserContext userContext;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends BaseService<T>> S getService() {
|
||||
return (S) service;
|
||||
}
|
||||
|
||||
@GetMapping("/exists")
|
||||
public Result<Boolean> exists(@PagedQuery Q qo) {
|
||||
return Result.accept(service.count(qo) != 0);
|
||||
}
|
||||
|
||||
@GetMapping("/count")
|
||||
public Result<Long> count(@PagedQuery Q qo) {
|
||||
return Result.accept(service.count(qo));
|
||||
}
|
||||
|
||||
@PostMapping("")
|
||||
public Result<T> create(@RequestBody @Valid T entity, @CurrentUser User user) {
|
||||
userContext.setUser(user);
|
||||
return Result.accept(service.create(entity));
|
||||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
public Result<T> get(@PathVariable String id) {
|
||||
return service.getById(id).map(Result::accept).orElse(Result.notFound());
|
||||
}
|
||||
|
||||
@PutMapping("{id}")
|
||||
public Result<T> update(@RequestBody @Valid T entity, @CurrentUser User user) {
|
||||
userContext.setUser(user);
|
||||
return Result.accept(service.updateSelectiveById(entity));
|
||||
}
|
||||
|
||||
@PutMapping("")
|
||||
public Result<List<T>> updateList(@RequestBody List<T> list, @CurrentUser User user) {
|
||||
userContext.setUser(user);
|
||||
return Result.accept(service.updateBatch(list));
|
||||
}
|
||||
|
||||
@PutMapping("/sync")
|
||||
public Result<SyncVo<T>> syncList(@RequestBody List<T> list, @CurrentUser User user) {
|
||||
userContext.setUser(user);
|
||||
return Result.accept(service.sync(list));
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
public Result<T> remove(@PathVariable String id) {
|
||||
service.deleteById(id);
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@GetMapping("/all")
|
||||
public Result<List<T>> all(@PagedQuery Q qo) {
|
||||
if (qo.isEmpty()) {
|
||||
return Result.accept(service.getAll());
|
||||
}
|
||||
return Result.accept(service.getList(qo));
|
||||
}
|
||||
|
||||
@GetMapping("")
|
||||
public Result<List<T>> list(@PagedQuery Q qo) {
|
||||
if (null != qo.getPageable()) {
|
||||
return Result.accept(service.getPageList(qo));
|
||||
}
|
||||
return Result.accept(service.getList(qo));
|
||||
}
|
||||
|
||||
/**
|
||||
* reactive接口
|
||||
*/
|
||||
@PostMapping(value = "", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<T>> reactiveCreate(@RequestBody @Valid T entity) {
|
||||
return reactiveService.create(entity).map(Result::accept);
|
||||
}
|
||||
|
||||
@GetMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<T>> reactiveGet(@PathVariable String id) {
|
||||
return reactiveService.getById(id)
|
||||
.map(Result::accept)
|
||||
.defaultIfEmpty(Result.notFound());
|
||||
}
|
||||
|
||||
@PutMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<T>> reactiveUpdate(@RequestBody @Valid T entity) {
|
||||
return reactiveService.updateById(entity)
|
||||
.map(Result::accept)
|
||||
.defaultIfEmpty(Result.notFound());
|
||||
}
|
||||
|
||||
@DeleteMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<T>> reactiveRemove(@PathVariable String id) {
|
||||
return reactiveService.deleteById(id).map(item -> Result.ok());
|
||||
}
|
||||
|
||||
@GetMapping(value = "/all", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<List<T>>> reactiveAll() {
|
||||
return reactiveService.getAll()
|
||||
.collectList()
|
||||
.map(Result::accept)
|
||||
.defaultIfEmpty(Result.emptyList());
|
||||
}
|
||||
|
||||
@GetMapping(value = "", headers = ReactiveConstants.USE_REACTIVE)
|
||||
public Mono<Result<List<T>>> reactiveList(@PagedQuery Q qo) {
|
||||
if (null != qo.getPageable()) {
|
||||
return reactiveService.getPageList(qo)
|
||||
.map(Result::accept)
|
||||
.defaultIfEmpty(Result.emptyPage());
|
||||
}
|
||||
return reactiveService.getList(qo).collectList()
|
||||
.map(Result::accept)
|
||||
.defaultIfEmpty(Result.emptyList());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.flyfish.framework.controller;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.domain.base.TreeDomain;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 支持树形解析的controller
|
||||
*
|
||||
* @param <T>
|
||||
* @param <Q>
|
||||
*/
|
||||
public abstract class TreeController<T extends TreeDomain<T>, Q extends Qo<T>> extends BaseController<T, Q> {
|
||||
|
||||
/**
|
||||
* 获取权限树
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@GetMapping("tree")
|
||||
public Result<List<T>> getTree(Q qo) {
|
||||
// 第一步,查询全部,并根据条件筛选
|
||||
List<T> filtered = service.getList(qo);
|
||||
// 第二步,根据父id组成map
|
||||
Map<String, List<T>> group = filtered.stream()
|
||||
.collect(Collectors.groupingBy(p -> StringUtils.defaultIfBlank(p.getParentId(), "")));
|
||||
// 第三步,筛选一级树深度
|
||||
List<T> topList = filtered.stream().filter(item -> item.getDepth() == 1).collect(Collectors.toList());
|
||||
// 第三步,根据父id的map填充根tree
|
||||
return Result.accept(applyChildren(topList, group));
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归赋值儿子们
|
||||
*
|
||||
* @param children 儿子们
|
||||
* @param group 分组
|
||||
*/
|
||||
private List<T> applyChildren(List<T> children, Map<String, List<T>> group) {
|
||||
if (CollectionUtils.isNotEmpty(children)) {
|
||||
children.forEach(child -> {
|
||||
List<T> treeNodes = group.getOrDefault(child.getId(), Collections.emptyList());
|
||||
child.setChildren(treeNodes);
|
||||
applyChildren(treeNodes, group);
|
||||
});
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.flyfish.framework.handler;
|
||||
|
||||
import com.flyfish.framework.bean.BaseResponse;
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.exception.BaseException;
|
||||
import com.flyfish.framework.exception.auth.ClientTokenException;
|
||||
import com.flyfish.framework.exception.auth.UserInvalidException;
|
||||
import com.flyfish.framework.exception.auth.UserTokenException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Created by wangyu on 2017/9/8.
|
||||
*/
|
||||
@RestControllerAdvice("com.flyfish")
|
||||
@ResponseBody
|
||||
public class GlobalExceptionHandler {
|
||||
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(ClientTokenException.class)
|
||||
public BaseResponse clientTokenExceptionHandler(ClientTokenException ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
return new BaseResponse(ex.getStatus(), ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(UserTokenException.class)
|
||||
public BaseResponse userTokenExceptionHandler(UserTokenException ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
return new BaseResponse(ex.getStatus(), ex.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(UserInvalidException.class)
|
||||
public Mono<Result<Void>> userInvalidExceptionHandler(UserInvalidException ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
return Mono.just(Result.error(ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(BaseException.class)
|
||||
public Mono<Result<Void>> baseExceptionHandler(BaseException ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
return Mono.just(Result.error(ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(DuplicateKeyException.class)
|
||||
public Mono<Result<Void>> duplicateHandler(DuplicateKeyException ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
if (StringUtils.isNotBlank(ex.getMessage())) {
|
||||
if (ex.getMessage().contains("username")) {
|
||||
return Mono.just(Result.error("用户名已经被注册,请换一个哟!"));
|
||||
}
|
||||
if (ex.getMessage().contains("openId")) {
|
||||
return Mono.just(Result.error("本设备已经注册过帐号了,请更换设备再试哦!"));
|
||||
}
|
||||
}
|
||||
return Mono.just(Result.error("系统中存在重复的值了,请换一个哟"));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Mono<Result<Void>> otherExceptionHandler(Exception ex) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
return Mono.just(Result.error(ex.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验参数异常
|
||||
*/
|
||||
@ExceptionHandler(WebExchangeBindException.class)
|
||||
public Mono<Result<Void>> validException(WebExchangeBindException e) {
|
||||
return Mono.just(Result.error(e.getBindingResult().getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
|
||||
.collect(Collectors.joining(","))));
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.flyfish.framework.handler;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.transform.DataBufferTransformer;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于json的登录失败包装,详见Spring Security
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
public class JsonAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
|
||||
|
||||
private final String defaultMessage = "用户登录异常!";
|
||||
// 数据块工厂
|
||||
private final DataBufferTransformer<Result> dataBufferTransformer;
|
||||
|
||||
private Map<Class<? extends AuthenticationException>, String> descriptionMap = new HashMap<>();
|
||||
|
||||
public JsonAuthenticationFailureHandler(DataBufferTransformer<Result> dataBufferTransformer) {
|
||||
this.dataBufferTransformer = dataBufferTransformer;
|
||||
descriptionMap.put(BadCredentialsException.class, "用户名密码不正确!");
|
||||
// todo 剩下的都在userDetailsService
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败时返回具体失败原因
|
||||
*
|
||||
* @param webFilterExchange 请求数据
|
||||
* @param exception 异常
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
|
||||
ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
return response.writeWith(Mono.fromCallable(() ->
|
||||
dataBufferTransformer.transform(
|
||||
Result.error(descriptionMap.getOrDefault(exception.getClass(), exception.getMessage()))
|
||||
)));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.flyfish.framework.handler;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.transform.DataBufferTransformer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 登录成功的回调
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JsonAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
|
||||
|
||||
// 数据块工厂
|
||||
private final DataBufferTransformer<Result> dataBufferTransformer;
|
||||
|
||||
/**
|
||||
* 登录成功后要返回用户的基本信息,节省带宽
|
||||
*
|
||||
* @param webFilterExchange 请求信息
|
||||
* @param authentication 认证信息
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
|
||||
ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
|
||||
HttpHeaders headers = response.getHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
// 查询
|
||||
return response.writeWith(ReactiveSecurityContextHolder.getContext().map(securityContext ->
|
||||
dataBufferTransformer.transform(Result.accept(securityContext.getAuthentication().getPrincipal()))
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.flyfish.framework.handler;
|
||||
|
||||
import com.flyfish.framework.bean.Result;
|
||||
import com.flyfish.framework.configuration.jwt.TokenProvider;
|
||||
import com.flyfish.framework.transform.DataBufferTransformer;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.server.WebFilterExchange;
|
||||
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 退出登录的回调
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JsonLogoutSuccessHandler implements ServerLogoutSuccessHandler {
|
||||
|
||||
// 数据块工厂
|
||||
private final DataBufferTransformer<Result> dataBufferTransformer;
|
||||
|
||||
private final TokenProvider tokenProvider;
|
||||
|
||||
@Override
|
||||
public Mono<Void> onLogoutSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
|
||||
ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
|
||||
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
tokenProvider.removeToken(webFilterExchange.getExchange());
|
||||
// 查询
|
||||
return response.writeWith(Mono.just(dataBufferTransformer.transform(Result.ok())));
|
||||
}
|
||||
}
|
@ -0,0 +1,160 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基础Service,全异步接口
|
||||
* 代理Repo的所有方法,内部封装常用的全部操作
|
||||
*
|
||||
* @author wybab
|
||||
*/
|
||||
public interface BaseReactiveService<T> {
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<T> getOne(T entity);
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param query 查询模型
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<T> getOne(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 通过Id查询
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<T> getById(String id);
|
||||
|
||||
/**
|
||||
* 根据ID集合来查询
|
||||
*
|
||||
* @param ids id列表
|
||||
* @return 结果
|
||||
*/
|
||||
Flux<T> getByIds(List<String> ids);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
Flux<T> getList(T entity);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询
|
||||
* @return 结果
|
||||
*/
|
||||
Flux<T> getList(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<Page<T>> getPageList(T entity, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询实体
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<Page<T>> getPageList(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 获取所有对象
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
Flux<T> getAll();
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @return 总数
|
||||
*/
|
||||
Mono<Long> countAll();
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @param entity 通过实体查询个数
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<Long> count(T entity);
|
||||
|
||||
/**
|
||||
* 插入新记录
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
Mono<T> create(T entity);
|
||||
|
||||
/**
|
||||
* 插入 不插入null字段
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
Mono<T> createSelective(T entity);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
Mono<Void> delete(T entity);
|
||||
|
||||
/**
|
||||
* 根据Id删除
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
Mono<Void> deleteById(String id);
|
||||
|
||||
/**
|
||||
* 根据id更新
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
Mono<T> updateById(T entity);
|
||||
|
||||
/**
|
||||
* 不update null
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
Mono<T> updateSelectiveById(T entity);
|
||||
|
||||
/**
|
||||
* 根据ID集合批量删除
|
||||
*
|
||||
* @param ids id结合
|
||||
*/
|
||||
Mono<Void> deleteBatchByIds(List<String> ids);
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
*
|
||||
* @param entities 要批量更新的集合
|
||||
*/
|
||||
Flux<T> updateBatch(List<T> entities);
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import com.flyfish.framework.bean.SyncVo;
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 基础的Service,提供同步数据结构
|
||||
* 代理Repo的所有方法,内部封装常用的全部操作
|
||||
*
|
||||
* @param <T> 实体泛型
|
||||
*/
|
||||
public interface BaseService<T extends Domain> {
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<T> getOne(T entity);
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param query 查询模型
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<T> getOne(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 通过Id查询
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<T> getById(String id);
|
||||
|
||||
/**
|
||||
* 通过实体名称访问
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 结果
|
||||
*/
|
||||
Optional<T> getByName(String name);
|
||||
|
||||
/**
|
||||
* 根据ID集合来查询
|
||||
*
|
||||
* @param ids id列表
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> getByIds(Collection<String> ids);
|
||||
|
||||
/**
|
||||
* 通过某个字段的集合包含查询
|
||||
*
|
||||
* @param key 键
|
||||
* @param values 值
|
||||
* @return 结果
|
||||
*/
|
||||
<K> List<T> getByValues(String key, List<K> values);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> getList(T entity);
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> getList(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
Page<T> getPageList(T entity, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询实体
|
||||
* @return 结果
|
||||
*/
|
||||
Page<T> getPageList(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 获取所有对象
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
List<T> getAll();
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @return 总数
|
||||
*/
|
||||
Long countAll();
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @param entity 通过实体查询个数
|
||||
* @return 结果
|
||||
*/
|
||||
Long count(T entity);
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @param query 查询实体
|
||||
* @return 结果
|
||||
*/
|
||||
Long count(Qo<T> query);
|
||||
|
||||
/**
|
||||
* 插入新记录
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
T create(T entity);
|
||||
|
||||
/**
|
||||
* 插入 不插入null字段
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
T createSelective(T entity);
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
void delete(T entity);
|
||||
|
||||
/**
|
||||
* 根据Id删除
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
void deleteById(String id);
|
||||
|
||||
/**
|
||||
* 根据id更新
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
T updateById(T entity);
|
||||
|
||||
/**
|
||||
* 不update null
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
T updateSelectiveById(T entity);
|
||||
|
||||
/**
|
||||
* 根据ID集合批量删除
|
||||
*
|
||||
* @param ids id结合
|
||||
*/
|
||||
void deleteBatchByIds(List<String> ids);
|
||||
|
||||
/**
|
||||
* 通过某一个键删除所有
|
||||
*
|
||||
* @param qo 查询实体
|
||||
*/
|
||||
void deleteAll(Qo<T> qo);
|
||||
|
||||
/**
|
||||
* 通过某一个键删除所有
|
||||
*
|
||||
* @param entity 查询
|
||||
*/
|
||||
void deleteAll(T entity);
|
||||
|
||||
/**
|
||||
* 删除全部实体,危险!
|
||||
*/
|
||||
void deleteAll();
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
*
|
||||
* @param entities 要批量更新的集合
|
||||
*/
|
||||
List<T> updateBatch(List<T> entities);
|
||||
|
||||
/**
|
||||
* 同步数据
|
||||
*
|
||||
* @param entities 数据们
|
||||
* @return 结果
|
||||
*/
|
||||
SyncVo<T> sync(List<T> entities);
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package com.flyfish.framework.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* spring security用户详情服务
|
||||
*
|
||||
* @author wangyu
|
||||
*/
|
||||
@Qualifier("mongoUserDetailsService")
|
||||
public interface MongoUserDetailsService extends ReactiveUserDetailsService, ReactiveUserDetailsPasswordService {
|
||||
|
||||
/**
|
||||
* 针对微信端,即时校验登录
|
||||
*
|
||||
* @param user 用户
|
||||
* @param exchange 数据
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<Authentication> authenticate(UserDetails user, ServerWebExchange exchange);
|
||||
|
||||
/**
|
||||
* 加载上下文,提供token调用
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<SecurityContext> loadContext(UserDetails user);
|
||||
|
||||
/**
|
||||
* 主动退出登录
|
||||
*
|
||||
* @param exchange 数据
|
||||
* @return 结果
|
||||
*/
|
||||
Mono<Void> logout(ServerWebExchange exchange);
|
||||
}
|
@ -0,0 +1,254 @@
|
||||
package com.flyfish.framework.service.impl;
|
||||
|
||||
import com.flyfish.framework.domain.base.Domain;
|
||||
import com.flyfish.framework.domain.base.Qo;
|
||||
import com.flyfish.framework.repository.DefaultReactiveRepository;
|
||||
import com.flyfish.framework.service.BaseReactiveService;
|
||||
import com.flyfish.framework.utils.Assert;
|
||||
import com.flyfish.framework.utils.CopyUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基础的Service,继承常用的全部Crud操作,基于JPARepository
|
||||
* TODO 此方法在更新和查询存在不完善,后续完善底层支持,用于性能优化
|
||||
*
|
||||
* @param <T> 实体泛型
|
||||
*/
|
||||
public class BaseReactiveServiceImpl<T extends Domain> implements BaseReactiveService<T> {
|
||||
|
||||
@Autowired
|
||||
protected DefaultReactiveRepository<T> repository;
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> getOne(T entity) {
|
||||
return repository.findOne(Example.of(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询
|
||||
*
|
||||
* @param query 查询模型
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> getOne(Qo<T> query) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过Id查询
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> getById(String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID集合来查询
|
||||
*
|
||||
* @param ids id列表
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> getByIds(List<String> ids) {
|
||||
return repository.findAllById(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> getList(T entity) {
|
||||
return repository.findAll(Example.of(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
* @param query 查询
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> getList(Qo<T> query) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param entity 实体本身
|
||||
* @param pageable 分页
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Page<T>> getPageList(T entity, Pageable pageable) {
|
||||
return repository.findAll(Example.of(entity), pageable.getSort())
|
||||
.buffer(pageable.getPageSize(), (int) pageable.getOffset())
|
||||
.reduce(new PageImpl<>(new ArrayList<>()), (result, item) -> {
|
||||
List<T> list = result.getContent();
|
||||
list.addAll(item);
|
||||
return new PageImpl<>(list, pageable, result.getTotalElements());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询列表
|
||||
*
|
||||
* @param query 查询实体
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Page<T>> getPageList(Qo<T> query) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有对象
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> getAll() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @return 总数
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> countAll() {
|
||||
return repository.count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询总记录数
|
||||
*
|
||||
* @param entity 通过实体查询个数
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> count(T entity) {
|
||||
return repository.count(Example.of(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入新记录
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> create(T entity) {
|
||||
return repository.insert(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 不插入null字段
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> createSelective(T entity) {
|
||||
return repository.insert(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> delete(T entity) {
|
||||
return repository.delete(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据Id删除
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> deleteById(String id) {
|
||||
return repository.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id更新
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> updateById(T entity) {
|
||||
return repository.save(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不update null
|
||||
*
|
||||
* @param entity 实体
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> updateSelectiveById(T entity) {
|
||||
Assert.hasText(entity.getId(), "更新的主键不可为空!");
|
||||
Mono<T> saved = repository.findById(entity.getId());
|
||||
// Assert.isTrue(saved.isPresent(), "要更新的信息不存在!");
|
||||
return repository.saveAll(saved.filter(Objects::nonNull)
|
||||
.map(t -> CopyUtils.copyProps(entity, t)).flux()).single();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID集合批量删除
|
||||
*
|
||||
* @param ids id结合
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> deleteBatchByIds(List<String> ids) {
|
||||
return Flux.fromIterable(ids)
|
||||
.flatMap(t -> repository.deleteById(t))
|
||||
.single();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新
|
||||
*
|
||||
* @param entities 要批量更新的集合
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> updateBatch(List<T> entities) {
|
||||
// Assert.notEmpty(entities, "数据不可为空!");
|
||||
// entities.forEach(item -> Assert.hasText(item.getId(), "每个对象的id必须附带!"));
|
||||
// // 过滤不存在的记录
|
||||
// Map<String, T> savedMap = ((List<T>) repository.findAllById(entities.stream()
|
||||
// .map(Domain::getId).collect(Collectors.toList()))).stream()
|
||||
// .collect(Collectors.toMap(Domain::getId, t -> t));
|
||||
// List<T> updating = entities.stream()
|
||||
// .filter(t -> savedMap.containsKey(t.getId()))
|
||||
// .map(t -> CopyUtils.copyProps(savedMap.get(t.getId()), t))
|
||||
// .collect(Collectors.toList());
|
||||
return repository.saveAll(entities);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user