初始化项目,集成简单的东西

This commit is contained in:
wangyu 2020-04-09 10:17:14 +08:00
parent 866aec160f
commit 0cbf0bbe24
108 changed files with 7846 additions and 18 deletions

83
.gitignore vendored
View File

@ -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
View File

@ -0,0 +1,8 @@
# FlyFish快速开发框架
经过长期积累后实现的快速开发框架,可用于任何业务快速开发上线
兼容各种场景和复杂的封装
# 框架兼容性
基于JDK1.8 +
基于MongoDB可动态切换Mysql
基于模块化CRUD

64
flyfish-common/pom.xml Normal file
View 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>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -0,0 +1,11 @@
package com.flyfish.framework.constant;
/**
* 异步接口常量
*
* @author wangyu
*/
public interface ReactiveConstants {
String USE_REACTIVE = "Reactive=1";
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -0,0 +1,16 @@
package com.flyfish.framework.enums;
/**
* 基于标识码的枚举基类
*
* @author wangyu
*/
public interface CodeBasedEnum {
/**
* 获取枚举对象标识码
*
* @return 枚举标识
*/
int getCode();
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 &gt; 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 &gt; 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);
}
}
}

View File

@ -0,0 +1,47 @@
package com.flyfish.framework.utils;
/**
* 实体类相关工具类
* 解决问题 1快速对实体的常驻字段crtUsercrtHostupdUser等值快速注入
*
* @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);
}
}

View File

@ -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() + "]");
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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
View 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>

View File

@ -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);
}

View File

@ -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());
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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"));
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
package com.flyfish.framework.domain.base;
/**
* 基础的DTO
*
* @param <T> 泛型
*/
public interface Dto<T> {
}

View File

@ -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();
}

View File

@ -0,0 +1,9 @@
package com.flyfish.framework.domain.base;
/**
* 持久层模型
*
* @author Mr.Wang
*/
public interface Po {
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -0,0 +1,9 @@
package com.flyfish.framework.domain.base;
/**
* 基础的视图模型
*
* @param <T> 泛型
*/
public interface Vo<T> {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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<>();
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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
View 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>

View File

@ -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;
}

View File

@ -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 "";
}

View File

@ -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 {};
}

View File

@ -0,0 +1,14 @@
package com.flyfish.framework.beans.resolver;
import org.springframework.stereotype.Component;
/**
* 动态Bean生成解析器
*
* @author wangyu
*/
@Component
public class DynamicRestBeanResolver {
}

View File

@ -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);
};
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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时校验jwtredis也需要存在
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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}));
});
}
}

View File

@ -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));
}
}

View File

@ -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);
});
}
}

View File

@ -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();
}
}

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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(""))));
}
}

View File

@ -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()))
)));
}
}

View File

@ -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()))
));
}
}

View File

@ -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())));
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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