初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-commons</artifactId>
<version>${revision}</version>
</parent>
<artifactId>easyflow-common-web</artifactId>
<dependencies>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-common-satoken</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-common-base</artifactId>
</dependency>
<dependency>
<groupId>tech.easyflow</groupId>
<artifactId>easyflow-common-ai</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,6 @@
package tech.easyflow.common.web.controller;
public class BaseController {
}

View File

@@ -0,0 +1,318 @@
package tech.easyflow.common.web.controller;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.query.SqlOperators;
import com.mybatisflex.core.service.IService;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.SqlOperatorsUtil;
import tech.easyflow.common.util.SqlUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.exceptions.ProgramException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.util.*;
public class BaseCurdController<S extends IService<M>, M> extends BaseController {
protected final S service;
public BaseCurdController(S service) {
this.service = service;
}
/**
* 添加(保存)数据
*
* @param entity 表生成内容配置
* @return {@code Result.errorCode == 0} 添加成功,否则添加失败
*/
@PostMapping("save")
public Result<?> save(@JsonBody M entity) {
Result<?> result = onSaveOrUpdateBefore(entity, true);
if (result != null) return result;
if (entity == null) {
throw new NullPointerException("entity is null");
}
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
commonFiled(entity, loginAccount.getId(), loginAccount.getTenantId(), loginAccount.getDeptId());
service.save(entity);
onSaveOrUpdateAfter(entity, true);
TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass());
Object[] pkArgs = tableInfo.buildPkSqlArgs(entity);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("id", pkArgs);
return Result.ok(resultMap);
}
/**
* 根据主键删除数据id 需通过 json 传入,例如:
* <pre>
* {
* "id":123
* }
* <pre/>
*
*
* @param id 主键
* @return {@code Result.errorCode == 0} 删除成功,否则删除失败
*/
@PostMapping("remove")
@Transactional
public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
List<Serializable> ids = Collections.singletonList(id);
Result<?> result = onRemoveBefore(ids);
if (result != null) return result;
boolean success = service.removeById(id);
onRemoveAfter(ids);
return Result.ok(success);
}
/**
* 根据多个主键删数据内容id 需通过 json 传入,例如:
* <pre>
* {
* "ids":[123, 234, 222]
* }
* <pre/>
*
* @param ids 主键
* @return {@code Result.errorCode == 0} 删除成功,否则删除失败
*/
@PostMapping("removeBatch")
@Transactional
public Result<?> removeBatch(@JsonBody(value = "ids", required = true) Collection<Serializable> ids) {
if (ids == null || ids.isEmpty()) {
return Result.fail("id不能为空");
}
Result<?> result = onRemoveBefore(ids);
if (result != null) return result;
boolean success = service.removeByIds(ids);
onRemoveAfter(ids);
return Result.ok(success);
}
/**
* 根据主键更新内容
*
* @param entity 实体类数据
* @return {@code Result.errorCode == 0} 更新成功,否则更新失败
*/
@PostMapping("update")
public Result<?> update(@JsonBody M entity) {
Result<?> result = onSaveOrUpdateBefore(entity, false);
if (result != null) return result;
service.updateById(entity);
onSaveOrUpdateAfter(entity, false);
return Result.ok();
}
/**
* 查询所有所有数据
*
* @return 所有数据
*/
@GetMapping("list")
public Result<List<M>> list(M entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<M> list = Tree.tryToTree(service.list(queryWrapper), asTree);
return Result.ok(list);
}
/**
* 根据表主键查询数据详情。
*
* @param id 主键值
* @return 内容详情
*/
@GetMapping("detail")
public Result<M> detail(String id) {
if (tech.easyflow.common.util.StringUtil.noText(id)) {
throw new BusinessException("id must not be null");
}
return Result.ok(service.getById(id));
}
/**
* 分页查询数据列表
*
* @param request 查询数据
* @param sortKey 排序字段
* @param sortType 排序方式 asc | desc
* @param pageNumber 当前页码
* @param pageSize 每页的数据量
* @return 查询的结果集
*/
@GetMapping("page")
public Result<Page<M>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
if (pageSize == null || pageSize < 1) {
pageSize = 10L;
}
QueryWrapper queryWrapper = buildQueryWrapper(request);
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
}
protected QueryWrapper buildQueryWrapper(HttpServletRequest request) {
QueryWrapper queryWrapper = new QueryWrapper();
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap == null || parameterMap.isEmpty()) {
return queryWrapper;
}
String[] isQueryOrs = parameterMap.get("isQueryOr");
boolean isQueryOrBool = false;
if (isQueryOrs != null && isQueryOrs.length > 0) {
String isQueryOr = isQueryOrs[0];
isQueryOrBool = "true".equals(isQueryOr);
}
Map<String, String> propertyColumnMapping = TableInfoFactory.ofEntityClass(getEntityClass())
.getPropertyColumnMapping();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String paramKey = entry.getKey();
if (StringUtil.hasText(paramKey) && !paramKey.endsWith(OperatorBuilder.operatorSuffix) && propertyColumnMapping.containsKey(paramKey)) {
String columnName = propertyColumnMapping.get(paramKey);
String[] values = entry.getValue();
if (values != null && values.length > 0 && StringUtil.hasText(values[0])) {
String op = request.getParameter(paramKey + OperatorBuilder.operatorSuffix);
if (StringUtil.hasText(op)) {
OperatorBuilder.buildOperator(queryWrapper, columnName, op.trim(), values);
} else {
if (values.length == 2) {
queryWrapper.between(columnName, values[0], values[1]);
} else {
String value = values[0];
if (StringUtil.isNumeric(value)) {
queryWrapper.eq(columnName, value);
} else {
if (isQueryOrBool) {
queryWrapper.or(columnName + " like " + "'%" + value + "%' ");
} else {
queryWrapper.like(columnName, value);
}
}
}
}
}
}
}
return queryWrapper;
}
protected Class<?> getEntityClass() {
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
return (Class<M>) ((ParameterizedType) type).getActualTypeArguments()[1];
}
return null;
}
/**
* 方便子类复写,构建自己的 SqlOperators
*
* @param entity 实体类
* @return SqlOperators
*/
protected SqlOperators buildOperators(M entity) {
return entity == null ? SqlOperators.empty() : SqlOperatorsUtil.build(entity.getClass());
}
protected String getDefaultOrderBy() {
return "id desc";
}
/**
* 方便子类复写,构建自己的 orderBy
*
* @param sortKey 排序字段
* @param sortType 排序类型
* @param defaultOrderBy 默认方式内容
* @return orderBy 的内容,返回 null 或者 空字符串,表示不参与排序
*/
protected String buildOrderBy(String sortKey, String sortType, String defaultOrderBy) {
sortKey = StringUtil.camelToUnderline(sortKey);
return SqlUtil.buildOrderBy(sortKey, sortType, defaultOrderBy);
}
protected Page<M> queryPage(Page<M> page, QueryWrapper queryWrapper) {
return service.page(page, queryWrapper);
}
protected Result<?> onSaveOrUpdateBefore(M entity, boolean isSave) {
return null;
}
protected void onSaveOrUpdateAfter(M entity, boolean isSave) {
//void
}
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
return null;
}
protected void onRemoveAfter(Collection<Serializable> ids) {
//void
}
protected void commonFiled(Object t, BigInteger userId, BigInteger tenantId, BigInteger deptId) {
Method[] methods = t.getClass().getMethods();
try {
for (Method m : methods) {
String name = m.getName();
if ("setDeptId".equals(name)) {
m.invoke(t, deptId);
}
if ("setTenantId".equals(name)) {
m.invoke(t, tenantId);
}
if ("setCreatedBy".equals(name)) {
m.invoke(t, userId);
}
if ("setModifiedBy".equals(name)) {
m.invoke(t, userId);
}
if ("setCreated".equals(name)) {
m.invoke(t, new Date());
}
if ("setModified".equals(name)) {
m.invoke(t, new Date());
}
}
} catch (Exception e) {
throw new ProgramException("commonFiled反射出错" + e.getMessage());
}
}
}

View File

@@ -0,0 +1,81 @@
package tech.easyflow.common.web.controller;
import com.mybatisflex.core.query.QueryWrapper;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class OperatorBuilder {
public static final String operatorSuffix = "__op";
public static final Set<String> supportedOperators = new HashSet<>(Arrays.asList(
"eq", "ne", "gt", "ge", "lt", "le",
"like", "likeLeft", "likeRight", "notLike", "notLikeLeft", "notLikeRight",
"between", "notBetween", "in", "notIn"
, "isNull", "isNotNull"));
public static void buildOperator(QueryWrapper queryWrapper, String columnName, String op, String[] values) {
if (!supportedOperators.contains(op)) {
return;
}
switch (op) {
case "eq":
queryWrapper.eq(columnName, values[0]);
break;
case "ne":
queryWrapper.ne(columnName, values[0]);
break;
case "gt":
queryWrapper.gt(columnName, values[0]);
break;
case "ge":
queryWrapper.ge(columnName, values[0]);
break;
case "lt":
queryWrapper.lt(columnName, values[0]);
break;
case "le":
queryWrapper.le(columnName, values[0]);
break;
case "like":
queryWrapper.like(columnName, values[0]);
break;
case "likeLeft":
queryWrapper.likeLeft(columnName, values[0]);
break;
case "likeRight":
queryWrapper.likeRight(columnName, values[0]);
case "notLike":
queryWrapper.notLike(columnName, values[0]);
break;
case "notLikeLeft":
queryWrapper.notLikeLeft(columnName, values[0]);
break;
case "notLikeRight":
queryWrapper.notLikeRight(columnName, values[0]);
break;
case "between":
queryWrapper.between(columnName, values[0], values[1]);
break;
case "notBetween":
queryWrapper.notBetween(columnName, values[0], values[1]);
break;
case "in":
queryWrapper.in(columnName, values);
break;
case "notIn":
queryWrapper.notIn(columnName, values);
break;
case "isNull":
queryWrapper.isNull(columnName);
break;
case "isNotNull":
queryWrapper.isNotNull(columnName);
break;
}
}
}

View File

@@ -0,0 +1,26 @@
package tech.easyflow.common.web.controller;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
/**
* @ignore
*/
@RestController
@RequestMapping
public class WhiteLabelPageController implements ErrorController {
/**
* 解决 Whitelabel Error Page
*/
@RequestMapping("/error")
public Result<Void> error(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("jakarta.servlet.error.status_code");
HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
return Result.fail(httpStatus.value(), httpStatus.getReasonPhrase());
}
}

View File

@@ -0,0 +1,163 @@
//package tech.easyflow.common.web.devlog;
//
//
//import com.alibaba.fastjson2.JSON;
//import com.alibaba.fastjson2.JSONWriter;
//import com.alibaba.fastjson2.filter.PropertyFilter;
//import com.mybatisflex.core.util.ClassUtil;
//import com.mybatisflex.core.util.StringUtil;
//import jakarta.servlet.http.HttpServletRequest;
//import org.apache.ibatis.javassist.*;
//import org.aspectj.lang.ProceedingJoinPoint;
//import org.aspectj.lang.annotation.Around;
//import org.aspectj.lang.annotation.Aspect;
//import org.aspectj.lang.annotation.Pointcut;
//import org.aspectj.lang.reflect.MethodSignature;
//import org.springframework.context.annotation.Profile;
//import org.springframework.stereotype.Component;
//import org.springframework.web.context.request.RequestContextHolder;
//import org.springframework.web.context.request.ServletRequestAttributes;
//import org.springframework.web.servlet.ModelAndView;
//
//import java.lang.reflect.Method;
//import java.util.Enumeration;
//import java.util.StringJoiner;
//
//@Aspect
//@Component
//@Profile({"dev", "test"})
//public class DevLogAspect {
//
// private static final int maxOutputLengthOfParaValue = 512;
// private static ClassPool classPool = ClassPool.getDefault();
//
// static {
// classPool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
// }
//
//
// @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) || execution(* tech.easyflow.common.web.controller.BaseCurdController.*(..))")
// public void webLog() {
// }
//
// @Around("webLog()")
// public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// HttpServletRequest request = attributes.getRequest();
//
// MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
// Class<?> controllerClass = signature.getDeclaringType();
// Method method = signature.getMethod();
//
// String url = request.getRequestURL().toString();
// String param = getRequestParamsString(request);
//
// int lineNumber = getLineNumber(controllerClass, method);
//
//
// long startTime = System.currentTimeMillis();
// Object result = null;
// try {
// result = proceedingJoinPoint.proceed();
// } finally {
// String logInfo = "\n" +
// "+========================================= Start ==========================================\n" +
// "| Request : " + request.getMethod() + " " + url + "\n" +
// "| Request Params : " + param + "\n" +
// "| Request IP : " + request.getRemoteAddr() + "\n" +
// "| Controller : " + signature.getDeclaringTypeName() + "." + "(" + controllerClass.getSimpleName() + ".java:" + lineNumber + ")" + "\n" +
// "| Method : " + method.getName() + buildParamsString(method) + "\n" +
// "| Response : " + getResponseText(result) + "\n" +
// "| Elapsed Time : " + (System.currentTimeMillis() - startTime) + " ms" + "\n" +
// "+========================================== End ===========================================\n";
// System.out.println(logInfo);
// }
// return result;
// }
//
// private static String getResponseText(Object result) {
// if (result instanceof ModelAndView && ((ModelAndView) result).isReference()) {
// return ((ModelAndView) result).getViewName();
// }
//
// String originalText;
// if (result instanceof String) {
// originalText = (String) result;
// } else {
// originalText = JSON.toJSONString(result);
// }
//
// if (StringUtil.noText(originalText)) {
// return "";
// }
//
// originalText = originalText.replace("\n", "");
//
// if (originalText.length() > 100) {
// return originalText.substring(0, 100) + "...";
// }
//
// try {
// // 使用PropertyFilter过滤掉timeout字段
// PropertyFilter filter = (object, name, value) -> !"timeout".equals(name);
//
// String resultStr = JSON.toJSONString(result,
// filter,
// JSONWriter.Feature.WriteMapNullValue,
// JSONWriter.Feature.IgnoreNonFieldGetter);
// return resultStr;
// } catch (Exception e) {
// return "[Serialization Error: " + e.getMessage() + "]";
// }
// }
//
//
// private String buildParamsString(Method method) {
// StringJoiner joiner = new StringJoiner(", ", "(", ")");
// for (Class<?> parameterType : method.getParameterTypes()) {
// joiner.add(parameterType.getSimpleName());
// }
// return joiner.toString();
// }
//
//
// private int getLineNumber(Class<?> controllerClass, Method method) throws NotFoundException {
// CtClass ctClass = classPool.get(ClassUtil.getUsefulClass(controllerClass).getName());
// classPool.get(ClassUtil.getUsefulClass(controllerClass).getName());
// String desc = DevLogUtil.getMethodDescWithoutName(method);
// CtMethod ctMethod = ctClass.getMethod(method.getName(), desc);
// return ctMethod.getMethodInfo().getLineNumber(0);
// }
//
//
// private String getRequestParamsString(HttpServletRequest request) {
// StringBuilder sb = new StringBuilder();
// Enumeration<String> e = request.getParameterNames();
// if (e.hasMoreElements()) {
// while (e.hasMoreElements()) {
// String name = e.nextElement();
// String[] values = request.getParameterValues(name);
// if (values.length == 1) {
// sb.append(name).append("=");
// if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) {
// sb.append(values[0], 0, maxOutputLengthOfParaValue).append("...");
// } else {
// sb.append(values[0]);
// }
// } else {
// sb.append(name).append("[]={");
// for (int i = 0; i < values.length; i++) {
// if (i > 0) {
// sb.append(",");
// }
// sb.append(values[i]);
// }
// sb.append("}");
// }
// sb.append(" ");
// }
// }
// return sb.toString();
// }
//
//}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.easyflow.common.web.devlog;
import java.lang.reflect.Method;
/**
* 参考 https://github.com/apache/dubbo/blob/master/dubbo-common/src/main/java/org/apache/dubbo/common/utils/ReflectUtils.java
*/
public final class DevLogUtil {
/**
* void(V).
*/
public static final char JVM_VOID = 'V';
/**
* boolean(Z).
*/
public static final char JVM_BOOLEAN = 'Z';
/**
* byte(B).
*/
public static final char JVM_BYTE = 'B';
/**
* char(C).
*/
public static final char JVM_CHAR = 'C';
/**
* double(D).
*/
public static final char JVM_DOUBLE = 'D';
/**
* float(F).
*/
public static final char JVM_FLOAT = 'F';
/**
* int(I).
*/
public static final char JVM_INT = 'I';
/**
* long(J).
*/
public static final char JVM_LONG = 'J';
/**
* short(S).
*/
public static final char JVM_SHORT = 'S';
/**
* get class desc.
* boolean[].class => "[Z"
* Object.class => "Ljava/lang/Object;"
*
* @param c class.
* @return desc.
*/
public static String getDesc(Class<?> c) {
StringBuilder ret = new StringBuilder();
while (c.isArray()) {
ret.append('[');
c = c.getComponentType();
}
if (c.isPrimitive()) {
String t = c.getName();
if ("void".equals(t)) {
ret.append(JVM_VOID);
} else if ("boolean".equals(t)) {
ret.append(JVM_BOOLEAN);
} else if ("byte".equals(t)) {
ret.append(JVM_BYTE);
} else if ("char".equals(t)) {
ret.append(JVM_CHAR);
} else if ("double".equals(t)) {
ret.append(JVM_DOUBLE);
} else if ("float".equals(t)) {
ret.append(JVM_FLOAT);
} else if ("int".equals(t)) {
ret.append(JVM_INT);
} else if ("long".equals(t)) {
ret.append(JVM_LONG);
} else if ("short".equals(t)) {
ret.append(JVM_SHORT);
}
} else {
ret.append('L');
ret.append(c.getName().replace('.', '/'));
ret.append(';');
}
return ret.toString();
}
/**
* get method desc.
* "(I)I", "()V", "(Ljava/lang/String;Z)V"
*
* @param m method.
* @return desc.
*/
public static String getMethodDescWithoutName(Method m) {
StringBuilder ret = new StringBuilder();
ret.append('(');
Class<?>[] parameterTypes = m.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
ret.append(getDesc(parameterTypes[i]));
}
ret.append(')').append(getDesc(m.getReturnType()));
return ret.toString();
}
}

View File

@@ -0,0 +1,46 @@
package tech.easyflow.common.web.error;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tech.easyflow.common.domain.Result;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import jakarta.validation.ConstraintViolationException;
import tech.easyflow.common.web.exceptions.BusinessException;
public class GlobalErrorResolver implements HandlerExceptionResolver {
private static final Logger LOG = LoggerFactory.getLogger(GlobalErrorResolver.class);
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ex.printStackTrace();
Result<?> error;
if (ex instanceof MissingServletRequestParameterException) {
error = Result.fail(1, ((MissingServletRequestParameterException) ex).getParameterName() + " 不能为空.");
} else if (ex instanceof NotLoginException) {
response.setStatus(401);
error = Result.fail(401, "请登录");
} else if (ex instanceof NotPermissionException || ex instanceof NotRoleException) {
error = Result.fail(4010, "无权操作");
} else if (ex instanceof ConstraintViolationException) {
error = Result.fail(400, ex.getMessage());
} else if (ex instanceof BusinessException) {
error = Result.fail(1, ex.getMessage());
} else {
LOG.error(ex.toString(), ex);
error = Result.fail(1, "错误信息:" + ex.getMessage());
}
JSONObject object = JSON.parseObject(JSON.toJSONString(error));
return new ModelAndView(new JakartaJsonView())
.addAllObjects(object);
}
}

View File

@@ -0,0 +1,25 @@
package tech.easyflow.common.web.error;
import com.alibaba.fastjson2.JSON;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.View;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class JakartaJsonView implements View {
@Override
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
if (model != null) {
String jsonString = JSON.toJSONString(model);
try (ServletOutputStream out = response.getOutputStream()) {
out.write(jsonString.getBytes(StandardCharsets.UTF_8));
out.flush();
}
}
}
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.common.web.exceptions;
/**
* 业务报错
*/
public class BusinessException extends RuntimeException {
public BusinessException() {
}
public BusinessException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.common.web.exceptions;
/**
* 参数报错
*/
public class ParamException extends RuntimeException {
public ParamException() {
}
public ParamException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,14 @@
package tech.easyflow.common.web.exceptions;
/**
* 程序报错
*/
public class ProgramException extends RuntimeException {
public ProgramException() {
}
public ProgramException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.easyflow.common.web.jsonbody;
import java.lang.annotation.*;
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface JsonBody {
/**
* 自定义前缀
*/
String value() default "";
/**
* 是否跳过转换异常
*/
boolean skipConvertError() default true;
boolean required() default false;
String defaultValue() default "";
}

View File

@@ -0,0 +1,93 @@
package tech.easyflow.common.web.jsonbody;
import jakarta.servlet.http.HttpServletRequest;
import tech.easyflow.common.util.RequestUtil;
import com.mybatisflex.core.util.ConvertUtil;
import com.mybatisflex.core.util.StringUtil;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Component
public class JsonBodyArgumentResolver implements HandlerMethodArgumentResolver, SmartInitializingSingleton {
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
public JsonBodyArgumentResolver(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;
}
@Override
public void afterSingletonsInstantiated() {
List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
ArrayList<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(Objects.requireNonNull(argumentResolvers));
resolvers.add(0, this);
requestMappingHandlerAdapter.setArgumentResolvers(resolvers);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer
, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
JsonBody jsonBody = parameter.getParameterAnnotation(JsonBody.class);
Class<?> paraClass = parameter.getParameterType();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
return null;
}
Object jsonObjectOrArray = RequestUtil.readJsonObjectOrArray(request);
Object result = null;
Type paraType = parameter.getGenericParameterType();
if (paraType instanceof TypeVariable) {
Type variableRawType = JsonBodyParser.getTypeVariableRawType(
parameter.getContainingClass(), ((TypeVariable<?>) paraType));
if (variableRawType != null) {
paraClass = (Class<?>) variableRawType;
paraType = variableRawType;
}
}
try {
result = JsonBodyParser.parseJsonBody(jsonObjectOrArray, paraClass, paraType, jsonBody.value());
} catch (Exception e) {
if (jsonBody.skipConvertError()) {
//ignore
} else {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
if (result == null && StringUtil.hasText(jsonBody.defaultValue())) {
result = ConvertUtil.convert(jsonBody.defaultValue(), paraClass);
}
if ((result == null) && jsonBody.required()) {
throw new IllegalArgumentException(jsonBody.value() + " must not be null or blank");
}
return result;
}
}

View File

@@ -0,0 +1,253 @@
/**
* Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.easyflow.common.web.jsonbody;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mybatisflex.core.util.ConvertUtil;
import org.springframework.util.StringUtils;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class JsonBodyParser {
private static final String startOfArray = "[";
private static final String endOfArray = "]";
/**
* 获取方法里的泛型参数 T 对于的真实的 Class 类
*
* @param defClass
* @param typeVariable
* @return
*/
public static Type getTypeVariableRawType(Class<?> defClass, TypeVariable<?> typeVariable) {
Type type = defClass.getGenericSuperclass();
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (typeArguments.length == 1) {
return typeArguments[0];
} else if (typeArguments.length > 1) {
TypeVariable<?>[] typeVariables = typeVariable.getGenericDeclaration().getTypeParameters();
for (int i = 0; i < typeVariables.length; i++) {
if (typeVariable.getName().equals(typeVariables[i].getName())) {
return typeArguments[i];
}
}
}
}
return null;
}
public static Object parseJsonBody(Object jsonObjectOrArray, Class<?> paraClass, Type paraType, String jsonKey) throws InstantiationException, IllegalAccessException {
if (jsonObjectOrArray == null) {
return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null;
}
if (Collection.class.isAssignableFrom(paraClass) || paraClass.isArray()) {
return parseArray(jsonObjectOrArray, paraClass, paraType, jsonKey);
} else {
return parseObject((JSONObject) jsonObjectOrArray, paraClass, paraType, jsonKey);
}
}
public static Object getPrimitiveDefaultValue(Class<?> paraClass) {
if (paraClass == int.class || paraClass == long.class || paraClass == float.class || paraClass == double.class) {
return 0;
} else if (paraClass == boolean.class) {
return Boolean.FALSE;
} else if (paraClass == short.class) {
return (short) 0;
} else if (paraClass == byte.class) {
return (byte) 0;
} else if (paraClass == char.class) {
return '\u0000';
} else {
//不存在这种类型
return null;
}
}
private static Object parseObject(JSONObject rawObject, Class<?> paraClass, Type paraType, String jsonKey) throws IllegalAccessException, InstantiationException {
if (!StringUtils.hasText(jsonKey)) {
return toJavaObject(rawObject, paraClass, paraType);
}
Object result = null;
String[] keys = jsonKey.split("\\.");
for (int i = 0; i < keys.length; i++) {
if (rawObject != null && !rawObject.isEmpty()) {
String key = keys[i].trim();
if (StringUtils.hasText(key)) {
//the last
if (i == keys.length - 1) {
if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
String realKey = key.substring(0, key.indexOf(startOfArray));
JSONArray jarray = rawObject.getJSONArray(realKey.trim());
if (jarray != null && jarray.size() > 0) {
String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1);
int arrayIndex = StringUtils.hasText(arrayString) ? Integer.parseInt(arrayString.trim()) : 0;
result = arrayIndex >= jarray.size() ? null : jarray.get(arrayIndex);
}
} else {
result = rawObject.get(key);
}
}
//not last
else {
rawObject = getJSONObjectByKey(rawObject, key);
}
}
}
}
if (result == null || "".equals(result)) {
return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null;
}
if (paraClass == String.class && paraClass == paraType) {
return result.toString();
}
// JSONObject 类型
if (result instanceof JSONObject) {
return toJavaObject((JSONObject) result, paraClass, paraType);
}
return ConvertUtil.convert(result, paraClass);
}
private static Object parseArray(Object rawJsonObjectOrArray, Class<?> typeClass, Type type, String jsonKey) {
JSONArray jsonArray = null;
if (!StringUtils.hasText(jsonKey)) {
if (rawJsonObjectOrArray instanceof JSONArray) {
jsonArray = (JSONArray) rawJsonObjectOrArray;
}
} else {
if (rawJsonObjectOrArray instanceof JSONObject) {
JSONObject rawObject = (JSONObject) rawJsonObjectOrArray;
String[] keys = jsonKey.split("\\.");
for (int i = 0; i < keys.length; i++) {
if (rawObject == null || rawObject.isEmpty()) {
break;
}
String key = keys[i].trim();
if (StringUtils.hasText(key)) {
//the last
if (i == keys.length - 1) {
if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
String realKey = key.substring(0, key.indexOf(startOfArray));
JSONArray jarray = rawObject.getJSONArray(realKey.trim());
if (jarray == null || jarray.isEmpty()) {
return null;
}
String subKey = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1).trim();
if (!StringUtils.hasText(subKey)) {
throw new IllegalStateException("Sub key can not empty: " + jsonKey);
}
JSONArray newJsonArray = new JSONArray();
for (int j = 0; j < jarray.size(); j++) {
Object value = jarray.getJSONObject(j).get(subKey);
if (value != null) {
newJsonArray.add(value);
}
}
jsonArray = newJsonArray;
} else {
jsonArray = rawObject.getJSONArray(key);
}
}
//not last
else {
rawObject = getJSONObjectByKey(rawObject, key);
}
}
}
}
}
if (jsonArray == null || jsonArray.isEmpty()) {
return null;
}
//非泛型 set
if ((typeClass == Set.class || typeClass == HashSet.class) && typeClass == type) {
return new HashSet<>(jsonArray);
}
//直接获取 JsonArray
if (typeClass == type && typeClass == JSONArray.class) {
return jsonArray;
}
return jsonArray.toJavaObject(type);
}
private static JSONObject getJSONObjectByKey(JSONObject jsonObject, String key) {
if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
String realKey = key.substring(0, key.indexOf(startOfArray));
JSONArray jarray = jsonObject.getJSONArray(realKey.trim());
if (jarray == null || jarray.isEmpty()) {
return null;
}
String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1);
int arrayIndex = StringUtils.hasText(arrayString) ? Integer.parseInt(arrayString.trim()) : 0;
return arrayIndex >= jarray.size() ? null : jarray.getJSONObject(arrayIndex);
} else {
return jsonObject.getJSONObject(key);
}
}
private static Object toJavaObject(JSONObject rawObject, Class<?> paraClass, Type paraType) throws IllegalAccessException, InstantiationException {
if (rawObject.isEmpty()) {
return paraClass.isPrimitive() ? getPrimitiveDefaultValue(paraClass) : null;
}
//非泛型 的 map
if ((paraClass == Map.class || paraClass == JSONObject.class) && paraClass == paraType) {
return rawObject;
}
//非泛型 的 map
if (Map.class.isAssignableFrom(paraClass) && paraClass == paraType && canNewInstance(paraClass)) {
Map map = (Map) paraClass.newInstance();
map.putAll(rawObject);
return map;
}
return rawObject.toJavaObject(paraType);
}
private static boolean canNewInstance(Class<?> clazz) {
int modifiers = clazz.getModifiers();
return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers);
}
}