diff --git a/.gitignore b/.gitignore index a1c2a23..e090081 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5621df --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# FlyFish快速开发框架 +经过长期积累后实现的快速开发框架,可用于任何业务快速开发上线 +兼容各种场景和复杂的封装 + +# 框架兼容性 +基于JDK1.8 + +基于MongoDB(可动态切换Mysql) +基于模块化CRUD \ No newline at end of file diff --git a/flyfish-common/pom.xml b/flyfish-common/pom.xml new file mode 100644 index 0000000..ae28dd3 --- /dev/null +++ b/flyfish-common/pom.xml @@ -0,0 +1,64 @@ + + + + flyfish-framework + com.flyfish.framework + 0.0.1-SNAPSHOT + + 4.0.0 + + flyfish-common + + + + + + org.apache.commons + commons-lang3 + + + + org.apache.commons + commons-collections4 + + + + org.springframework.data + spring-data-commons + + + + + commons-io + commons-io + + + + org.springframework + spring-context + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + io.projectreactor + reactor-core + + + javax.validation + validation-api + + + org.slf4j + slf4j-api + + + + \ No newline at end of file diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/BaseResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/BaseResponse.java new file mode 100644 index 0000000..a3a803f --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/BaseResponse.java @@ -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; + } + + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/ListRestResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/ListRestResponse.java new file mode 100644 index 0000000..958b47b --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/ListRestResponse.java @@ -0,0 +1,59 @@ +package com.flyfish.framework.bean; + +/** + * ${DESCRIPTION} + * + * @author wanghaobin + * @create 2017-06-09 7:32 + */ +public class ListRestResponse { + 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; + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/ObjectRestResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/ObjectRestResponse.java new file mode 100644 index 0000000..7cb37f3 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/ObjectRestResponse.java @@ -0,0 +1,40 @@ +package com.flyfish.framework.bean; + +/** + * Created by Wangyu on 2017/6/11. + */ +public class ObjectRestResponse 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; + } + + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/PageBean.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/PageBean.java new file mode 100644 index 0000000..5854713 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/PageBean.java @@ -0,0 +1,60 @@ +package com.flyfish.framework.bean; + +import org.springframework.data.domain.Page; + +/** + * 分页实体的bean + * + * @param 泛型 + */ +public class PageBean { + + private Integer page; + + private Long totalCount; + + private Integer totalPages; + + private Integer size; + + public PageBean(Page 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; + } +} \ No newline at end of file diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/PageResult.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/PageResult.java new file mode 100644 index 0000000..94fd00b --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/PageResult.java @@ -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 extends Result> { + + public static final Pageable DEFAULT_PAGE = PageRequest.of(0, 20); + + private PageBean page; + + private Integer count; + + public PageResult(String errCode, String errMsg, Page 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; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/Result.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/Result.java new file mode 100644 index 0000000..6020615 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/Result.java @@ -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 { + + 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 Result accept(T data) { + if (null == data) { + return new Result<>(FAIL, MSG_NO_DATA); + } + return new Result<>(SUCCESS, MSG_SUCCESS, data); + } + + /** + * 单个元素,附带分页信息,分页数据写死 + * + * @param data 数据 + * @param 泛型 + * @return 结果 + */ + public static Result> single(T data) { + Page page = new PageImpl<>(Collections.singletonList(data), PageResult.DEFAULT_PAGE, 1); + return accept(page); + } + + public static Result> accept(List data, Pageable pageable, int total) { + Page page = new PageImpl<>(data, pageable, total); + return accept(page); + } + + public static Result> accept(Page page) { + if (null == page) { + return new PageResult<>(FAIL, MSG_NO_DATA); + } + return new PageResult<>(SUCCESS, MSG_SUCCESS, page); + } + + public static Result error() { + return new Result<>(FAIL, MSG_FAIL, null); + } + + public static Result ok() { + return empty(); + } + + public static Result ok(T data) { + return new Result<>(SUCCESS, MSG_SUCCESS, data); + } + + public static Result empty() { + return new Result<>(SUCCESS, MSG_SUCCESS, null); + } + + public static Result> emptyList() { + return new Result<>(SUCCESS, MSG_SUCCESS, Collections.emptyList()); + } + + public static Result> emptyPage() { + return new PageResult<>(SUCCESS, MSG_SUCCESS, Page.empty()); + } + + public static Result notFound() { + return new Result<>(FAIL, MSG_NO_DATA, null); + } + + public static Result error(String errMsg) { + return new Result<>(FAIL, errMsg, null); + } + + public Result toVoid() { + return (Result) this; + } + + public Optional optional() { + return isSuccess() ? Optional.ofNullable(data) : Optional.empty(); + } + + public Result map(Function function) { + if (this.data != null) { + this.data = (T) function.apply(this.data); + return (Result) this; + } + return (Result) this; + } + + public Result orElse(Result other) { + if (null == this.data) { + return other; + } + return this; + } + + public Result filter(Function 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; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/TableResultResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/TableResultResponse.java new file mode 100644 index 0000000..cb930f2 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/TableResultResponse.java @@ -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 extends BaseResponse { + + TableData data; + + public TableResultResponse(long total, List rows) { + this.data = new TableData(total, rows); + } + + public TableResultResponse() { + this.data = new TableData(); + } + + TableResultResponse total(int total) { + this.data.setTotal(total); + return this; + } + + TableResultResponse total(List rows) { + this.data.setRows(rows); + return this; + } + + public TableData getData() { + return data; + } + + public void setData(TableData data) { + this.data = data; + } + + class TableData { + long total; + List rows; + + public TableData(long total, List rows) { + this.total = total; + this.rows = rows; + } + + public TableData() { + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public List getRows() { + return rows; + } + + public void setRows(List rows) { + this.rows = rows; + } + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/TreeNode.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/TreeNode.java new file mode 100644 index 0000000..a7ca24a --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/TreeNode.java @@ -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 children = new ArrayList(); + + public List getChildren() { + return children; + } + + public void setChildren(List 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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenErrorResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenErrorResponse.java new file mode 100644 index 0000000..a09f577 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenErrorResponse.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenForbiddenResponse.java b/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenForbiddenResponse.java new file mode 100644 index 0000000..a950239 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/bean/auth/TokenForbiddenResponse.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/constant/CommonConstants.java b/flyfish-common/src/main/java/com/flyfish/framework/constant/CommonConstants.java new file mode 100644 index 0000000..617d704 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/constant/CommonConstants.java @@ -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"; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/constant/ReactiveConstants.java b/flyfish-common/src/main/java/com/flyfish/framework/constant/ReactiveConstants.java new file mode 100644 index 0000000..fefbc68 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/constant/ReactiveConstants.java @@ -0,0 +1,11 @@ +package com.flyfish.framework.constant; + +/** + * 异步接口常量 + * + * @author wangyu + */ +public interface ReactiveConstants { + + String USE_REACTIVE = "Reactive=1"; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/constant/RestCodeConstants.java b/flyfish-common/src/main/java/com/flyfish/framework/constant/RestCodeConstants.java new file mode 100644 index 0000000..0eefbcb --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/constant/RestCodeConstants.java @@ -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; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/constant/UserConstant.java b/flyfish-common/src/main/java/com/flyfish/framework/constant/UserConstant.java new file mode 100644 index 0000000..2584898 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/constant/UserConstant.java @@ -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; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/context/DateContext.java b/flyfish-common/src/main/java/com/flyfish/framework/context/DateContext.java new file mode 100644 index 0000000..aff4623 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/context/DateContext.java @@ -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 formatThreadLocal = new ThreadLocal<>(); + private ThreadLocal> 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 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; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/enums/CodeBasedEnum.java b/flyfish-common/src/main/java/com/flyfish/framework/enums/CodeBasedEnum.java new file mode 100644 index 0000000..948f289 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/enums/CodeBasedEnum.java @@ -0,0 +1,16 @@ +package com.flyfish.framework.enums; + +/** + * 基于标识码的枚举基类 + * + * @author wangyu + */ +public interface CodeBasedEnum { + + /** + * 获取枚举对象标识码 + * + * @return 枚举标识 + */ + int getCode(); +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/enums/RoleType.java b/flyfish-common/src/main/java/com/flyfish/framework/enums/RoleType.java new file mode 100644 index 0000000..188b5e1 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/enums/RoleType.java @@ -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; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/enums/UserStatus.java b/flyfish-common/src/main/java/com/flyfish/framework/enums/UserStatus.java new file mode 100644 index 0000000..5b4e65c --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/enums/UserStatus.java @@ -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; +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/enums/UserType.java b/flyfish-common/src/main/java/com/flyfish/framework/enums/UserType.java new file mode 100644 index 0000000..bb6d4ae --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/enums/UserType.java @@ -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 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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/BaseException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/BaseException.java new file mode 100644 index 0000000..08436e0 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/BaseException.java @@ -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; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientForbiddenException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientForbiddenException.java new file mode 100644 index 0000000..3e5623f --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientForbiddenException.java @@ -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); + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientInvalidException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientInvalidException.java new file mode 100644 index 0000000..79b7019 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientInvalidException.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientTokenException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientTokenException.java new file mode 100644 index 0000000..778bb85 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/ClientTokenException.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserInvalidException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserInvalidException.java new file mode 100644 index 0000000..2f542b1 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserInvalidException.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserTokenException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserTokenException.java new file mode 100644 index 0000000..1755f58 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/auth/UserTokenException.java @@ -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); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/DataLackException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/DataLackException.java new file mode 100644 index 0000000..7d2f49c --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/DataLackException.java @@ -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 supplier(String message) { + return () -> msg(message); + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/InvalidBusinessException.java b/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/InvalidBusinessException.java new file mode 100644 index 0000000..af05606 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/exception/biz/InvalidBusinessException.java @@ -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; + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/Assert.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/Assert.java new file mode 100644 index 0000000..1cf7fd3 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/Assert.java @@ -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}. + *
Assert.isTrue(i > 0, "The value must be greater than zero");
+ * + * @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}. + *
Assert.isTrue(i > 0);
+ * + * @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} . + *
Assert.isNull(value, "The value must be null");
+ * + * @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} . + *
Assert.isNull(value);
+ * + * @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} . + *
Assert.notNull(clazz, "The class must not be null");
+ * + * @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} . + *
Assert.notNull(clazz);
+ * + * @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. + *
Assert.hasLength(name, "Name must not be empty");
+ * + * @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. + *
Assert.hasLength(name);
+ * + * @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. + *
Assert.hasText(name, "'name' must not be empty");
+ * + * @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. + *
Assert.hasText(name, "'name' must not be empty");
+ * + * @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. + *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
+ * + * @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. + *
Assert.doesNotContain(name, "rod");
+ * + * @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. + *
Assert.notEmpty(array, "The array must have elements");
+ * + * @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. + *
Assert.notEmpty(array);
+ * + * @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! + *
Assert.noNullElements(array, "The array must have non-null elements");
+ * + * @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! + *
Assert.noNullElements(array);
+ * + * @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. + *
Assert.notEmpty(collection, "Collection must have elements");
+ * + * @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. + *
Assert.notEmpty(collection, "Collection must have elements");
+ * + * @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. + *
Assert.notEmpty(map, "Map must have entries");
+ * + * @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. + *
Assert.notEmpty(map);
+ * + * @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. + *
Assert.instanceOf(Foo.class, foo);
+ * + * @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. + *
Assert.instanceOf(Foo.class, foo);
+ * + * @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}. + *
Assert.isAssignable(Number.class, myClass);
+ * + * @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}. + *
Assert.isAssignable(Number.class, myClass);
+ * + * @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. + *
Assert.state(id == null, "The id property must not already be initialized");
+ * + * @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}. + *

Call {@link #isTrue(boolean)} if you wish to + * throw {@link InvalidBusinessException} on an assertion failure. + *

Assert.state(id == null);
+ * + * @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); + } + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/EntityUtils.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/EntityUtils.java new file mode 100644 index 0000000..da6ef8e --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/EntityUtils.java @@ -0,0 +1,47 @@ +package com.flyfish.framework.utils; + +/** + * 实体类相关工具类 + * 解决问题: 1、快速对实体的常驻字段,如:crtUser、crtHost、updUser等值快速注入 + * + * @author Wangyu + * @version 1.0 + * @date 2016年4月18日 + * @since 1.7 + */ +public class EntityUtils { + + /** + * 依据对象的属性数组和值数组对对象的属性进行赋值 + * + * @param entity 对象 + * @param fields 属性数组 + * @param value 值数组 + * @author 王浩彬 + */ + private static 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 boolean isPKNotNull(T entity, String field) { + if (!ReflectionUtils.hasField(entity, field)) { + return false; + } + Object value = ReflectionUtils.getFieldValue(entity, field); + return value != null && !"".equals(value); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/EnumUtils.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/EnumUtils.java new file mode 100644 index 0000000..eee1be2 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/EnumUtils.java @@ -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 枚举泛型 + * @return 枚举对象 + * @throws NoSuchElementException 找不到指定元素 + */ + public static & CodeBasedEnum> E getByCode(int code, Class enumClass) throws NoSuchElementException { + E[] enumConstants = enumClass.getEnumConstants(); + for (E e : enumConstants) { + if (e.getCode() == code) { + return e; + } + } + throw new NoSuchElementException("找不到标识码[" + code + "]对应的枚举对象: [" + enumClass.getSimpleName() + "]"); + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/JacksonUtil.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/JacksonUtil.java new file mode 100644 index 0000000..711335f --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/JacksonUtil.java @@ -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 fromJson(final String json) { + return readValue(json, new TypeReference() { + }); + } + + public static T fromJson(final String json, TypeReference reference) { + return readValue(json, reference); + } + + public static T fromJson(final String json, Class 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 Map json2Map(String json) { + return readValue(json, new TypeReference>() { + }); + } + + public static List json2List(final String json) { + return readValue(json, new TypeReference>() { + }); + } + + public static List json2List(final String json, Class 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 toJson(final Object obj) { + try { + return Optional.of(mapper.writeValueAsString(obj)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return Optional.empty(); + } + + private static T readValue(String json, TypeReference valueTypeRef) { + if (null == json || "".equals(json)) { + return null; + } + try { + return mapper.readValue(json, valueTypeRef); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/ReflectionUtils.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/ReflectionUtils.java new file mode 100644 index 0000000..b86c398 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/ReflectionUtils.java @@ -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, 并强制设置为可访问. + *

+ * 如向上转型到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. + * 匹配函数名+参数类型。 + *

+ * 用于方法需要被多次调用的情况. 先使用本函数先取得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. + * 只匹配函数名。 + *

+ * 用于方法需要被多次调用的情况. 先使用本函数先取得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 + * + * @param clazz The class to introspect + * @return the first generic declaration, or Object.class if cannot be determined + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + *

+ * 如public UserDao extends HibernateDao + * + * @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; + + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java new file mode 100644 index 0000000..59a58ab --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/StringHelper.java @@ -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(); + } +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/TreeUtil.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/TreeUtil.java new file mode 100644 index 0000000..0241ede --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/TreeUtil.java @@ -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 List bulid(List treeNodes, Object root) { + + List trees = new ArrayList(); + + 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.add(it); + } + } + } + return trees; + } + + /** + * 使用递归方法建树 + * + * @param treeNodes + * @return + */ + public static List buildByRecursive(List treeNodes, Object root) { + List trees = new ArrayList(); + for (T treeNode : treeNodes) { + if (root.equals(treeNode.getParentId())) { + trees.add(findChildren(treeNode, treeNodes)); + } + } + return trees; + } + + /** + * 递归查找子节点 + * + * @param treeNodes + * @return + */ + public static T findChildren(T treeNode, List treeNodes) { + for (T it : treeNodes) { + if (treeNode.getId() == it.getParentId()) { + if (treeNode.getChildren() == null) { + treeNode.setChildren(new ArrayList()); + } + treeNode.add(findChildren(it, treeNodes)); + } + } + return treeNode; + } + +} diff --git a/flyfish-common/src/main/java/com/flyfish/framework/utils/UUIDUtils.java b/flyfish-common/src/main/java/com/flyfish/framework/utils/UUIDUtils.java new file mode 100644 index 0000000..de6f803 --- /dev/null +++ b/flyfish-common/src/main/java/com/flyfish/framework/utils/UUIDUtils.java @@ -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(); + + } +} diff --git a/flyfish-data/pom.xml b/flyfish-data/pom.xml new file mode 100644 index 0000000..cde41ee --- /dev/null +++ b/flyfish-data/pom.xml @@ -0,0 +1,32 @@ + + + + flyfish-framework + com.flyfish.framework + 0.0.1-SNAPSHOT + + 4.0.0 + + flyfish-data + + + + com.flyfish.framework + flyfish-common + ${project.version} + + + + org.springframework.data + spring-data-mongodb + + + + org.springframework.boot + spring-boot-starter-data-mongodb-reactive + + + + \ No newline at end of file diff --git a/flyfish-data/src/main/java/com/flyfish/framework/auditor/BeanAuditor.java b/flyfish-data/src/main/java/com/flyfish/framework/auditor/BeanAuditor.java new file mode 100644 index 0000000..b381493 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/auditor/BeanAuditor.java @@ -0,0 +1,20 @@ +package com.flyfish.framework.auditor; + +import com.flyfish.framework.domain.base.Domain; + +/** + * 负责补全或者生成默认值(仅针对新建) + * + * @author wybab + */ +public interface BeanAuditor { + + /** + * 对实体进行审查,并补全相关字段 + * + * @param data 原数据 + * @return 结果 + */ + void audit(T data); + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/auditor/OperationAuditor.java b/flyfish-data/src/main/java/com/flyfish/framework/auditor/OperationAuditor.java new file mode 100644 index 0000000..3912c35 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/auditor/OperationAuditor.java @@ -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 { + + 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()); + } + } + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/auditor/UserAuditor.java b/flyfish-data/src/main/java/com/flyfish/framework/auditor/UserAuditor.java new file mode 100644 index 0000000..44db7d3 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/auditor/UserAuditor.java @@ -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 { + + private final UserContext userContext; + + /** + * 在异步上下文中获取结果 + * + * @return 结果 + */ + @Override + public Optional getCurrentAuditor() { + return Optional.ofNullable(userContext.currentUser()) + .map(Domain::getName); + } + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/builder/CriteriaBuilder.java b/flyfish-data/src/main/java/com/flyfish/framework/builder/CriteriaBuilder.java new file mode 100644 index 0000000..706d296 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/builder/CriteriaBuilder.java @@ -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 { + + private Qo qo; + + private Map> functionMap = new HashMap<>(); + + private Map keyMapper = new HashMap<>(); + + /** + * 构造器,接受一个qo + * + * @param qo 查询实体 + * @param 泛型,实体 + * @return 结果 + */ + public static CriteriaBuilder accept(Qo qo) { + CriteriaBuilder builder = new CriteriaBuilder<>(); + builder.qo = qo; + return builder; + } + + /** + * 添加策略 + * + * @param key 键 + * @param function 方法 + * @param field 域的键值 + * @return 结果 + */ + public CriteriaBuilder with(String key, String field, BiFunction function) { + this.functionMap.putIfAbsent(key, function); + keyMapper.putIfAbsent(key, field); + return this; + } + + /** + * 添加策略 + * + * @param key 键 + * @param function 方法 + * @return 结果 + */ + public CriteriaBuilder with(String key, BiFunction function) { + this.functionMap.putIfAbsent(key, function); + return this; + } + + /** + * 添加策略 + * + * @param key 键 + * @return 结果 + */ + public CriteriaBuilder with(String... key) { + Arrays.stream(key).forEach(item -> this.functionMap.putIfAbsent(item, Criteria::is)); + return this; + } + + /** + * 批量导入规则 + * + * @param criteriaDescriptors 规则集合 + * @return 结果 + */ + public CriteriaBuilder with(List criteriaDescriptors) { + criteriaDescriptors.forEach(criteriaDescriptor -> + this.with(criteriaDescriptor.getKey(), criteriaDescriptor.getMapper())); + return this; + } + + public Criteria build() { + if (!CollectionUtils.isEmpty(functionMap)) { + // 键集合 + Set 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 collection = (Collection) 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 LIKE = (criteria, o) -> criteria.regex((String) o); + + // 精确匹配 + BiFunction IS = Criteria::is; + + // 不等 + BiFunction NE = Criteria::ne; + + // 包含匹配 + BiFunction IN = (criteria, o) -> o instanceof Collection ? criteria.in(((Collection) o)) : criteria.in(o); + + // 不包含匹配 + BiFunction NIN = (criteria, o) -> o instanceof Collection ? criteria.nin(((Collection) o)) : criteria.nin(o); + + // 不为空 + BiFunction NOT_NULL = ((criteria, o) -> criteria.ne(null)); + + // 范围匹配 + BiFunction RANGE = (criteria, o) -> { + if (o instanceof List) { + List list = (List) o; + return criteria.gte(list.get(0)).lte(list.get(1)); + } + return criteria; + }; + + // 时间起始 + BiFunction DATE_GTE = (criteria, o) -> criteria.gte(DateContext.parse(o)); + + // 时间中止 + BiFunction DATE_LTE = (criteria, o) -> criteria.lte(DateContext.parse(o)); + + // 日期范围包含 + @SuppressWarnings("unchecked") + BiFunction DATE_RANGE = (criteria, o) -> { + if (o instanceof List) { + List list = (List) 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 mapper; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/context/UserContext.java b/flyfish-data/src/main/java/com/flyfish/framework/context/UserContext.java new file mode 100644 index 0000000..9d5f6f7 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/context/UserContext.java @@ -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 userThreadLocal = new ThreadLocal<>(); + + public UserContext() { + instance = this; + } + + public static Optional sharedContext() { + return Optional.ofNullable(instance); + } + + public User currentUser() { + return userThreadLocal.get(); + } + + public void setUser(User user) { + userThreadLocal.set(user); + } + + public void clear() { + userThreadLocal.remove(); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/AuditDomain.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/AuditDomain.java new file mode 100644 index 0000000..80977de --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/AuditDomain.java @@ -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; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/BaseQo.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/BaseQo.java new file mode 100644 index 0000000..b43ea8d --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/BaseQo.java @@ -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 implements Qo { + + protected Pageable pageable; + + protected List result; + + protected User user; + + public Qo accept(List result, Pageable pageable) { + this.pageable = pageable; + this.result = result; + return this; + } + + public Qo 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 getResult() { + return result; + } + + /** + * 设置结果集,用于单例部署时快速封装 + * + * @param result 查询结果集 + */ + @JsonIgnore + @Override + public void setResult(List result) { + this.result = result; + } + + /** + * 定义查询规则的方法 + * + * @return 包含匹配规则的example + */ + @JsonIgnore + @Override + public Example getExample() { + return null; + } + + /** + * 获取data-mongo的对象Criteria + * + * @return 结果 + */ + @Override + public Criteria getCriteria() { + CriteriaBuilder criteriaBuilder = criteriaBuilder(); + if (null != criteriaBuilder) { + return criteriaBuilder.build(); + } + return null; + } + + /** + * 内置的builder,重写后自动合并结果 + * + * @return 结果 + */ + public CriteriaBuilder criteriaBuilder() { + return null; + } + + /** + * 让值全部包含在Pojo里 + * + * @return 结果 + */ + @Override + @SuppressWarnings("unchecked") + public Class pojoType() { + ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass(); + return (Class) type.getActualTypeArguments()[0]; + } + + + @Override + public Sort sorts() { + return Sort.by(Sort.Order.desc("createTime")); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Domain.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Domain.java new file mode 100644 index 0000000..e31e17a --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Domain.java @@ -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; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Dto.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Dto.java new file mode 100644 index 0000000..de64ba2 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Dto.java @@ -0,0 +1,11 @@ +package com.flyfish.framework.domain.base; + +/** + * 基础的DTO + * + * @param 泛型 + */ +public interface Dto { + + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/IUser.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/IUser.java new file mode 100644 index 0000000..809c003 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/IUser.java @@ -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 getDepartments(); + + void setDepartments(List departments); + + List getRoles(); + + void setRoles(List roles); + + String getOpenId(); + + void setOpenId(String openId); + + Object getDetail(); + + void setDetail(Object detail); + + User toUser(); +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Po.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Po.java new file mode 100644 index 0000000..f300247 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Po.java @@ -0,0 +1,9 @@ +package com.flyfish.framework.domain.base; + +/** + * 持久层模型 + * + * @author Mr.Wang + */ +public interface Po { +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Qo.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Qo.java new file mode 100644 index 0000000..c8110e0 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Qo.java @@ -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 泛型 + */ +public interface Qo { + + /** + * 获取分页对象 + * + * @return 结果 + */ + Pageable getPageable(); + + /** + * 设置分页对象 + * + * @param pageable 分页对象 + */ + @JsonIgnore + void setPageable(Pageable pageable); + + /** + * 获取当前用户 + * + * @return 结果 + */ + User getUser(); + + /** + * 当前用户 + */ + void setUser(User user); + + /** + * 获取结果集,用于单例部署时快速封装 + * + * @return 结果 + */ + List getResult(); + + /** + * 设置结果集,用于单例部署时快速封装 + * + * @param result 查询结果集 + */ + @JsonIgnore + void setResult(List result); + + /** + * 获取jpa的example对象 + * + * @return 结果 + */ + @JsonIgnore + Example getExample(); + + /** + * 获取data-mongo的对象Predicate + * + * @return 结果 + */ + @JsonIgnore + Criteria getCriteria(); + + /** + * 让值全部包含在Pojo里 + * + * @return 结果 + */ + Class pojoType(); + + /** + * 排序字段,默认createTime + * + * @return 结果 + */ + Sort sorts(); + + /** + * 判断查询是否为空 + * + * @return 结果 + */ + default boolean isEmpty() { + Criteria criteria = getCriteria(); + Example example = getExample(); + return example == null && (null == criteria.getCriteriaObject() || criteria.getCriteriaObject().size() == 0); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/TreeDomain.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/TreeDomain.java new file mode 100644 index 0000000..53a4fc9 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/TreeDomain.java @@ -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> extends AuditDomain { + + // 父id,顶级是0 + private String parentId; + + // 深度,遍历标识 + private Integer depth; + + // 冗余的字段,用来放儿子 + @Transient + private List children; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Vo.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Vo.java new file mode 100644 index 0000000..d5b420d --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/base/Vo.java @@ -0,0 +1,9 @@ +package com.flyfish.framework.domain.base; + +/** + * 基础的视图模型 + * + * @param 泛型 + */ +public interface Vo { +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Department.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Department.java new file mode 100644 index 0000000..b6f9e7b --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Department.java @@ -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; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Permission.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Permission.java new file mode 100644 index 0000000..2e43532 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Permission.java @@ -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 { + + public static final Permission ROOT; + + static { + ROOT = new Permission(); + ROOT.setId("0"); + ROOT.setDepth(0); + } + + /** + * 是否是管理员权限 + */ + private boolean admin; + + /** + * 是否是叶子节点 + */ + private Boolean leaf; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Role.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Role.java new file mode 100644 index 0000000..d2bc503 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/Role.java @@ -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 permissions; +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java new file mode 100644 index 0000000..b2684a2 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/domain/po/User.java @@ -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 departments; + + /** + * 所属角色 + */ + @DBRef + private List roles; + + /** + * 微信openId + */ + @Indexed(unique = true) + private String openId; + + /** + * 查询冗余,标记用户信息 + */ + @Transient + private Object detail; + + @Override + public User toUser() { + return this; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultReactiveRepository.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultReactiveRepository.java new file mode 100644 index 0000000..5a66618 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultReactiveRepository.java @@ -0,0 +1,15 @@ +package com.flyfish.framework.repository; + +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import org.springframework.data.repository.NoRepositoryBean; + + +/** + * 默认的持久层dao + * + * @param 泛型 + */ +@NoRepositoryBean +public interface DefaultReactiveRepository extends ReactiveMongoRepository { + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultRepository.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultRepository.java new file mode 100644 index 0000000..ac5dae2 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/DefaultRepository.java @@ -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 extends MongoRepository, QueryModelExecutor { + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/QueryModelExecutor.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/QueryModelExecutor.java new file mode 100644 index 0000000..438328a --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/QueryModelExecutor.java @@ -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 { + + /** + * 通过名称查找一个 + * + * @param name 名称 + * @return 结果 + */ + Optional 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 findOne(Qo 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 findAll(Qo 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 findAll(Qo 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 findAll(Qo 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 query); + + /** + * 通过特定键的集合查询 + * + * @param key 键 + * @param values 集合 + * @return 结果 + */ + List findAllByValues(String key, List 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 query); +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactory.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactory.java new file mode 100644 index 0000000..76ffb5a --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactory.java @@ -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; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactoryBean.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactoryBean.java new file mode 100644 index 0000000..7d293b9 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryFactoryBean.java @@ -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, S> + extends MongoRepositoryFactoryBean { + + /** + * Creates a new {@link MongoRepositoryFactoryBean} for the given repository interface. + * + * @param repositoryInterface must not be {@literal null}. + */ + public DefaultRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + + @Override + protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) { + return new DefaultRepositoryFactory(operations); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryImpl.java b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryImpl.java new file mode 100644 index 0000000..1a36a30 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/repository/impl/DefaultRepositoryImpl.java @@ -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 extends SimpleMongoRepository + implements DefaultRepository { + + private MongoOperations mongoOperations; + private MongoEntityInformation 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 metadata, MongoOperations mongoOperations) { + super(metadata, mongoOperations); + this.mongoOperations = mongoOperations; + this.entityInformation = metadata; + } + + /** + * 通过名称查找一个 + * + * @param name 名称 + * @return 结果 + */ + @Override + public Optional 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 findOne(Qo 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 findAll(Qo 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 findAll(Qo 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 findAll(Qo query, Pageable pageable) { + Query querying = getQuery(query); + if (null != querying) { + querying.with(pageable); + List 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 query) { + Query q = getQuery(query); + return this.mongoOperations.count(q, entityInformation.getJavaType(), entityInformation.getCollectionName()); + } + + /** + * 通过特定键的集合查询 + * + * @param key 键 + * @param values 集合 + * @return 结果 + */ + @Override + public List findAllByValues(String key, List 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 query) { + return false; + } + + /** + * 从查询实体抽取内部查询信息 + * + * @param qo 查询实体 + * @return 结果 + */ + private Query getQuery(Qo qo) { + Criteria criteria = null; + if (null != qo.getCriteria()) { + criteria = qo.getCriteria(); + } else if (null != qo.getExample()) { + criteria = new Criteria().alike(qo.getExample()); + } else { + Class 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; + } + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/CopyUtils.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/CopyUtils.java new file mode 100644 index 0000000..4d1d1fb --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/CopyUtils.java @@ -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 泛型 + * @param 泛型(查询) + * @return 结果 + */ + public static > T copyQueryProps(Q query, T destination) { + Map 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 泛型 + */ + public static 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 getWriteMethod(Object target, String propertyName) { + return Optional.ofNullable(BeanUtils.getPropertyDescriptor(target.getClass(), propertyName)) + .map(PropertyDescriptor::getWriteMethod); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/CriteriaUtils.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/CriteriaUtils.java new file mode 100644 index 0000000..bc35188 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/CriteriaUtils.java @@ -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; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java new file mode 100644 index 0000000..13bc69a --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/DataUtils.java @@ -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 泛型 + * @return 结果 + */ + @SuppressWarnings("deprecation") + public static T ref(Class 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 泛型 + * @return 结果 + */ + @SuppressWarnings("unchecked") + public static T ref(T instance) { + Class tClass = (Class) instance.getClass(); + return ref(tClass, instance.getId()); + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/DateRangeUtil.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/DateRangeUtil.java new file mode 100644 index 0000000..daa218d --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/DateRangeUtil.java @@ -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> getDates(Date start, Date end, Set weeks) { + // 获取日历 + Calendar calendar = Calendar.getInstance(); + calendar.setTime(start); + // 构建结果集 + Map> 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 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 getMonthBetween(String minDate, String maxDate) { + try { + ArrayList 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 getRangeOfMonth(String month) { + try { + ArrayList 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<>(); + } + } + +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/FieldUtils.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/FieldUtils.java new file mode 100644 index 0000000..666eab7 --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/FieldUtils.java @@ -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 complete(T value, Supplier supplier) { + return ObjectUtils.isEmpty(value) ? supplier.get() : value; + } + + public static T complete(T value, T defaultValue) { + return ObjectUtils.isEmpty(value) ? defaultValue : value; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/Query.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/Query.java new file mode 100644 index 0000000..afb659a --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/Query.java @@ -0,0 +1,46 @@ +package com.flyfish.framework.utils; + + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 查询参数 + */ +public class Query extends LinkedHashMap { + private static final long serialVersionUID = 1L; + //当前页码 + private int page = 1; + //每页条数 + private int limit = 10; + + public Query(Map 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; + } +} diff --git a/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java b/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java new file mode 100644 index 0000000..59a58ab --- /dev/null +++ b/flyfish-data/src/main/java/com/flyfish/framework/utils/StringHelper.java @@ -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(); + } +} diff --git a/flyfish-web/pom.xml b/flyfish-web/pom.xml new file mode 100644 index 0000000..166a11c --- /dev/null +++ b/flyfish-web/pom.xml @@ -0,0 +1,81 @@ + + + + flyfish-framework + com.flyfish.framework + 0.0.1-SNAPSHOT + + 4.0.0 + + flyfish-web + + + + com.flyfish.framework + flyfish-data + ${project.version} + + + + org.springframework.security + spring-security-core + + + + org.springframework.boot + spring-boot-starter-aop + + + + org.springframework.boot + spring-boot-starter-cache + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + + + \ No newline at end of file diff --git a/flyfish-web/src/main/java/com/flyfish/framework/bean/SyncVo.java b/flyfish-web/src/main/java/com/flyfish/framework/bean/SyncVo.java new file mode 100644 index 0000000..677b549 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/bean/SyncVo.java @@ -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 { + + private int success; + + private int failed; + + private int updated; + + private List list; +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java new file mode 100644 index 0000000..8f7cbff --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestBean.java @@ -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 ""; +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestMapping.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestMapping.java new file mode 100644 index 0000000..2e98b3f --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/annotations/RestMapping.java @@ -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 {}; +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java new file mode 100644 index 0000000..be397aa --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/beans/resolver/DynamicRestBeanResolver.java @@ -0,0 +1,14 @@ +package com.flyfish.framework.beans.resolver; + +import org.springframework.stereotype.Component; + +/** + * 动态Bean生成解析器 + * + * @author wangyu + */ +@Component +public class DynamicRestBeanResolver { + + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java b/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java new file mode 100644 index 0000000..70c30d8 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/config/BeanConfig.java @@ -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); + }; + } + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/RedisConfig.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/RedisConfig.java new file mode 100644 index 0000000..08ed67e --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/RedisConfig.java @@ -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 redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { + // 设置序列化 + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + // 配置redisTemplate + RedisTemplate 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); + } + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/WebfluxConfig.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/WebfluxConfig.java new file mode 100644 index 0000000..e4f3d44 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/WebfluxConfig.java @@ -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)); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/CurrentUser.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/CurrentUser.java new file mode 100644 index 0000000..31ba50d --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/CurrentUser.java @@ -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 { +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/PagedQuery.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/PagedQuery.java new file mode 100644 index 0000000..695614e --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/PagedQuery.java @@ -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 { +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/RequestContextBody.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/RequestContextBody.java new file mode 100644 index 0000000..f07544f --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/annotations/RequestContextBody.java @@ -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 { +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java new file mode 100644 index 0000000..cd11055 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/JwtSecurityContextRepository.java @@ -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 load(ServerWebExchange serverWebExchange) { + ServerHttpRequest request = serverWebExchange.getRequest(); + String jwt = tokenProvider.retrieveToken(serverWebExchange).orElse(null); + URI requestURI = request.getURI(); + // 存在jwt时,校验jwt。redis也需要存在 + if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt) && redisOperations.hasKey(jwt)) { + Authentication authentication = tokenProvider.getAuthentication(jwt); + log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestURI); + return userDetailsService.findByUsername(authentication.getName()) + .flatMap(userDetails -> userDetailsService.loadContext(userDetails)); + } else { + log.debug("no valid JWT token found, uri: {}", requestURI); + return Mono.empty(); + } + } + + /** + * 只有登录会调用这个 + * + * @param webExchange 交换信息 + * @param securityContext 上下文 + * @return 结果 + */ + @Override + public Mono save(ServerWebExchange webExchange, SecurityContext securityContext) { + // 添加jwtToken + tokenProvider.addToken(webExchange, securityContext.getAuthentication()); + return Mono.empty(); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java new file mode 100644 index 0000000..5aafec2 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/jwt/TokenProvider.java @@ -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 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 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 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; + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ModelAttributeFilter.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ModelAttributeFilter.java new file mode 100644 index 0000000..a3083fd --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ModelAttributeFilter.java @@ -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 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 model = context.getModel().asMap(); + MonoProcessor 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; + } + })); + }); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageParameterFilter.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageParameterFilter.java new file mode 100644 index 0000000..5975518 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageParameterFilter.java @@ -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 { + + 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 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 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 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 defaultOrFallback = getDefaultFromAnnotationOrFallback(parameter).toOptional(); + ServerHttpRequest request = exchange.getRequest(); + MultiValueMap params = request.getQueryParams(); + String pageString = params.getFirst(getParameterNameToUse(pageParameterName, parameter)); + String pageSizeString = params.getFirst(getParameterNameToUse(sizeParameterName, parameter)); + + Optional page = parseAndApplyBoundaries(pageString, Integer.MAX_VALUE, true); + Optional 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)); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageQueryArgumentResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageQueryArgumentResolver.java new file mode 100644 index 0000000..6bf9324 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/PageQueryArgumentResolver.java @@ -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 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 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); + }); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ParameterFilter.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ParameterFilter.java new file mode 100644 index 0000000..dd0cb07 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/ParameterFilter.java @@ -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 { + + /** + * 从已有的拦截器解析数据 + * + * @param parameter 方法参数 + * @param bindingContext 上下文 + * @param exchange web数据 + * @return 结果 + */ + T filter(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange); + + default Mono filterMono(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { + return Mono.empty(); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/RequestContextBodyArgumentResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/RequestContextBodyArgumentResolver.java new file mode 100644 index 0000000..817b141 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/RequestContextBodyArgumentResolver.java @@ -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> readers, + ReactiveAdapterRegistry registry) { + + super(readers, registry); + } + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(RequestContextBody.class); + } + + @Override + public Mono 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); + }); + } + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/SortParameterFilter.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/SortParameterFilter.java new file mode 100644 index 0000000..69ee67b --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/SortParameterFilter.java @@ -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 { + + 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 toOrder(String property, Optional 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 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 source, String delimiter) { + + List allOrders = new ArrayList<>(); + + for (String part : source) { + + if (part == null) { + continue; + } + + String[] elements = part.split(delimiter); + + Optional 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); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/UserArgumentResolver.java b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/UserArgumentResolver.java new file mode 100644 index 0000000..6187efa --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/configuration/resolver/UserArgumentResolver.java @@ -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 resolveArgument(MethodParameter methodParameter, BindingContext bindingContext, ServerWebExchange serverWebExchange) { + return ReactiveSecurityContextHolder.getContext() + .map(UserUtils::extractUser); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/controller/BaseController.java b/flyfish-web/src/main/java/com/flyfish/framework/controller/BaseController.java new file mode 100644 index 0000000..58baf63 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/controller/BaseController.java @@ -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> { + + @Autowired + protected BaseService service; + @Autowired(required = false) + protected BaseReactiveService reactiveService; + @Autowired + protected UserContext userContext; + + @SuppressWarnings("unchecked") + public > S getService() { + return (S) service; + } + + @GetMapping("/exists") + public Result exists(@PagedQuery Q qo) { + return Result.accept(service.count(qo) != 0); + } + + @GetMapping("/count") + public Result count(@PagedQuery Q qo) { + return Result.accept(service.count(qo)); + } + + @PostMapping("") + public Result create(@RequestBody @Valid T entity, @CurrentUser User user) { + userContext.setUser(user); + return Result.accept(service.create(entity)); + } + + @GetMapping("{id}") + public Result get(@PathVariable String id) { + return service.getById(id).map(Result::accept).orElse(Result.notFound()); + } + + @PutMapping("{id}") + public Result update(@RequestBody @Valid T entity, @CurrentUser User user) { + userContext.setUser(user); + return Result.accept(service.updateSelectiveById(entity)); + } + + @PutMapping("") + public Result> updateList(@RequestBody List list, @CurrentUser User user) { + userContext.setUser(user); + return Result.accept(service.updateBatch(list)); + } + + @PutMapping("/sync") + public Result> syncList(@RequestBody List list, @CurrentUser User user) { + userContext.setUser(user); + return Result.accept(service.sync(list)); + } + + @DeleteMapping("{id}") + public Result remove(@PathVariable String id) { + service.deleteById(id); + return Result.ok(); + } + + @GetMapping("/all") + public Result> all(@PagedQuery Q qo) { + if (qo.isEmpty()) { + return Result.accept(service.getAll()); + } + return Result.accept(service.getList(qo)); + } + + @GetMapping("") + public Result> 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> reactiveCreate(@RequestBody @Valid T entity) { + return reactiveService.create(entity).map(Result::accept); + } + + @GetMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE) + public Mono> reactiveGet(@PathVariable String id) { + return reactiveService.getById(id) + .map(Result::accept) + .defaultIfEmpty(Result.notFound()); + } + + @PutMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE) + public Mono> reactiveUpdate(@RequestBody @Valid T entity) { + return reactiveService.updateById(entity) + .map(Result::accept) + .defaultIfEmpty(Result.notFound()); + } + + @DeleteMapping(value = "{id}", headers = ReactiveConstants.USE_REACTIVE) + public Mono> reactiveRemove(@PathVariable String id) { + return reactiveService.deleteById(id).map(item -> Result.ok()); + } + + @GetMapping(value = "/all", headers = ReactiveConstants.USE_REACTIVE) + public Mono>> reactiveAll() { + return reactiveService.getAll() + .collectList() + .map(Result::accept) + .defaultIfEmpty(Result.emptyList()); + } + + @GetMapping(value = "", headers = ReactiveConstants.USE_REACTIVE) + public Mono>> 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()); + } + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/controller/TreeController.java b/flyfish-web/src/main/java/com/flyfish/framework/controller/TreeController.java new file mode 100644 index 0000000..ae40e34 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/controller/TreeController.java @@ -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 + * @param + */ +public abstract class TreeController, Q extends Qo> extends BaseController { + + /** + * 获取权限树 + * + * @return 结果 + */ + @GetMapping("tree") + public Result> getTree(Q qo) { + // 第一步,查询全部,并根据条件筛选 + List filtered = service.getList(qo); + // 第二步,根据父id组成map + Map> group = filtered.stream() + .collect(Collectors.groupingBy(p -> StringUtils.defaultIfBlank(p.getParentId(), ""))); + // 第三步,筛选一级树深度 + List 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 applyChildren(List children, Map> group) { + if (CollectionUtils.isNotEmpty(children)) { + children.forEach(child -> { + List treeNodes = group.getOrDefault(child.getId(), Collections.emptyList()); + child.setChildren(treeNodes); + applyChildren(treeNodes, group); + }); + } + return children; + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/GlobalExceptionHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..528b758 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/GlobalExceptionHandler.java @@ -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> userInvalidExceptionHandler(UserInvalidException ex) { + logger.error(ex.getMessage(), ex); + return Mono.just(Result.error(ex.getMessage())); + } + + @ExceptionHandler(BaseException.class) + public Mono> baseExceptionHandler(BaseException ex) { + logger.error(ex.getMessage(), ex); + return Mono.just(Result.error(ex.getMessage())); + } + + @ExceptionHandler(DuplicateKeyException.class) + public Mono> 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> otherExceptionHandler(Exception ex) { + logger.error(ex.getMessage(), ex); + return Mono.just(Result.error(ex.getMessage())); + } + + /** + * 校验参数异常 + */ + @ExceptionHandler(WebExchangeBindException.class) + public Mono> validException(WebExchangeBindException e) { + return Mono.just(Result.error(e.getBindingResult().getFieldErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining(",")))); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java new file mode 100644 index 0000000..d714c4b --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationFailureHandler.java @@ -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 dataBufferTransformer; + + private Map, String> descriptionMap = new HashMap<>(); + + public JsonAuthenticationFailureHandler(DataBufferTransformer dataBufferTransformer) { + this.dataBufferTransformer = dataBufferTransformer; + descriptionMap.put(BadCredentialsException.class, "用户名密码不正确!"); + // todo 剩下的都在userDetailsService + } + + /** + * 失败时返回具体失败原因 + * + * @param webFilterExchange 请求数据 + * @param exception 异常 + * @return 结果 + */ + @Override + public Mono 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())) + ))); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java new file mode 100644 index 0000000..3775b39 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonAuthenticationSuccessHandler.java @@ -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 dataBufferTransformer; + + /** + * 登录成功后要返回用户的基本信息,节省带宽 + * + * @param webFilterExchange 请求信息 + * @param authentication 认证信息 + * @return 结果 + */ + @Override + public Mono 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())) + )); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java new file mode 100644 index 0000000..100917f --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/handler/JsonLogoutSuccessHandler.java @@ -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 dataBufferTransformer; + + private final TokenProvider tokenProvider; + + @Override + public Mono 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()))); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/BaseReactiveService.java b/flyfish-web/src/main/java/com/flyfish/framework/service/BaseReactiveService.java new file mode 100644 index 0000000..2bcd3ba --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/BaseReactiveService.java @@ -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 { + + /** + * 查询 + * + * @param entity 实体本身 + * @return 结果 + */ + Mono getOne(T entity); + + /** + * 查询 + * + * @param query 查询模型 + * @return 结果 + */ + Mono getOne(Qo query); + + /** + * 通过Id查询 + * + * @param id 主键 + * @return 结果 + */ + Mono getById(String id); + + /** + * 根据ID集合来查询 + * + * @param ids id列表 + * @return 结果 + */ + Flux getByIds(List ids); + + /** + * 查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + Flux getList(T entity); + + /** + * 查询列表 + * + * @param query 查询 + * @return 结果 + */ + Flux getList(Qo query); + + /** + * 分页查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + Mono> getPageList(T entity, Pageable pageable); + + /** + * 分页查询列表 + * + * @param query 查询实体 + * @return 结果 + */ + Mono> getPageList(Qo query); + + /** + * 获取所有对象 + * + * @return 结果 + */ + Flux getAll(); + + /** + * 查询总记录数 + * + * @return 总数 + */ + Mono countAll(); + + /** + * 查询总记录数 + * + * @param entity 通过实体查询个数 + * @return 结果 + */ + Mono count(T entity); + + /** + * 插入新记录 + * + * @param entity 实体 + */ + Mono create(T entity); + + /** + * 插入 不插入null字段 + * + * @param entity 实体 + */ + Mono createSelective(T entity); + + /** + * 删除 + * + * @param entity 实体 + */ + Mono delete(T entity); + + /** + * 根据Id删除 + * + * @param id 主键 + */ + Mono deleteById(String id); + + /** + * 根据id更新 + * + * @param entity 实体 + */ + Mono updateById(T entity); + + /** + * 不update null + * + * @param entity 实体 + */ + Mono updateSelectiveById(T entity); + + /** + * 根据ID集合批量删除 + * + * @param ids id结合 + */ + Mono deleteBatchByIds(List ids); + + /** + * 批量更新 + * + * @param entities 要批量更新的集合 + */ + Flux updateBatch(List entities); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/BaseService.java b/flyfish-web/src/main/java/com/flyfish/framework/service/BaseService.java new file mode 100644 index 0000000..3986aec --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/BaseService.java @@ -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 实体泛型 + */ +public interface BaseService { + + /** + * 查询 + * + * @param entity 实体本身 + * @return 结果 + */ + Optional getOne(T entity); + + /** + * 查询 + * + * @param query 查询模型 + * @return 结果 + */ + Optional getOne(Qo query); + + /** + * 通过Id查询 + * + * @param id 主键 + * @return 结果 + */ + Optional getById(String id); + + /** + * 通过实体名称访问 + * + * @param name 名称 + * @return 结果 + */ + Optional getByName(String name); + + /** + * 根据ID集合来查询 + * + * @param ids id列表 + * @return 结果 + */ + List getByIds(Collection ids); + + /** + * 通过某个字段的集合包含查询 + * + * @param key 键 + * @param values 值 + * @return 结果 + */ + List getByValues(String key, List values); + + /** + * 查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + List getList(T entity); + + /** + * 查询列表 + * + * @param query 查询 + * @return 结果 + */ + List getList(Qo query); + + /** + * 分页查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + Page getPageList(T entity, Pageable pageable); + + /** + * 分页查询列表 + * + * @param query 查询实体 + * @return 结果 + */ + Page getPageList(Qo query); + + /** + * 获取所有对象 + * + * @return 结果 + */ + List getAll(); + + /** + * 查询总记录数 + * + * @return 总数 + */ + Long countAll(); + + /** + * 查询总记录数 + * + * @param entity 通过实体查询个数 + * @return 结果 + */ + Long count(T entity); + + /** + * 查询总记录数 + * + * @param query 查询实体 + * @return 结果 + */ + Long count(Qo 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 ids); + + /** + * 通过某一个键删除所有 + * + * @param qo 查询实体 + */ + void deleteAll(Qo qo); + + /** + * 通过某一个键删除所有 + * + * @param entity 查询 + */ + void deleteAll(T entity); + + /** + * 删除全部实体,危险! + */ + void deleteAll(); + + /** + * 批量更新 + * + * @param entities 要批量更新的集合 + */ + List updateBatch(List entities); + + /** + * 同步数据 + * + * @param entities 数据们 + * @return 结果 + */ + SyncVo sync(List entities); + +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java b/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java new file mode 100644 index 0000000..1b95d10 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/MongoUserDetailsService.java @@ -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 authenticate(UserDetails user, ServerWebExchange exchange); + + /** + * 加载上下文,提供token调用 + * + * @param user 用户信息 + * @return 结果 + */ + Mono loadContext(UserDetails user); + + /** + * 主动退出登录 + * + * @param exchange 数据 + * @return 结果 + */ + Mono logout(ServerWebExchange exchange); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseReactiveServiceImpl.java b/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseReactiveServiceImpl.java new file mode 100644 index 0000000..13b7e49 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseReactiveServiceImpl.java @@ -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 实体泛型 + */ +public class BaseReactiveServiceImpl implements BaseReactiveService { + + @Autowired + protected DefaultReactiveRepository repository; + + /** + * 查询 + * + * @param entity 实体本身 + * @return 结果 + */ + @Override + public Mono getOne(T entity) { + return repository.findOne(Example.of(entity)); + } + + /** + * 查询 + * + * @param query 查询模型 + * @return 结果 + */ + @Override + public Mono getOne(Qo query) { + return Mono.empty(); + } + + /** + * 通过Id查询 + * + * @param id 主键 + * @return 结果 + */ + @Override + public Mono getById(String id) { + return repository.findById(id); + } + + /** + * 根据ID集合来查询 + * + * @param ids id列表 + * @return 结果 + */ + @Override + public Flux getByIds(List ids) { + return repository.findAllById(ids); + } + + /** + * 查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + @Override + public Flux getList(T entity) { + return repository.findAll(Example.of(entity)); + } + + /** + * 查询列表 + * + * @param query 查询 + * @return 结果 + */ + @Override + public Flux getList(Qo query) { + return Flux.empty(); + } + + /** + * 分页查询列表 + * + * @param entity 实体本身 + * @param pageable 分页 + * @return 结果 + */ + @Override + public Mono> 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 list = result.getContent(); + list.addAll(item); + return new PageImpl<>(list, pageable, result.getTotalElements()); + }); + } + + /** + * 分页查询列表 + * + * @param query 查询实体 + * @return 结果 + */ + @Override + public Mono> getPageList(Qo query) { + return Mono.empty(); + } + + /** + * 获取所有对象 + * + * @return 结果 + */ + @Override + public Flux getAll() { + return repository.findAll(); + } + + /** + * 查询总记录数 + * + * @return 总数 + */ + @Override + public Mono countAll() { + return repository.count(); + } + + /** + * 查询总记录数 + * + * @param entity 通过实体查询个数 + * @return 结果 + */ + @Override + public Mono count(T entity) { + return repository.count(Example.of(entity)); + } + + /** + * 插入新记录 + * + * @param entity 实体 + */ + @Override + public Mono create(T entity) { + return repository.insert(entity); + } + + /** + * 插入 不插入null字段 + * + * @param entity 实体 + */ + @Override + public Mono createSelective(T entity) { + return repository.insert(entity); + } + + /** + * 删除 + * + * @param entity 实体 + */ + @Override + public Mono delete(T entity) { + return repository.delete(entity); + } + + /** + * 根据Id删除 + * + * @param id 主键 + */ + @Override + public Mono deleteById(String id) { + return repository.deleteById(id); + } + + /** + * 根据id更新 + * + * @param entity 实体 + */ + @Override + public Mono updateById(T entity) { + return repository.save(entity); + } + + /** + * 不update null + * + * @param entity 实体 + */ + @Override + public Mono updateSelectiveById(T entity) { + Assert.hasText(entity.getId(), "更新的主键不可为空!"); + Mono 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 deleteBatchByIds(List ids) { + return Flux.fromIterable(ids) + .flatMap(t -> repository.deleteById(t)) + .single(); + } + + /** + * 批量更新 + * + * @param entities 要批量更新的集合 + */ + @Override + public Flux updateBatch(List entities) { +// Assert.notEmpty(entities, "数据不可为空!"); +// entities.forEach(item -> Assert.hasText(item.getId(), "每个对象的id必须附带!")); +// // 过滤不存在的记录 +// Map savedMap = ((List) repository.findAllById(entities.stream() +// .map(Domain::getId).collect(Collectors.toList()))).stream() +// .collect(Collectors.toMap(Domain::getId, t -> t)); +// List 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); + } +} + diff --git a/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseServiceImpl.java b/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseServiceImpl.java new file mode 100644 index 0000000..eca2af2 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/service/impl/BaseServiceImpl.java @@ -0,0 +1,354 @@ +package com.flyfish.framework.service.impl; + +import com.flyfish.framework.auditor.BeanAuditor; +import com.flyfish.framework.bean.SyncVo; +import com.flyfish.framework.domain.base.AuditDomain; +import com.flyfish.framework.domain.base.Domain; +import com.flyfish.framework.domain.base.Qo; +import com.flyfish.framework.repository.DefaultRepository; +import com.flyfish.framework.service.BaseService; +import com.flyfish.framework.utils.Assert; +import com.flyfish.framework.utils.CopyUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 基本的Service实现 + * + * @param + */ +public class BaseServiceImpl implements BaseService { + + @Autowired + protected DefaultRepository repository; + @Autowired(required = false) + protected BeanAuditor auditor; + @Autowired + private BeanAuditor operationAuditor; + + /** + * 查询 + * + * @param entity 实体本身 + * @return 结果 + */ + @Override + public Optional getOne(T entity) { + return repository.findOne(Example.of(entity)); + } + + /** + * 查询 + * + * @param query 查询模型 + * @return 结果 + */ + @Override + public Optional getOne(Qo query) { + return repository.findOne(query); + } + + /** + * 通过Id查询 + * + * @param id 主键 + * @return 结果 + */ + @Override + public Optional getById(String id) { + return repository.findById(id); + } + + /** + * 通过实体名称访问 + * + * @param name 名称 + * @return 结果 + */ + @Override + public Optional getByName(String name) { + return repository.findByName(name); + } + + /** + * 根据ID集合来查询 + * + * @param ids id列表 + * @return 结果 + */ + @Override + public List getByIds(Collection ids) { + return (List) repository.findAllById(ids); + } + + @Override + @SuppressWarnings("unchecked") + public List getByValues(String key, List values) { + return repository.findAllByValues(key, (List) values); + } + + /** + * 查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + @Override + public List getList(T entity) { + return repository.findAll(Example.of(entity)); + } + + /** + * 查询列表 + * + * @param query 查询 + * @return 结果 + */ + @Override + public List getList(Qo query) { + return (List) repository.findAll(query); + } + + /** + * 分页查询列表 + * + * @param entity 实体本身 + * @return 结果 + */ + @Override + public Page getPageList(T entity, Pageable pageable) { + return repository.findAll(Example.of(entity), pageable); + } + + /** + * 分页查询列表 + * + * @param query 查询实体 + * @return 结果 + */ + @Override + public Page getPageList(Qo query) { + return repository.findAll(query, query.getPageable()); + } + + /** + * 获取所有对象 + * + * @return 结果 + */ + @Override + public List getAll() { + return repository.findAll(); + } + + /** + * 查询总记录数 + * + * @return 总数 + */ + @Override + public Long countAll() { + return repository.count(); + } + + /** + * 查询总记录数 + * + * @param entity 通过实体查询个数 + * @return 结果 + */ + @Override + public Long count(T entity) { + return repository.count(Example.of(entity)); + } + + /** + * 查询总记录数 + * + * @param query 查询实体 + * @return 结果 + */ + @Override + public Long count(Qo query) { + return repository.count(query); + } + + /** + * 插入新记录 + * + * @param entity 实体 + */ + @Override + public T create(T entity) { + audit(entity); + return repository.insert(entity); + } + + /** + * 插入 不插入null字段 + * + * @param entity 实体 + */ + @Override + public T createSelective(T entity) { + audit(entity); + return repository.insert(entity); + } + + /** + * 删除 + * + * @param entity 实体 + */ + @Override + public void delete(T entity) { + repository.delete(entity); + } + + /** + * 根据Id删除 + * + * @param id 主键 + */ + @Override + public void deleteById(String id) { + repository.deleteById(id); + } + + /** + * 根据id更新 + * + * @param entity 实体 + */ + @Override + public T updateById(T entity) { + audit(entity); + return repository.save(entity); + } + + /** + * 不update null + * + * @param entity 实体 + */ + @Override + public T updateSelectiveById(T entity) { + Assert.hasText(entity.getId(), "更新的主键不可为空!"); + Optional saved = repository.findById(entity.getId()); + Assert.isTrue(saved.isPresent(), "要更新的信息不存在!"); + return updateById(CopyUtils.copyProps(entity, saved.get())); + } + + /** + * 根据ID集合批量删除 + * + * @param ids id结合 + */ + @Override + public void deleteBatchByIds(List ids) { + ids.forEach(id -> repository.deleteById(id)); + } + + /** + * 通过某一个键删除所有 + * + * @param qo 查询实体 + */ + @Override + public void deleteAll(Qo qo) { + Iterable list = repository.findAll(qo); + repository.deleteAll(list); + } + + /** + * 通过某一个键删除所有 + * + * @param entity 查询 + */ + @Override + public void deleteAll(T entity) { + Iterable list = repository.findAll(Example.of(entity)); + repository.deleteAll(list); + } + + /** + * 删除全部实体,危险! + */ + @Override + public void deleteAll() { + repository.deleteAll(); + } + + /** + * 批量更新 + * + * @param entities 要批量更新的集合 + */ + @Override + public List updateBatch(List entities) { + if (CollectionUtils.isNotEmpty(entities)) { + // entities.forEach(item -> Assert.hasText(item.getId(), "每个对象的id必须附带!")); + // 带有id的集合 + List ids = entities.stream() + .filter(Objects::nonNull) + .map(Domain::getId).collect(Collectors.toList()); + // 过滤不存在的记录 + Map savedMap = CollectionUtils.isEmpty(ids) ? ((List) repository.findAllById(ids)).stream() + .collect(Collectors.toMap(Domain::getId, t -> t)) : Collections.emptyMap(); + List updating = entities.stream() + .map(t -> { + // 补全已保存信息 + if (savedMap.containsKey(t.getId())) { + return CopyUtils.copyProps(t, savedMap.get(t.getId())); + } + audit(t); + return t; + }) + .collect(Collectors.toList()); + return repository.saveAll(updating); + } + return entities; + } + + /** + * 同步数据 + * + * @param entities 数据们 + * @return 结果 + */ + @Override + public SyncVo sync(List entities) { + List results = this.updateBatch(entities); + return SyncVo.builder() + .success(results.size()) + .list(results) + .build(); + } + + /** + * 对象审查 + * + * @param entity 实体 + */ + protected void audit(T entity) { + // 用户审查 + if (entity instanceof AuditDomain) { + operationAuditor.audit((AuditDomain) entity); + } + // 自定义审查 + if (auditor != null) { + auditor.audit(entity); + } + } + + @SuppressWarnings("unchecked") + public > R getRepository() { + return (R) repository; + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java b/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java new file mode 100644 index 0000000..3732ffa --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/transform/DataBufferTransformer.java @@ -0,0 +1,13 @@ +package com.flyfish.framework.transform; + +import org.springframework.core.io.buffer.DataBuffer; + +/** + * 用于转换DataBuffer的工具 + * + * @auther wangyu + */ +public interface DataBufferTransformer { + + DataBuffer transform(T source); +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java b/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java new file mode 100644 index 0000000..e3a5625 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/transform/ResultDataTransformer.java @@ -0,0 +1,32 @@ +package com.flyfish.framework.transform; + +import com.flyfish.framework.bean.Result; +import com.flyfish.framework.utils.JacksonUtil; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.stereotype.Component; + +import java.nio.charset.Charset; + +/** + * Json返回值转换器 + * + * @author wangyu + */ +@Component +public class ResultDataTransformer implements DataBufferTransformer { + + private DataBufferFactory factory; + + public ResultDataTransformer() { + this.factory = SharedDataBufferFactory.getDefaultFactory(); + } + + @Override + public DataBuffer transform(Result source) { + return JacksonUtil.toJson(source) + .map(result -> result.getBytes(Charset.defaultCharset())) + .map(factory::wrap) + .orElse(null); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/transform/SharedDataBufferFactory.java b/flyfish-web/src/main/java/com/flyfish/framework/transform/SharedDataBufferFactory.java new file mode 100644 index 0000000..174fb13 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/transform/SharedDataBufferFactory.java @@ -0,0 +1,18 @@ +package com.flyfish.framework.transform; + +import org.springframework.core.io.buffer.DataBufferFactory; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; + +/** + * 共享的工厂 + * + * @author wangyu + */ +public final class SharedDataBufferFactory { + + private static final DataBufferFactory defaultFactory = new DefaultDataBufferFactory(); + + public static DataBufferFactory getDefaultFactory() { + return defaultFactory; + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/ClientUtil.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/ClientUtil.java new file mode 100644 index 0000000..d288e84 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/ClientUtil.java @@ -0,0 +1,36 @@ +package com.flyfish.framework.utils; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.reactive.function.server.ServerRequest; + +import java.net.InetSocketAddress; +import java.util.Optional; + +public class ClientUtil { + /** + * 获取客户端真实ip + * + * @param request + * @return + */ + public static String getClientIp(ServerRequest request) { + Optional optional = filteredIp(request, "x-forwarded-for"); + if (optional.isPresent()) { + return optional.get(); + } + optional = filteredIp(request, "Proxy-Client-IP"); + if (optional.isPresent()) { + return optional.get(); + } + optional = filteredIp(request, "WL-Proxy-Client-IP"); + return optional + .orElseGet(() -> request.remoteAddress().map(InetSocketAddress::getHostName) + .orElse(null) + ); + } + + private static Optional filteredIp(ServerRequest request, String header) { + return request.headers().header(header).stream() + .filter(ip -> StringUtils.isNotBlank(ip) && !"unknown".equals(ip)).findFirst(); + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/RedisOperations.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/RedisOperations.java new file mode 100644 index 0000000..a597dec --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/RedisOperations.java @@ -0,0 +1,609 @@ +package com.flyfish.framework.utils; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.util.CollectionUtils; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * redis 工具类 + * + * @author wangyu + */ +@RequiredArgsConstructor +public class RedisOperations { + + private final RedisTemplate redisTemplate; + + private final StringRedisTemplate stringRedisTemplate; + + /** + * 指定缓存失效时间 + * + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据key 获取过期时间 + * + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public Long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + + /** + * 判断key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除缓存 + * + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete(CollectionUtils.arrayToList(key)); + } + } + } + + // ============================String============================= + + /** + * 普通缓存获取 + * + * @param key 键 + * @return 值 + */ + public Object get(String key) { + if (key == null) { + return null; + } + try { + return redisTemplate.opsForValue().get(key); + } catch (Exception e) { + return stringRedisTemplate.opsForValue().get(key); + } + } + + /** + * 获取string + * + * @param key 键 + * @return 结果 + */ + public String getString(String key) { + return null == key ? null : stringRedisTemplate.opsForValue().get(key); + } + + /** + * 获取多个key值 + * + * @param key 键 + * @return 结果 + */ + public Set getKeys(String key) { + return null == key ? null : redisTemplate.keys(key); + } + + /** + * 普通缓存放入 + * + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public Boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + } + + public boolean set(String key, String value) { + try { + stringRedisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 普通缓存放入并设置时间 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 递增 + * + * @param key 键 + * @param by 要增加几(大于0) + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + + /** + * 递减 + * + * @param key 键 + * @param by 要减少几(小于0) + * @return + */ + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递减因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + + // ================================Map================================= + + /** + * HashGet + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + + /** + * 获取hashKey对应的所有键值 + * + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * HashSet + * + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * HashSet 并设置时间 + * + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 向一张hash表中放入数据,如果不存在将创建 + * + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 删除hash表中的值 + * + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + + /** + * 判断hash表中是否有该项的值 + * + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + + /** + * hash递减 + * + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + + // ============================set============================= + + /** + * 根据key获取Set中的所有值 + * + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 根据value从一个set中查询,是否存在 + * + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将数据放入set缓存 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 将set数据放入缓存 + * + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + + /** + * 获取set缓存的长度 + * + * @param key 键 + * @return + */ + public Long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0L; + } + } + + /** + * 移除值为value的 + * + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public Long setRemove(String key, Object... values) { + try { + return redisTemplate.opsForSet().remove(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0L; + } + } + // ===============================list================================= + + /** + * 获取list缓存的内容 + * + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取list缓存的长度 + * + * @param key 键 + * @return + */ + public Long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0L; + } + } + + /** + * 通过索引 获取list中的值 + * + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据索引修改list中的某条数据 + * + * @param key 键 + * @param index 索引 + * @param value 值 + * @return + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 移除N个值为value + * + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public Long lRemove(String key, long count, Object value) { + try { + return redisTemplate.opsForList().remove(key, count, value); + } catch (Exception e) { + e.printStackTrace(); + return 0L; + } + } +} diff --git a/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java b/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java new file mode 100644 index 0000000..b7ed042 --- /dev/null +++ b/flyfish-web/src/main/java/com/flyfish/framework/utils/UserUtils.java @@ -0,0 +1,23 @@ +package com.flyfish.framework.utils; + +import com.flyfish.framework.domain.base.IUser; +import com.flyfish.framework.domain.po.User; +import org.springframework.security.core.context.SecurityContext; + +/** + * 用户工具 + * + * @author wangyu + */ +public class UserUtils { + + /** + * 解压用户 + * + * @param securityContext 上下文 + * @return 结果 + */ + public static User extractUser(SecurityContext securityContext) { + return ((IUser) securityContext.getAuthentication().getPrincipal()).toUser(); + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..77bf7c9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,103 @@ + + + 4.0.0 + + com.flyfish.framework + flyfish-framework + 0.0.1-SNAPSHOT + pom + + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + + + flyfish + 0.27.2 + UTF-8 + UTF-8 + 1.8 + Finchley.SR1 + 0.11.0 + + + + + wangyu + wybaby168@gmail.com + + + + + + flyfish-data + flyfish-common + flyfish-web + + + + + central + http://nexus.flyfish.group/repository/maven-public/ + + + + + flyfish-releases + http://nexus.flyfish.group/repository/maven-releases/ + + + flyfish-snapshots + http://nexus.flyfish.group/repository/maven-snapshots/ + + + + + + + org.projectlombok + lombok + true + + + com.alibaba + fastjson + 1.2.56 + + + + + + + org.apache.commons + commons-collections4 + 4.1 + + + commons-io + commons-io + 2.6 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + +