commit:同步2.0版本
@@ -161,7 +161,7 @@ public class LoginController {
|
||||
@PostMapping("/changePassword")
|
||||
public ResponseResult<Void> changePassword(
|
||||
@MyRequestBody String oldPass, @MyRequestBody String newPass) throws Exception {
|
||||
if (MyCommonUtil.existBlankArgument(oldPass, oldPass)) {
|
||||
if (MyCommonUtil.existBlankArgument(newPass, oldPass)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
|
||||
}
|
||||
TokenData tokenData = TokenData.takeFromRequest();
|
||||
|
||||
@@ -10,14 +10,12 @@ import com.flow.demo.common.core.object.*;
|
||||
import com.flow.demo.common.core.util.*;
|
||||
import com.flow.demo.common.core.constant.*;
|
||||
import com.flow.demo.common.core.annotation.MyRequestBody;
|
||||
import com.flow.demo.common.core.validator.UpdateGroup;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
import javax.validation.groups.Default;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -44,7 +42,7 @@ public class SysDeptController {
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
public ResponseResult<Long> add(@MyRequestBody SysDeptDto sysDeptDto) {
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptDto);
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptDto, false);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
@@ -70,7 +68,7 @@ public class SysDeptController {
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
public ResponseResult<Void> update(@MyRequestBody SysDeptDto sysDeptDto) {
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptDto, Default.class, UpdateGroup.class);
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptDto, true);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
@@ -188,16 +186,20 @@ public class SysDeptController {
|
||||
@MyRequestBody SysPostDto sysPostDtoFilter,
|
||||
@MyRequestBody MyOrderParam orderParam,
|
||||
@MyRequestBody MyPageParam pageParam) {
|
||||
ResponseResult<Void> verifyResult = this.doSysDeptPostVerify(deptId);
|
||||
if (!verifyResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(verifyResult);
|
||||
if (MyCommonUtil.isNotBlankOrNull(deptId) && !sysDeptService.existId(deptId)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
|
||||
}
|
||||
if (pageParam != null) {
|
||||
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
|
||||
}
|
||||
SysPost filter = MyModelUtil.copyTo(sysPostDtoFilter, SysPost.class);
|
||||
String orderBy = MyOrderParam.buildOrderBy(orderParam, SysPost.class);
|
||||
List<SysPost> sysPostList = sysPostService.getNotInSysPostListByDeptId(deptId, filter, orderBy);
|
||||
List<SysPost> sysPostList;
|
||||
if (MyCommonUtil.isNotBlankOrNull(deptId)) {
|
||||
sysPostList = sysPostService.getNotInSysPostListByDeptId(deptId, filter, orderBy);
|
||||
} else {
|
||||
sysPostList = sysPostService.getSysPostList(filter, orderBy);
|
||||
}
|
||||
return ResponseResult.success(MyPageUtil.makeResponseData(sysPostList, SysPost.INSTANCE));
|
||||
}
|
||||
|
||||
@@ -212,13 +214,12 @@ public class SysDeptController {
|
||||
*/
|
||||
@PostMapping("/listSysDeptPost")
|
||||
public ResponseResult<MyPageData<SysPostVo>> listSysDeptPost(
|
||||
@MyRequestBody Long deptId,
|
||||
@MyRequestBody(required = true) Long deptId,
|
||||
@MyRequestBody SysPostDto sysPostDtoFilter,
|
||||
@MyRequestBody MyOrderParam orderParam,
|
||||
@MyRequestBody MyPageParam pageParam) {
|
||||
ResponseResult<Void> verifyResult = this.doSysDeptPostVerify(deptId);
|
||||
if (!verifyResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(verifyResult);
|
||||
if (!sysDeptService.existId(deptId)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
|
||||
}
|
||||
if (pageParam != null) {
|
||||
PageMethod.startPage(pageParam.getPageNum(), pageParam.getPageSize());
|
||||
@@ -229,16 +230,6 @@ public class SysDeptController {
|
||||
return ResponseResult.success(MyPageUtil.makeResponseData(sysPostList, SysPost.INSTANCE));
|
||||
}
|
||||
|
||||
private ResponseResult<Void> doSysDeptPostVerify(Long deptId) {
|
||||
if (MyCommonUtil.existBlankArgument(deptId)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
|
||||
}
|
||||
if (!sysDeptService.existId(deptId)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.INVALID_RELATED_RECORD_ID);
|
||||
}
|
||||
return ResponseResult.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加部门管理和 [岗位管理] 对象的多对多关联关系数据。
|
||||
*
|
||||
@@ -253,11 +244,9 @@ public class SysDeptController {
|
||||
if (MyCommonUtil.existBlankArgument(deptId, sysDeptPostDtoList)) {
|
||||
return ResponseResult.error(ErrorCodeEnum.ARGUMENT_NULL_EXIST);
|
||||
}
|
||||
for (SysDeptPostDto sysDeptPost : sysDeptPostDtoList) {
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptPost);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysDeptPostDtoList);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
Set<Long> postIdSet = sysDeptPostDtoList.stream().map(SysDeptPostDto::getPostId).collect(Collectors.toSet());
|
||||
if (!sysDeptService.existId(deptId) || !sysPostService.existUniqueKeyList("postId", postIdSet)) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.flow.demo.common.core.util.*;
|
||||
import com.flow.demo.common.core.constant.*;
|
||||
import com.flow.demo.common.core.annotation.MyRequestBody;
|
||||
import com.flow.demo.common.core.validator.AddGroup;
|
||||
import com.flow.demo.common.core.validator.UpdateGroup;
|
||||
import com.flow.demo.webadmin.config.ApplicationConfig;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
@@ -19,7 +18,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.*;
|
||||
import javax.validation.groups.Default;
|
||||
|
||||
/**
|
||||
* 用户管理操作控制器类。
|
||||
@@ -54,7 +52,7 @@ public class SysUserController {
|
||||
@MyRequestBody String deptPostIdListString,
|
||||
@MyRequestBody String dataPermIdListString,
|
||||
@MyRequestBody String roleIdListString) {
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto, Default.class, AddGroup.class);
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto, false);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
@@ -86,7 +84,7 @@ public class SysUserController {
|
||||
@MyRequestBody String deptPostIdListString,
|
||||
@MyRequestBody String dataPermIdListString,
|
||||
@MyRequestBody String roleIdListString) {
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto, Default.class, UpdateGroup.class);
|
||||
String errorMessage = MyCommonUtil.getModelValidationError(sysUserDto, true);
|
||||
if (errorMessage != null) {
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ import java.util.*;
|
||||
*/
|
||||
public interface SysDeptMapper extends BaseDaoMapper<SysDept> {
|
||||
|
||||
/**
|
||||
* 批量插入对象列表。
|
||||
*
|
||||
* @param sysDeptList 新增对象列表。
|
||||
*/
|
||||
void insertList(List<SysDept> sysDeptList);
|
||||
|
||||
/**
|
||||
* 获取过滤后的对象列表。
|
||||
*
|
||||
|
||||
@@ -14,6 +14,13 @@ import java.util.*;
|
||||
*/
|
||||
public interface SysUserMapper extends BaseDaoMapper<SysUser> {
|
||||
|
||||
/**
|
||||
* 批量插入对象列表。
|
||||
*
|
||||
* @param sysUserList 新增对象列表。
|
||||
*/
|
||||
void insertList(List<SysUser> sysUserList);
|
||||
|
||||
/**
|
||||
* 获取过滤后的对象列表。
|
||||
*
|
||||
|
||||
@@ -13,6 +13,31 @@
|
||||
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insertList">
|
||||
INSERT INTO zz_sys_dept
|
||||
(dept_id,
|
||||
dept_name,
|
||||
show_order,
|
||||
parent_id,
|
||||
deleted_flag,
|
||||
create_user_id,
|
||||
update_user_id,
|
||||
create_time,
|
||||
update_time)
|
||||
VALUES
|
||||
<foreach collection="list" index="index" item="item" separator="," >
|
||||
(#{item.deptId},
|
||||
#{item.deptName},
|
||||
#{item.showOrder},
|
||||
#{item.parentId},
|
||||
#{item.deletedFlag},
|
||||
#{item.createUserId},
|
||||
#{item.updateUserId},
|
||||
#{item.createTime},
|
||||
#{item.updateTime})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 如果有逻辑删除字段过滤,请写到这里 -->
|
||||
<sql id="filterRef">
|
||||
<!-- 这里必须加上全包名,否则当filterRef被其他Mapper.xml包含引用的时候,就会调用Mapper.xml中的该SQL片段 -->
|
||||
|
||||
@@ -17,6 +17,39 @@
|
||||
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
|
||||
</resultMap>
|
||||
|
||||
<insert id="insertList">
|
||||
INSERT INTO zz_sys_user
|
||||
(user_id,
|
||||
login_name,
|
||||
password,
|
||||
show_name,
|
||||
dept_id,
|
||||
user_type,
|
||||
head_image_url,
|
||||
user_status,
|
||||
deleted_flag,
|
||||
create_user_id,
|
||||
update_user_id,
|
||||
create_time,
|
||||
update_time)
|
||||
VALUES
|
||||
<foreach collection="list" index="index" item="item" separator="," >
|
||||
(#{item.userId},
|
||||
#{item.loginName},
|
||||
#{item.password},
|
||||
#{item.showName},
|
||||
#{item.deptId},
|
||||
#{item.userType},
|
||||
#{item.headImageUrl},
|
||||
#{item.userStatus},
|
||||
#{item.deletedFlag},
|
||||
#{item.createUserId},
|
||||
#{item.updateUserId},
|
||||
#{item.createTime},
|
||||
#{item.updateTime})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- 如果有逻辑删除字段过滤,请写到这里 -->
|
||||
<sql id="filterRef">
|
||||
<!-- 这里必须加上全包名,否则当filterRef被其他Mapper.xml包含引用的时候,就会调用Mapper.xml中的该SQL片段 -->
|
||||
|
||||
@@ -5,7 +5,7 @@ import lombok.Data;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* SysDeptVO对象。
|
||||
* SysDeptVO视图对象。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.Map;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SysUserVO对象。
|
||||
* SysUserVO视图对象。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
|
||||
@@ -362,6 +362,52 @@ public abstract class BaseService<M, K extends Serializable> extends ServiceImpl
|
||||
return mapper().selectList(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回符合主键 in (idValues) 条件的所有数据。同时返回关联数据。
|
||||
*
|
||||
* @param idValues 主键值集合。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
@Override
|
||||
public List<M> getInListWithRelation(Set<K> idValues, MyRelationParam relationParam) {
|
||||
List<M> resultList = this.getInList(idValues);
|
||||
this.buildRelationForDataList(resultList, relationParam);
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据。同时返回关联数据。
|
||||
*
|
||||
* @param inFilterField 参与(In-list)过滤的Java字段。
|
||||
* @param inFilterValues 参与(In-list)过滤的Java字段值集合。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
@Override
|
||||
public <T> List<M> getInListWithRelation(String inFilterField, Set<T> inFilterValues, MyRelationParam relationParam) {
|
||||
List<M> resultList = this.getInList(inFilterField, inFilterValues);
|
||||
this.buildRelationForDataList(resultList, relationParam);
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据,并根据orderBy字段排序。同时返回关联数据。
|
||||
*
|
||||
* @param inFilterField 参与(In-list)过滤的Java字段。
|
||||
* @param inFilterValues 参与(In-list)过滤的Java字段值集合。
|
||||
* @param orderBy 排序字段。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
@Override
|
||||
public <T> List<M> getInListWithRelation(
|
||||
String inFilterField, Set<T> inFilterValues, String orderBy, MyRelationParam relationParam) {
|
||||
List<M> resultList = this.getInList(inFilterField, inFilterValues, orderBy);
|
||||
this.buildRelationForDataList(resultList, relationParam);
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用参数对象作为过滤条件,获取数据数量。
|
||||
*
|
||||
|
||||
@@ -121,6 +121,37 @@ public interface IBaseService<M, K extends Serializable> extends IService<M>{
|
||||
*/
|
||||
<T> List<M> getInList(String inFilterField, Set<T> inFilterValues, String orderBy);
|
||||
|
||||
/**
|
||||
* 返回符合主键 in (idValues) 条件的所有数据。同时返回关联数据。
|
||||
*
|
||||
* @param idValues 主键值集合。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
List<M> getInListWithRelation(Set<K> idValues, MyRelationParam relationParam);
|
||||
|
||||
/**
|
||||
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据。同时返回关联数据。
|
||||
*
|
||||
* @param inFilterField 参与(In-list)过滤的Java字段。
|
||||
* @param inFilterValues 参与(In-list)过滤的Java字段值集合。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
<T> List<M> getInListWithRelation(String inFilterField, Set<T> inFilterValues, MyRelationParam relationParam);
|
||||
|
||||
/**
|
||||
* 返回符合 inFilterField in (inFilterValues) 条件的所有数据,并根据orderBy字段排序。同时返回关联数据。
|
||||
*
|
||||
* @param inFilterField 参与(In-list)过滤的Java字段。
|
||||
* @param inFilterValues 参与(In-list)过滤的Java字段值集合。
|
||||
* @param orderBy 排序字段。
|
||||
* @param relationParam 实体对象数据组装的参数构建器。
|
||||
* @return 检索后的数据列表。
|
||||
*/
|
||||
<T> List<M> getInListWithRelation(
|
||||
String inFilterField, Set<T> inFilterValues, String orderBy, MyRelationParam relationParam);
|
||||
|
||||
/**
|
||||
* 用参数对象作为过滤条件,获取数据数量。
|
||||
*
|
||||
|
||||
@@ -5,10 +5,13 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import com.flow.demo.common.core.constant.AppDeviceType;
|
||||
import com.flow.demo.common.core.validator.AddGroup;
|
||||
import com.flow.demo.common.core.validator.UpdateGroup;
|
||||
|
||||
import javax.validation.ConstraintViolation;
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.groups.Default;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -109,11 +112,75 @@ public class MyCommonUtil {
|
||||
* @return 没有错误返回null,否则返回具体的错误信息。
|
||||
*/
|
||||
public static <T> String getModelValidationError(T model, Class<?>...groups) {
|
||||
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR.validate(model, groups);
|
||||
if (!constraintViolations.isEmpty()) {
|
||||
Iterator<ConstraintViolation<T>> it = constraintViolations.iterator();
|
||||
ConstraintViolation<T> constraint = it.next();
|
||||
return constraint.getMessage();
|
||||
if (model != null) {
|
||||
Set<ConstraintViolation<T>> constraintViolations = VALIDATOR.validate(model, groups);
|
||||
if (!constraintViolations.isEmpty()) {
|
||||
Iterator<ConstraintViolation<T>> it = constraintViolations.iterator();
|
||||
ConstraintViolation<T> constraint = it.next();
|
||||
return constraint.getMessage();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型对象是否通过校验,没有通过返回具体的校验错误信息。
|
||||
*
|
||||
* @param model 带校验的model。
|
||||
* @param forUpdate 是否为更新。
|
||||
* @return 没有错误返回null,否则返回具体的错误信息。
|
||||
*/
|
||||
public static <T> String getModelValidationError(T model, boolean forUpdate) {
|
||||
if (model != null) {
|
||||
Set<ConstraintViolation<T>> constraintViolations;
|
||||
if (forUpdate) {
|
||||
constraintViolations = VALIDATOR.validate(model, Default.class, UpdateGroup.class);
|
||||
} else {
|
||||
constraintViolations = VALIDATOR.validate(model, Default.class, AddGroup.class);
|
||||
}
|
||||
if (!constraintViolations.isEmpty()) {
|
||||
Iterator<ConstraintViolation<T>> it = constraintViolations.iterator();
|
||||
ConstraintViolation<T> constraint = it.next();
|
||||
return constraint.getMessage();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型对象是否通过校验,没有通过返回具体的校验错误信息。
|
||||
*
|
||||
* @param modelList 带校验的model列表。
|
||||
* @param groups Validate绑定的校验组。
|
||||
* @return 没有错误返回null,否则返回具体的错误信息。
|
||||
*/
|
||||
public static <T> String getModelValidationError(List<T> modelList, Class<?>... groups) {
|
||||
if (CollUtil.isNotEmpty(modelList)) {
|
||||
for (T model : modelList) {
|
||||
String errorMessage = getModelValidationError(model, groups);
|
||||
if (StrUtil.isNotBlank(errorMessage)) {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断模型对象是否通过校验,没有通过返回具体的校验错误信息。
|
||||
*
|
||||
* @param modelList 带校验的model列表。
|
||||
* @param forUpdate 是否为更新。
|
||||
* @return 没有错误返回null,否则返回具体的错误信息。
|
||||
*/
|
||||
public static <T> String getModelValidationError(List<T> modelList, boolean forUpdate) {
|
||||
if (CollUtil.isNotEmpty(modelList)) {
|
||||
for (T model : modelList) {
|
||||
String errorMessage = getModelValidationError(model, forUpdate);
|
||||
if (StrUtil.isNotBlank(errorMessage)) {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,9 @@ public class MyModelUtil {
|
||||
* @return copy后的目标类型对象集合。
|
||||
*/
|
||||
public static <S, T> List<T> copyCollectionTo(Collection<S> sourceCollection, Class<T> targetClazz) {
|
||||
if (sourceCollection == null) {
|
||||
return null;
|
||||
}
|
||||
List<T> targetList = new LinkedList<>();
|
||||
if (CollectionUtils.isNotEmpty(sourceCollection)) {
|
||||
for (S source : sourceCollection) {
|
||||
|
||||
@@ -17,13 +17,13 @@ public class DataFilterProperties {
|
||||
/**
|
||||
* 是否启用租户过滤。
|
||||
*/
|
||||
@Value("${datafilter.tenant.enabled}")
|
||||
@Value("${datafilter.tenant.enabled:false}")
|
||||
private Boolean enabledTenantFilter;
|
||||
|
||||
/**
|
||||
* 是否启动数据权限过滤。
|
||||
*/
|
||||
@Value("${datafilter.dataperm.enabled}")
|
||||
@Value("${datafilter.dataperm.enabled:false}")
|
||||
private Boolean enabledDataPermFilter;
|
||||
|
||||
/**
|
||||
|
||||
@@ -85,8 +85,7 @@ public class MybatisDataFilterInterceptor implements Interceptor {
|
||||
if (proxy == null) {
|
||||
proxy = ReflectUtil.getFieldValue(mapperProxy, "CGLIB$CALLBACK_0");
|
||||
}
|
||||
Class<?> mapperClass =
|
||||
(Class<?>) ReflectUtil.getFieldValue(proxy, "mapperInterface");
|
||||
Class<?> mapperClass = (Class<?>) ReflectUtil.getFieldValue(proxy, "mapperInterface");
|
||||
if (properties.getEnabledTenantFilter()) {
|
||||
loadTenantFilterData(mapperClass);
|
||||
}
|
||||
@@ -111,8 +110,7 @@ public class MybatisDataFilterInterceptor implements Interceptor {
|
||||
tenantInfo.setFieldName(field.getName());
|
||||
tenantInfo.setColumnName(MyModelUtil.mapToColumnName(field, modelClass));
|
||||
// 判断当前dao中是否包括不需要自动注入租户Id过滤的方法。
|
||||
DisableTenantFilter disableTenantFilter =
|
||||
mapperClass.getAnnotation(DisableTenantFilter.class);
|
||||
DisableTenantFilter disableTenantFilter = mapperClass.getAnnotation(DisableTenantFilter.class);
|
||||
if (disableTenantFilter != null) {
|
||||
// 这里开始获取当前Mapper已经声明的的SqlId中,有哪些是需要排除在外的。
|
||||
// 排除在外的将不进行数据过滤。
|
||||
@@ -280,16 +278,14 @@ public class MybatisDataFilterInterceptor implements Interceptor {
|
||||
String dataPermSessionKey = RedisKeyUtil.makeSessionDataPermIdKey(tokenData.getSessionId());
|
||||
String dataPermData = redissonClient.getBucket(dataPermSessionKey).get().toString();
|
||||
if (StringUtils.isBlank(dataPermData)) {
|
||||
throw new NoDataPermException(
|
||||
"No Related DataPerm found for SQL_ID [ " + sqlId + " ].");
|
||||
throw new NoDataPermException("No Related DataPerm found for SQL_ID [ " + sqlId + " ].");
|
||||
}
|
||||
Map<Integer, String> dataPermMap = new HashMap<>(8);
|
||||
for (Map.Entry<String, Object> entry : JSON.parseObject(dataPermData).entrySet()) {
|
||||
dataPermMap.put(Integer.valueOf(entry.getKey()), entry.getValue().toString());
|
||||
}
|
||||
if (MapUtils.isEmpty(dataPermMap)) {
|
||||
throw new NoDataPermException(
|
||||
"No Related DataPerm found for SQL_ID [ " + sqlId + " ].");
|
||||
throw new NoDataPermException("No Related DataPerm found for SQL_ID [ " + sqlId + " ].");
|
||||
}
|
||||
if (dataPermMap.containsKey(DataPermRuleType.TYPE_ALL)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.flow.demo.common.flow.base.service;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.flow.demo.common.core.base.service.BaseService;
|
||||
import com.flow.demo.common.flow.constant.FlowApprovalType;
|
||||
import com.flow.demo.common.flow.constant.FlowTaskStatus;
|
||||
import com.flow.demo.common.flow.model.FlowTaskComment;
|
||||
import com.flow.demo.common.flow.service.FlowApiService;
|
||||
import com.flow.demo.common.flow.service.FlowWorkOrderService;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class BaseFlowService<M, K extends Serializable> extends BaseService<M, K> {
|
||||
|
||||
@Autowired
|
||||
private FlowApiService flowApiService;
|
||||
@Autowired
|
||||
private FlowWorkOrderService flowWorkOrderService;
|
||||
|
||||
public void startAndTakeFirst(
|
||||
String processDefinitionId, K dataId, FlowTaskComment comment, JSONObject variables) {
|
||||
ProcessInstance instance = flowApiService.startAndTakeFirst(
|
||||
processDefinitionId, dataId, comment, variables);
|
||||
flowWorkOrderService.saveNew(instance, dataId, null);
|
||||
}
|
||||
|
||||
public void takeFirstTask(
|
||||
String processInstanceId, String taskId, K dataId, FlowTaskComment comment, JSONObject variables) {
|
||||
Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId);
|
||||
flowApiService.setBusinessKeyForProcessInstance(processInstanceId, dataId);
|
||||
flowApiService.completeTask(task, comment, variables);
|
||||
ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId);
|
||||
flowWorkOrderService.saveNew(instance, dataId, null);
|
||||
}
|
||||
|
||||
public void takeTask(Task task, K dataId, FlowTaskComment comment, JSONObject variables) {
|
||||
int flowStatus = FlowTaskStatus.APPROVING;
|
||||
if (comment.getApprovalType().equals(FlowApprovalType.REFUSE)) {
|
||||
flowStatus = FlowTaskStatus.REFUSED;
|
||||
}
|
||||
flowWorkOrderService.updateFlowStatusByBusinessKey(dataId.toString(), flowStatus);
|
||||
flowApiService.completeTask(task, comment, variables);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import lombok.Data;
|
||||
public class FlowWorkOrderDto {
|
||||
|
||||
/**
|
||||
* 流程状态。
|
||||
* 流程状态。参考FlowTaskStatus常量值对象。
|
||||
*/
|
||||
private Integer flowStatus;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ public class FlowWorkOrder {
|
||||
private String businessKey;
|
||||
|
||||
/**
|
||||
* 流程状态。
|
||||
* 流程状态。参考FlowTaskStatus常量值对象。
|
||||
*/
|
||||
@TableField(value = "flow_status")
|
||||
private Integer flowStatus;
|
||||
|
||||
@@ -57,6 +57,14 @@ public interface FlowWorkOrderService extends IBaseService<FlowWorkOrder, Long>
|
||||
*/
|
||||
List<FlowWorkOrder> getFlowWorkOrderListWithRelation(FlowWorkOrder filter, String orderBy);
|
||||
|
||||
/**
|
||||
* 根据流程实例Id,查询关联的工单对象。
|
||||
*
|
||||
* @param processInstanceId 流程实例Id。
|
||||
* @return 工作流工单对象。
|
||||
*/
|
||||
FlowWorkOrder getFlowWorkOrderByProcessInstanceId(String processInstanceId);
|
||||
|
||||
/**
|
||||
* 根据业务主键,查询是否存在指定的工单。
|
||||
*
|
||||
@@ -66,6 +74,13 @@ public interface FlowWorkOrderService extends IBaseService<FlowWorkOrder, Long>
|
||||
*/
|
||||
boolean existByBusinessKey(Object businessKey, boolean unfinished);
|
||||
|
||||
/**
|
||||
* 根据业务数据的主键Id,更新流程状态。
|
||||
* @param businessKey 业务数据主键Id。
|
||||
* @param flowStatus 新的流程状态值。
|
||||
*/
|
||||
void updateFlowStatusByBusinessKey(String businessKey, int flowStatus);
|
||||
|
||||
/**
|
||||
* 根据流程实例Id,更新流程状态。
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.flow.demo.common.flow.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.flow.demo.common.core.base.dao.BaseDaoMapper;
|
||||
import com.flow.demo.common.core.constant.GlobalDeletedFlag;
|
||||
import com.flow.demo.common.core.object.MyRelationParam;
|
||||
@@ -109,6 +110,13 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
|
||||
return resultList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlowWorkOrder getFlowWorkOrderByProcessInstanceId(String processInstanceId) {
|
||||
FlowWorkOrder filter = new FlowWorkOrder();
|
||||
filter.setProcessInstanceId(processInstanceId);
|
||||
return flowWorkOrderMapper.selectOne(new QueryWrapper<>(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existByBusinessKey(Object businessKey, boolean unfinished) {
|
||||
LambdaQueryWrapper<FlowWorkOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
@@ -120,6 +128,20 @@ public class FlowWorkOrderServiceImpl extends BaseService<FlowWorkOrder, Long> i
|
||||
return flowWorkOrderMapper.selectCount(queryWrapper) > 0;
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void updateFlowStatusByBusinessKey(String businessKey, int flowStatus) {
|
||||
FlowWorkOrder flowWorkOrder = new FlowWorkOrder();
|
||||
flowWorkOrder.setFlowStatus(flowStatus);
|
||||
if (FlowTaskStatus.FINISHED != flowStatus) {
|
||||
flowWorkOrder.setUpdateTime(new Date());
|
||||
flowWorkOrder.setUpdateUserId(TokenData.takeFromRequest().getUserId());
|
||||
}
|
||||
LambdaQueryWrapper<FlowWorkOrder> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(FlowWorkOrder::getBusinessKey, businessKey);
|
||||
flowWorkOrderMapper.update(flowWorkOrder, queryWrapper);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void updateFlowStatusByProcessInstanceId(String processInstanceId, int flowStatus) {
|
||||
|
||||
@@ -7,11 +7,14 @@ import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.flow.demo.common.core.constant.ErrorCodeEnum;
|
||||
import com.flow.demo.common.core.object.CallResult;
|
||||
import com.flow.demo.common.core.object.ResponseResult;
|
||||
import com.flow.demo.common.core.object.TokenData;
|
||||
import com.flow.demo.common.core.util.MyModelUtil;
|
||||
import com.flow.demo.common.flow.constant.FlowApprovalType;
|
||||
import com.flow.demo.common.flow.constant.FlowConstant;
|
||||
import com.flow.demo.common.flow.constant.FlowTaskStatus;
|
||||
import com.flow.demo.common.flow.dto.FlowTaskCommentDto;
|
||||
import com.flow.demo.common.flow.dto.FlowWorkOrderDto;
|
||||
import com.flow.demo.common.flow.model.FlowEntry;
|
||||
import com.flow.demo.common.flow.model.FlowEntryPublish;
|
||||
@@ -22,8 +25,11 @@ import com.flow.demo.common.flow.service.FlowEntryService;
|
||||
import com.flow.demo.common.flow.vo.FlowWorkOrderVo;
|
||||
import com.flow.demo.common.flow.vo.TaskInfoVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.flowable.engine.history.HistoricProcessInstance;
|
||||
import org.flowable.engine.runtime.ProcessInstance;
|
||||
import org.flowable.task.api.Task;
|
||||
import org.flowable.task.api.TaskInfo;
|
||||
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -71,6 +77,103 @@ public class FlowOperationHelper {
|
||||
return ResponseResult.success(flowEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流静态表单的参数验证工具方法。根据流程定义标识,获取关联的流程并对其进行合法性验证。
|
||||
*
|
||||
* @param processDefinitionKey 流程定义标识。
|
||||
* @return 返回流程对象。
|
||||
*/
|
||||
public ResponseResult<FlowEntry> verifyFullAndGetFlowEntry(String processDefinitionKey) {
|
||||
String errorMessage;
|
||||
// 验证流程管理数据状态的合法性。
|
||||
ResponseResult<FlowEntry> flowEntryResult = this.verifyAndGetFlowEntry(processDefinitionKey);
|
||||
if (!flowEntryResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(flowEntryResult);
|
||||
}
|
||||
// 验证流程一个用户任务的合法性。
|
||||
FlowEntryPublish flowEntryPublish = flowEntryResult.getData().getMainFlowEntryPublish();
|
||||
if (!flowEntryPublish.getActiveStatus()) {
|
||||
errorMessage = "数据验证失败,当前流程发布对象已被挂起,不能启动新流程!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
ResponseResult<TaskInfoVo> taskInfoResult =
|
||||
this.verifyAndGetInitialTaskInfo(flowEntryPublish, true);
|
||||
if (!taskInfoResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(taskInfoResult);
|
||||
}
|
||||
return flowEntryResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流静态表单的参数验证工具方法。根据参数验证并获取指定的流程任务对象。
|
||||
*
|
||||
* @param processInstanceId 流程实例Id。
|
||||
* @param taskId 流程任务Id。
|
||||
* @param flowTaskComment 流程审批对象。
|
||||
* @return 验证后的流程任务对象。
|
||||
*/
|
||||
public ResponseResult<Task> verifySubmitAndGetTask(
|
||||
String processInstanceId, String taskId, FlowTaskCommentDto flowTaskComment) {
|
||||
// 验证流程任务的合法性。
|
||||
Task task = flowApiService.getProcessInstanceActiveTask(processInstanceId, taskId);
|
||||
ResponseResult<TaskInfoVo> taskInfoResult = this.verifyAndGetRuntimeTaskInfo(task);
|
||||
if (!taskInfoResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(taskInfoResult);
|
||||
}
|
||||
CallResult assigneeVerifyResult = flowApiService.verifyAssigneeOrCandidateAndClaim(task);
|
||||
if (!assigneeVerifyResult.isSuccess()) {
|
||||
return ResponseResult.errorFrom(assigneeVerifyResult);
|
||||
}
|
||||
ProcessInstance instance = flowApiService.getProcessInstance(processInstanceId);
|
||||
if (StrUtil.isBlank(instance.getBusinessKey())) {
|
||||
return ResponseResult.success(task);
|
||||
}
|
||||
String errorMessage;
|
||||
if (StrUtil.equals(flowTaskComment.getApprovalType(), FlowApprovalType.TRANSFER)) {
|
||||
if (StrUtil.isBlank(flowTaskComment.getDelegateAssginee())) {
|
||||
errorMessage = "数据验证失败,加签或转办任务指派人不能为空!!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
}
|
||||
return ResponseResult.success(task);
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流静态表单的参数验证工具方法。根据参数验证并获取指定的历史流程实例对象。
|
||||
* 仅当登录用户为任务的分配人时,才能通过验证。
|
||||
*
|
||||
* @param processInstanceId 历史流程实例Id。
|
||||
* @param taskId 历史流程任务Id。
|
||||
* @return 验证后并返回的历史流程实例对象。
|
||||
*/
|
||||
public ResponseResult<HistoricProcessInstance> verifyAndHistoricProcessInstance(String processInstanceId, String taskId) {
|
||||
String errorMessage;
|
||||
// 验证流程实例的合法性。
|
||||
HistoricProcessInstance instance = flowApiService.getHistoricProcessInstance(processInstanceId);
|
||||
if (instance == null) {
|
||||
errorMessage = "数据验证失败,指定的流程实例Id并不存在,请刷新后重试!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
String loginName = TokenData.takeFromRequest().getLoginName();
|
||||
if (StrUtil.isBlank(taskId)) {
|
||||
if (!StrUtil.equals(loginName, instance.getStartUserId())) {
|
||||
errorMessage = "数据验证失败,指定历史流程的发起人与当前用户不匹配!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
} else {
|
||||
HistoricTaskInstance taskInstance = flowApiService.getHistoricTaskInstance(processInstanceId, taskId);
|
||||
if (taskInstance == null) {
|
||||
errorMessage = "数据验证失败,指定的任务Id并不存在,请刷新后重试!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
if (!StrUtil.equals(loginName, taskInstance.getAssignee())) {
|
||||
errorMessage = "数据验证失败,历史任务的指派人与当前用户不匹配!";
|
||||
return ResponseResult.error(ErrorCodeEnum.DATA_VALIDATED_FAILED, errorMessage);
|
||||
}
|
||||
}
|
||||
return ResponseResult.success(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并获取流程的实时任务信息。
|
||||
*
|
||||
|
||||
@@ -51,7 +51,7 @@ public class FlowWorkOrderVo {
|
||||
private String businessKey;
|
||||
|
||||
/**
|
||||
* 流程状态。
|
||||
* 流程状态。参考FlowTaskStatus常量值对象。
|
||||
*/
|
||||
private Integer flowStatus;
|
||||
|
||||
|
||||
@@ -117,6 +117,10 @@ public class OnlineColumnServiceImpl extends BaseService<OnlineColumn, Long> imp
|
||||
public void refresh(SqlTableColumn sqlTableColumn, OnlineColumn onlineColumn) {
|
||||
this.evictTableCache(onlineColumn.getTableId());
|
||||
BeanUtil.copyProperties(sqlTableColumn, onlineColumn, false);
|
||||
String objectFieldName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, onlineColumn.getColumnName());
|
||||
onlineColumn.setObjectFieldName(objectFieldName);
|
||||
String objectFieldType = convertToJavaType(onlineColumn.getColumnType());
|
||||
onlineColumn.setObjectFieldType(objectFieldType);
|
||||
onlineColumnMapper.updateById(onlineColumn);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>framework</artifactId>
|
||||
<groupId>com.flow.demo</groupId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>apidoc-tools</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<name>apidoc-tools</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.flow.demo</groupId>
|
||||
<artifactId>common-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.thoughtworks.qdox</groupId>
|
||||
<artifactId>qdox</artifactId>
|
||||
<version>${qdox.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.flow.demo.apidoc.tools;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeConfig;
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeParser;
|
||||
import com.flow.demo.apidoc.tools.export.ApiPostmanExporter;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ExportApiApp {
|
||||
|
||||
public static void main(String[] args) throws IOException, TemplateException {
|
||||
// 在第一次导出时,需要打开export-api-config.json配置文件,
|
||||
// 修改其中的工程根目录配置项(projectRootPath),其他配置保持不变即可。
|
||||
InputStream in = ExportApiApp.class.getResourceAsStream("/export-api-config.json");
|
||||
String jsonData = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
|
||||
ApiCodeConfig apiCodeConfig = JSON.parseObject(jsonData, ApiCodeConfig.class);
|
||||
ApiCodeParser apiCodeParser = new ApiCodeParser(apiCodeConfig);
|
||||
ApiCodeParser.ApiProject project = apiCodeParser.doParse();
|
||||
ApiPostmanExporter exporter = new ApiPostmanExporter();
|
||||
// 将下面的目录改为实际输出目录。
|
||||
exporter.doGenerate(project, "/xxx/Desktop/1.json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.flow.demo.apidoc.tools;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeConfig;
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeParser;
|
||||
import com.flow.demo.apidoc.tools.export.ApiDocExporter;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class ExportDocApp {
|
||||
|
||||
public static void main(String[] args) throws IOException, TemplateException {
|
||||
// 在第一次导出时,需要打开export-api-config.json配置文件,
|
||||
// 修改其中的工程根目录配置项(projectRootPath),其他配置保持不变即可。
|
||||
InputStream in = ExportDocApp.class.getResourceAsStream("/export-api-config.json");
|
||||
String jsonData = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
|
||||
ApiCodeConfig apiCodeConfig = JSON.parseObject(jsonData, ApiCodeConfig.class);
|
||||
ApiCodeParser apiCodeParser = new ApiCodeParser(apiCodeConfig);
|
||||
ApiCodeParser.ApiProject project = apiCodeParser.doParse();
|
||||
ApiDocExporter exporter = new ApiDocExporter();
|
||||
// 将下面的目录改为实际输出目录。
|
||||
exporter.doGenerate(project, "/xxx/Desktop/2.md");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.flow.demo.apidoc.tools.codeparser;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 解析项目中接口信息的配置对象。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
@Data
|
||||
public class ApiCodeConfig {
|
||||
|
||||
/**
|
||||
* 项目名称。
|
||||
*/
|
||||
private String projectName;
|
||||
/**
|
||||
* 项目的基础包名,如(com.demo.multi)。
|
||||
*/
|
||||
private String basePackage;
|
||||
/**
|
||||
* 项目在本地文件系统中的根目录。这里需要注意的是,Windows用户请务必使用反斜杠作为目录分隔符。
|
||||
* 如:"e:/mypath/OrangeSingleDemo","/Users/xxx/OrangeSingleDemo"。
|
||||
*/
|
||||
private String projectRootPath;
|
||||
/**
|
||||
* 是否为微服务项目。
|
||||
*/
|
||||
private Boolean microService;
|
||||
/**
|
||||
* 服务配置列表。对于单体服务,至少也会有一个ServiceConfig对象。
|
||||
*/
|
||||
private List<ServiceConfig> serviceList;
|
||||
|
||||
@Data
|
||||
public static class ServiceConfig {
|
||||
/**
|
||||
* 服务名称。
|
||||
*/
|
||||
private String serviceName;
|
||||
/**
|
||||
* 服务中文显示名称。
|
||||
*/
|
||||
private String showName;
|
||||
/**
|
||||
* 服务所在目录,相对于工程目录的子目录。
|
||||
*/
|
||||
private String servicePath;
|
||||
/**
|
||||
* 仅用于微服务工程。通常为服务路由路径,如:/admin/coursepaper。服务内的接口,都会加上该路径前缀。
|
||||
*/
|
||||
private String serviceRequestPath;
|
||||
/**
|
||||
* 服务的端口号。
|
||||
*/
|
||||
private String port;
|
||||
/**
|
||||
* Api Controller信息列表。
|
||||
*/
|
||||
private List<ControllerInfo> controllerInfoList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ControllerInfo {
|
||||
/**
|
||||
* Controller.java等接口文件的所在目录。该目录仅为相对于服务代码目录的子目录。
|
||||
* 目录分隔符请务必使用反斜杠。如:"/com/orange/demo/app/controller"。
|
||||
*/
|
||||
private String path;
|
||||
/**
|
||||
* 如果一个服务内,存在多个Controller目录,将再次生成二级子目录,目录名为groupName。(可使用中文)
|
||||
*/
|
||||
private String groupName;
|
||||
/**
|
||||
* 在当前Controller目录下,需要忽略的Controller列表 (只写类名即可)。如:LoginController。
|
||||
*/
|
||||
private Set<String> skipControllers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,672 @@
|
||||
package com.flow.demo.apidoc.tools.codeparser;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.flow.demo.common.core.object.Tuple2;
|
||||
import com.flow.demo.apidoc.tools.exception.ApiCodeConfigParseException;
|
||||
import com.thoughtworks.qdox.JavaProjectBuilder;
|
||||
import com.thoughtworks.qdox.model.*;
|
||||
import com.thoughtworks.qdox.model.impl.DefaultJavaParameterizedType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 解析项目中的接口信息,以及关联的Model、Dto和Mapper,主要用于生成接口文档。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class ApiCodeParser {
|
||||
|
||||
private static final String PATH_SEPERATOR = "/";
|
||||
private static final String REQUEST_MAPPING = "RequestMapping";
|
||||
private static final String FULL_REQUEST_MAPPING = "org.springframework.web.bind.annotation.RequestMapping";
|
||||
private static final String GET_MAPPING = "GetMapping";
|
||||
private static final String FULL_GET_MAPPING = "org.springframework.web.bind.annotation.GetMapping";
|
||||
private static final String POST_MAPPING = "PostMapping";
|
||||
private static final String FULL_POST_MAPPING = "org.springframework.web.bind.annotation.PostMapping";
|
||||
private static final String VALUE_PROP = "value";
|
||||
private static final String REQUIRED_PROP = "required";
|
||||
private static final String DELETED_COLUMN = "DeletedFlagColumn";
|
||||
|
||||
/**
|
||||
* 忽略微服务间标准调用接口的导出。
|
||||
*/
|
||||
private static final Set<String> IGNORED_API_METHOD_SET = new HashSet<>(8);
|
||||
|
||||
static {
|
||||
IGNORED_API_METHOD_SET.add("listByIds");
|
||||
IGNORED_API_METHOD_SET.add("getById");
|
||||
IGNORED_API_METHOD_SET.add("existIds");
|
||||
IGNORED_API_METHOD_SET.add("existId");
|
||||
IGNORED_API_METHOD_SET.add("deleteById");
|
||||
IGNORED_API_METHOD_SET.add("deleteBy");
|
||||
IGNORED_API_METHOD_SET.add("listBy");
|
||||
IGNORED_API_METHOD_SET.add("listMapBy");
|
||||
IGNORED_API_METHOD_SET.add("listByNotInList");
|
||||
IGNORED_API_METHOD_SET.add("getBy");
|
||||
IGNORED_API_METHOD_SET.add("countBy");
|
||||
IGNORED_API_METHOD_SET.add("aggregateBy");
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础配置。
|
||||
*/
|
||||
private ApiCodeConfig config;
|
||||
/**
|
||||
* 工程对象。
|
||||
*/
|
||||
private ApiProject apiProject;
|
||||
/**
|
||||
* 项目中所有的解析后Java文件,key是Java对象的全名,如:com.flow.demo.xxxx.Student。
|
||||
*/
|
||||
private final Map<String, JavaClass> projectJavaClassMap = new HashMap<>(128);
|
||||
/**
|
||||
* 存储服务数据。key为配置的serviceName。
|
||||
*/
|
||||
private final Map<String, InternalServiceData> serviceDataMap = new HashMap<>(8);
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param config 配置对象。
|
||||
*/
|
||||
public ApiCodeParser(ApiCodeConfig config) {
|
||||
this.config = config;
|
||||
// 验证配置中的数据是否正确,出现错误直接抛出运行时异常。
|
||||
this.verifyConfigData();
|
||||
// 将配置文件中所有目录相关的参数,全部规格化处理,后续的使用中不用再做处理了。
|
||||
this.normalizeConfigPath();
|
||||
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
|
||||
InternalServiceData serviceData = new InternalServiceData();
|
||||
// 仅有微服务项目,需要添加服务路由路径。
|
||||
if (StrUtil.isNotBlank(serviceConfig.getServiceRequestPath())) {
|
||||
String serviceRequestPath = "";
|
||||
if (!serviceRequestPath.equals(PATH_SEPERATOR)) {
|
||||
serviceRequestPath = normalizePath(serviceConfig.getServiceRequestPath());
|
||||
}
|
||||
serviceData.setServiceRequestPath(serviceRequestPath);
|
||||
}
|
||||
serviceDataMap.put(serviceConfig.getServiceName(), serviceData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行解析操作。
|
||||
*
|
||||
* @return 解析后的工程对象。
|
||||
*/
|
||||
public ApiProject doParse() throws IOException {
|
||||
// 先把工程完整编译一遍,以便工程内的Java对象的引用信息更加完整。
|
||||
this.parseProject();
|
||||
// 开始逐级推演。
|
||||
apiProject = new ApiProject();
|
||||
apiProject.setProjectName(config.getProjectName());
|
||||
apiProject.setMicroService(config.getMicroService());
|
||||
apiProject.setServiceList(new LinkedList<>());
|
||||
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
|
||||
ApiService apiService = this.parseService(serviceConfig);
|
||||
apiProject.getServiceList().add(apiService);
|
||||
}
|
||||
return apiProject;
|
||||
}
|
||||
|
||||
private void parseProject() throws IOException {
|
||||
JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder();
|
||||
javaProjectBuilder.setEncoding(StandardCharsets.UTF_8.name());
|
||||
javaProjectBuilder.addSourceTree(new File(config.getProjectRootPath()));
|
||||
// 全部导入,便于后续解析中使用和检索。
|
||||
for (JavaClass javaClass : javaProjectBuilder.getClasses()) {
|
||||
projectJavaClassMap.put(javaClass.getFullyQualifiedName(), javaClass);
|
||||
}
|
||||
}
|
||||
|
||||
private ApiService parseService(ApiCodeConfig.ServiceConfig serviceConfig) {
|
||||
InternalServiceData serviceData = serviceDataMap.get(serviceConfig.getServiceName());
|
||||
ApiService apiService = new ApiService();
|
||||
apiService.setServiceName(serviceConfig.getServiceName());
|
||||
apiService.setShowName(serviceConfig.getShowName());
|
||||
apiService.setPort(serviceConfig.getPort());
|
||||
List<ApiCodeConfig.ControllerInfo> controllerInfoList = serviceConfig.getControllerInfoList();
|
||||
// 准备解析接口文件
|
||||
for (ApiCodeConfig.ControllerInfo controllerInfo : controllerInfoList) {
|
||||
JavaProjectBuilder javaControllerBuilder = new JavaProjectBuilder();
|
||||
javaControllerBuilder.addSourceTree(new File(controllerInfo.getPath()));
|
||||
for (JavaClass javaClass : javaControllerBuilder.getClasses()) {
|
||||
if (controllerInfo.getSkipControllers() != null
|
||||
&& controllerInfo.getSkipControllers().contains(javaClass.getName())) {
|
||||
continue;
|
||||
}
|
||||
ApiClass apiClass = this.parseApiClass(controllerInfo, javaClass.getFullyQualifiedName(), serviceData);
|
||||
if (apiClass != null) {
|
||||
// 如果配置中,为当前ControllerInfo添加了groupName属性,
|
||||
// 所有的生成后接口都会位于serviceName/groupName子目录,否则,都直接位于当前服务的子目录。
|
||||
if (StrUtil.isBlank(apiClass.getGroupName())) {
|
||||
apiService.getDefaultGroupClassSet().add(apiClass);
|
||||
} else {
|
||||
Set<ApiClass> groupedClassList = apiService.getGroupedClassMap()
|
||||
.computeIfAbsent(apiClass.getGroupName(), k -> new TreeSet<>());
|
||||
groupedClassList.add(apiClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiService;
|
||||
}
|
||||
|
||||
private ApiClass parseApiClass(
|
||||
ApiCodeConfig.ControllerInfo controllerInfo,
|
||||
String classFullname,
|
||||
InternalServiceData serviceData) {
|
||||
// 去包含工程全部Class的Map中,找到当前ControllerClass。
|
||||
// 之所以这样做,主要是因为全工程分析controller文件,会包含更多更精确的对象关联信息。
|
||||
JavaClass controllerClass = this.projectJavaClassMap.get(classFullname);
|
||||
List<JavaAnnotation> classAnnotations = controllerClass.getAnnotations();
|
||||
boolean hasControllerAnnotation = false;
|
||||
String requestPath = "";
|
||||
for (JavaAnnotation annotation : classAnnotations) {
|
||||
String annotationName = annotation.getType().getValue();
|
||||
if (this.isRequestMapping(annotationName) && annotation.getNamedParameter(VALUE_PROP) != null) {
|
||||
requestPath = StrUtil.removeAll(
|
||||
annotation.getNamedParameter(VALUE_PROP).toString(), "\"");
|
||||
if (requestPath.equals(PATH_SEPERATOR) || StrUtil.isBlank(requestPath)) {
|
||||
requestPath = "";
|
||||
} else {
|
||||
requestPath = normalizePath(requestPath);
|
||||
}
|
||||
}
|
||||
if (isController(annotationName)) {
|
||||
hasControllerAnnotation = true;
|
||||
}
|
||||
}
|
||||
if (!hasControllerAnnotation) {
|
||||
return null;
|
||||
}
|
||||
requestPath = serviceData.getServiceRequestPath() + requestPath;
|
||||
ApiClass apiClass = new ApiClass();
|
||||
apiClass.setName(controllerClass.getName());
|
||||
apiClass.setFullName(controllerClass.getFullyQualifiedName());
|
||||
apiClass.setComment(controllerClass.getComment());
|
||||
apiClass.setGroupName(controllerInfo.getGroupName());
|
||||
apiClass.setRequestPath(requestPath);
|
||||
List<ApiMethod> methodList = this.parseApiMethodList(apiClass, controllerClass);
|
||||
apiClass.setMethodList(methodList);
|
||||
return apiClass;
|
||||
}
|
||||
|
||||
private boolean needToIgnore(JavaMethod method) {
|
||||
return !method.isPublic() || method.isStatic() || IGNORED_API_METHOD_SET.contains(method.getName());
|
||||
}
|
||||
|
||||
private List<ApiMethod> parseApiMethodList(ApiClass apiClass, JavaClass javaClass) {
|
||||
List<ApiMethod> apiMethodList = new LinkedList<>();
|
||||
List<JavaMethod> methodList = javaClass.getMethods();
|
||||
for (JavaMethod method : methodList) {
|
||||
if (this.needToIgnore(method)) {
|
||||
continue;
|
||||
}
|
||||
List<JavaAnnotation> methodAnnotations = method.getAnnotations();
|
||||
Tuple2<String, String> result = this.parseRequestPathAndHttpMethod(methodAnnotations);
|
||||
String methodRequestPath = result.getFirst();
|
||||
String httpMethod = result.getSecond();
|
||||
if (StrUtil.isNotBlank(methodRequestPath)) {
|
||||
ApiMethod apiMethod = new ApiMethod();
|
||||
apiMethod.setName(method.getName());
|
||||
apiMethod.setComment(method.getComment());
|
||||
apiMethod.setHttpMethod(httpMethod);
|
||||
methodRequestPath = StrUtil.removeAll(methodRequestPath, "\"");
|
||||
methodRequestPath = apiClass.getRequestPath() + normalizePath(methodRequestPath);
|
||||
apiMethod.setRequestPath(methodRequestPath);
|
||||
apiMethod.setPathList(StrUtil.splitTrim(apiMethod.getRequestPath(), PATH_SEPERATOR));
|
||||
if (apiMethod.getRequestPath().contains("/listDict")) {
|
||||
apiMethod.setListDictUrl(true);
|
||||
} else if (apiMethod.getRequestPath().endsWith("/list")
|
||||
|| apiMethod.getRequestPath().endsWith("/listWithGroup")
|
||||
|| apiMethod.getRequestPath().contains("/listNotIn")
|
||||
|| apiMethod.getRequestPath().contains("/list")) {
|
||||
apiMethod.setListUrl(true);
|
||||
} else if (apiMethod.getRequestPath().contains("/doLogin")) {
|
||||
apiMethod.setLoginUrl(true);
|
||||
}
|
||||
JavaClass returnClass = method.getReturns();
|
||||
if (returnClass.isVoid()) {
|
||||
apiMethod.setReturnString("void");
|
||||
} else {
|
||||
apiMethod.setReturnString(returnClass.getGenericValue());
|
||||
}
|
||||
apiMethodList.add(apiMethod);
|
||||
List<ApiArgument> apiArgumentList = this.parseApiMethodArgumentList(method);
|
||||
apiMethod.setArgumentList(apiArgumentList);
|
||||
this.classifyArgumentList(apiMethod, apiArgumentList);
|
||||
}
|
||||
}
|
||||
return apiMethodList;
|
||||
}
|
||||
|
||||
private void classifyArgumentList(ApiMethod apiMethod, List<ApiArgument> apiArgumentList) {
|
||||
for (ApiArgument arg : apiArgumentList) {
|
||||
if (arg.getAnnotationType() == ApiArgumentAnnotationType.REQUEST_PARAM) {
|
||||
if (arg.uploadFileParam) {
|
||||
apiMethod.getUploadParamArgumentList().add(arg);
|
||||
} else {
|
||||
apiMethod.getQueryParamArgumentList().add(arg);
|
||||
}
|
||||
}
|
||||
if (arg.getAnnotationType() != ApiArgumentAnnotationType.REQUEST_PARAM) {
|
||||
apiMethod.getJsonParamArgumentList().add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Tuple2<String, String> parseRequestPathAndHttpMethod(List<JavaAnnotation> methodAnnotations) {
|
||||
for (JavaAnnotation annotation : methodAnnotations) {
|
||||
String annotationName = annotation.getType().getValue();
|
||||
if (GET_MAPPING.equals(annotationName) || FULL_GET_MAPPING.equals(annotationName)) {
|
||||
String methodRequestPath = annotation.getNamedParameter(VALUE_PROP).toString();
|
||||
String httpMethod = "GET";
|
||||
return new Tuple2<>(methodRequestPath, httpMethod);
|
||||
}
|
||||
if (POST_MAPPING.equals(annotationName) || FULL_POST_MAPPING.equals(annotationName)) {
|
||||
String methodRequestPath = annotation.getNamedParameter(VALUE_PROP).toString();
|
||||
String httpMethod = "POST";
|
||||
return new Tuple2<>(methodRequestPath, httpMethod);
|
||||
}
|
||||
}
|
||||
return new Tuple2<>(null, null);
|
||||
}
|
||||
|
||||
private List<ApiArgument> parseApiMethodArgumentList(JavaMethod javaMethod) {
|
||||
List<ApiArgument> apiArgumentList = new LinkedList<>();
|
||||
List<JavaParameter> parameterList = javaMethod.getParameters();
|
||||
if (CollUtil.isEmpty(parameterList)) {
|
||||
return apiArgumentList;
|
||||
}
|
||||
for (JavaParameter parameter : parameterList) {
|
||||
String typeName = parameter.getType().getValue();
|
||||
// 该类型的参数为Validator的验证结果对象,因此忽略。
|
||||
if ("BindingResult".equals(typeName) || this.isServletArgument(typeName)) {
|
||||
continue;
|
||||
}
|
||||
ApiArgument apiArgument = this.parseApiMethodArgument(parameter);
|
||||
apiArgumentList.add(apiArgument);
|
||||
}
|
||||
return apiArgumentList;
|
||||
}
|
||||
|
||||
private String parseMethodArgmentComment(JavaParameter parameter) {
|
||||
String comment = null;
|
||||
JavaExecutable executable = parameter.getExecutable();
|
||||
List<DocletTag> tags = executable.getTagsByName("param");
|
||||
if (CollUtil.isNotEmpty(tags)) {
|
||||
for (DocletTag tag : tags) {
|
||||
if (tag.getValue().startsWith(parameter.getName())) {
|
||||
comment = StrUtil.removePrefix(tag.getValue(), parameter.getName()).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
}
|
||||
|
||||
private ApiArgument parseApiMethodArgument(JavaParameter parameter) {
|
||||
String typeName = parameter.getType().getValue();
|
||||
ApiArgument apiArgument = new ApiArgument();
|
||||
ApiArgumentAnnotation argumentAnnotation =
|
||||
this.parseArgumentAnnotationTypeAndName(parameter.getAnnotations(), parameter.getName());
|
||||
apiArgument.setAnnotationType(argumentAnnotation.getType());
|
||||
apiArgument.setName(argumentAnnotation.getName());
|
||||
apiArgument.setTypeName(typeName);
|
||||
apiArgument.setFullTypeName(parameter.getFullyQualifiedName());
|
||||
if (argumentAnnotation.getType() == ApiArgumentAnnotationType.REQUEST_PARAM) {
|
||||
apiArgument.setRequired(argumentAnnotation.isRequired());
|
||||
}
|
||||
String comment = parseMethodArgmentComment(parameter);
|
||||
apiArgument.setComment(comment);
|
||||
// 文件上传字段,是必填参数。
|
||||
if ("MultipartFile".equals(typeName)) {
|
||||
apiArgument.setUploadFileParam(true);
|
||||
apiArgument.setRequired(true);
|
||||
return apiArgument;
|
||||
}
|
||||
// 对于内置类型,则无需继续处理了。所有和内置类型参数相关的处理,应该在之前完成。
|
||||
if (this.verifyAndSetBuiltinParam(apiArgument, typeName)) {
|
||||
return apiArgument;
|
||||
}
|
||||
// 判断是否为集合类型的参数。
|
||||
if (this.isCollectionType(typeName)) {
|
||||
apiArgument.setCollectionParam(true);
|
||||
if (parameter.getType() instanceof DefaultJavaParameterizedType) {
|
||||
DefaultJavaParameterizedType javaType = (DefaultJavaParameterizedType) parameter.getType();
|
||||
JavaType genericType = javaType.getActualTypeArguments().get(0);
|
||||
ApiModel apiModel = this.buildApiModelForArgument(genericType.getFullyQualifiedName());
|
||||
apiArgument.setModelData(apiModel);
|
||||
apiArgument.setFullTypeName(parameter.getGenericFullyQualifiedName());
|
||||
apiArgument.setTypeName(parameter.getGenericValue());
|
||||
}
|
||||
} else {
|
||||
ApiModel apiModel = this.buildApiModelForArgument(parameter.getFullyQualifiedName());
|
||||
apiArgument.setModelData(apiModel);
|
||||
}
|
||||
return apiArgument;
|
||||
}
|
||||
|
||||
private boolean verifyAndSetBuiltinParam(ApiArgument apiArgument, String typeName) {
|
||||
if ("MyOrderParam".equals(typeName)) {
|
||||
apiArgument.setOrderParam(true);
|
||||
} else if ("MyPageParam".equals(typeName)) {
|
||||
apiArgument.setPageParam(true);
|
||||
} else if ("MyGroupParam".equals(typeName)) {
|
||||
apiArgument.setGroupParam(true);
|
||||
} else if ("MyQueryParam".equals(typeName)) {
|
||||
apiArgument.setQueryParam(true);
|
||||
} else if ("MyAggregationParam".equals(typeName)) {
|
||||
apiArgument.setAggregationParam(true);
|
||||
}
|
||||
return apiArgument.isOrderParam()
|
||||
|| apiArgument.isPageParam()
|
||||
|| apiArgument.isGroupParam()
|
||||
|| apiArgument.isQueryParam()
|
||||
|| apiArgument.isAggregationParam();
|
||||
}
|
||||
|
||||
private ApiArgumentAnnotation parseArgumentAnnotationTypeAndName(
|
||||
List<JavaAnnotation> annotationList, String defaultName) {
|
||||
ApiArgumentAnnotation argumentAnnotation = new ApiArgumentAnnotation();
|
||||
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_PARAM);
|
||||
argumentAnnotation.setName(defaultName);
|
||||
for (JavaAnnotation annotation : annotationList) {
|
||||
String annotationName = annotation.getType().getValue();
|
||||
if ("RequestBody".equals(annotationName)) {
|
||||
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_BODY);
|
||||
return argumentAnnotation;
|
||||
} else if ("MyRequestBody".equals(annotationName)) {
|
||||
String annotationValue = this.getArgumentNameFromAnnotationValue(annotation, VALUE_PROP);
|
||||
argumentAnnotation.setType(ApiArgumentAnnotationType.MY_REQUEST_BODY);
|
||||
argumentAnnotation.setName(annotationValue != null ? annotationValue : defaultName);
|
||||
return argumentAnnotation;
|
||||
} else if ("RequestParam".equals(annotationName)) {
|
||||
String annotationValue = this.getArgumentNameFromAnnotationValue(annotation, VALUE_PROP);
|
||||
argumentAnnotation.setType(ApiArgumentAnnotationType.REQUEST_PARAM);
|
||||
argumentAnnotation.setName(annotationValue != null ? annotationValue : defaultName);
|
||||
String requiredValue = this.getArgumentNameFromAnnotationValue(annotation, REQUIRED_PROP);
|
||||
if (StrUtil.isNotBlank(requiredValue)) {
|
||||
argumentAnnotation.setRequired(Boolean.parseBoolean(requiredValue));
|
||||
}
|
||||
return argumentAnnotation;
|
||||
}
|
||||
}
|
||||
// 缺省为@RequestParam
|
||||
return argumentAnnotation;
|
||||
}
|
||||
|
||||
private String getArgumentNameFromAnnotationValue(JavaAnnotation annotation, String attribute) {
|
||||
Object value = annotation.getNamedParameter(attribute);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String paramAlias = value.toString();
|
||||
if (StrUtil.isNotBlank(paramAlias)) {
|
||||
paramAlias = StrUtil.removeAll(paramAlias, "\"");
|
||||
}
|
||||
return paramAlias;
|
||||
}
|
||||
|
||||
private ApiModel buildApiModelForArgument(String fullJavaClassName) {
|
||||
// 先从当前服务内的Model中找,如果参数是Model类型的对象,微服务和单体行为一致。
|
||||
ApiModel apiModel = apiProject.getFullNameModelMap().get(fullJavaClassName);
|
||||
if (apiModel != null) {
|
||||
return apiModel;
|
||||
}
|
||||
// 判断工程全局对象映射中是否包括该对象类型,如果不包含,就直接返回了。
|
||||
JavaClass modelClass = projectJavaClassMap.get(fullJavaClassName);
|
||||
if (modelClass == null) {
|
||||
return apiModel;
|
||||
}
|
||||
// 先行解析对象中的字段。
|
||||
apiModel = parseModel(modelClass);
|
||||
apiProject.getFullNameModelMap().put(fullJavaClassName, apiModel);
|
||||
return apiModel;
|
||||
}
|
||||
|
||||
private ApiModel parseModel(JavaClass javaClass) {
|
||||
ApiModel apiModel = new ApiModel();
|
||||
apiModel.setName(javaClass.getName());
|
||||
apiModel.setFullName(javaClass.getFullyQualifiedName());
|
||||
apiModel.setComment(javaClass.getComment());
|
||||
apiModel.setFieldList(new LinkedList<>());
|
||||
List<JavaField> fieldList = javaClass.getFields();
|
||||
for (JavaField field : fieldList) {
|
||||
if (field.isStatic()) {
|
||||
continue;
|
||||
}
|
||||
ApiField apiField = new ApiField();
|
||||
apiField.setName(field.getName());
|
||||
apiField.setComment(field.getComment());
|
||||
apiField.setTypeName(field.getType().getSimpleName());
|
||||
apiModel.getFieldList().add(apiField);
|
||||
}
|
||||
return apiModel;
|
||||
}
|
||||
|
||||
private void verifyConfigData() {
|
||||
if (StrUtil.isBlank(config.getProjectName())) {
|
||||
throw new ApiCodeConfigParseException("ProjectName field can't be EMPTY.");
|
||||
}
|
||||
if (StrUtil.isBlank(config.getBasePackage())) {
|
||||
throw new ApiCodeConfigParseException("BasePackage field can't be EMPTY.");
|
||||
}
|
||||
if (StrUtil.isBlank(config.getProjectRootPath())) {
|
||||
throw new ApiCodeConfigParseException("ProjectRootPath field can't be EMPTY.");
|
||||
}
|
||||
if (!FileUtil.exist(config.getProjectRootPath())) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"ProjectRootPath doesn't exist, please check ./resources/export-api-config.json as DEFAULT.");
|
||||
}
|
||||
if (config.getMicroService() == null) {
|
||||
throw new ApiCodeConfigParseException("MicroService field can't be NULL.");
|
||||
}
|
||||
if (CollUtil.isEmpty(config.getServiceList())) {
|
||||
throw new ApiCodeConfigParseException("ServiceList field can't be EMPTY.");
|
||||
}
|
||||
this.verifyServiceConfig(config.getServiceList());
|
||||
}
|
||||
|
||||
private void verifyServiceConfig(List<ApiCodeConfig.ServiceConfig> serviceConfigList) {
|
||||
Set<String> serviceNameSet = new HashSet<>(8);
|
||||
Set<String> servicePathSet = new HashSet<>(8);
|
||||
for (ApiCodeConfig.ServiceConfig serviceConfig : serviceConfigList) {
|
||||
if (StrUtil.isBlank(serviceConfig.getServiceName())) {
|
||||
throw new ApiCodeConfigParseException("One of the ServiceName Field in Services List is NULL.");
|
||||
}
|
||||
String serviceName = serviceConfig.getServiceName();
|
||||
if (StrUtil.isBlank(serviceConfig.getServicePath())) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"The ServicePath Field in Service [" + serviceName + "] is NULL.");
|
||||
}
|
||||
if (serviceNameSet.contains(serviceName)) {
|
||||
throw new ApiCodeConfigParseException("The ServiceName [" + serviceName + "] is duplicated.");
|
||||
}
|
||||
serviceNameSet.add(serviceName);
|
||||
if (servicePathSet.contains(serviceConfig.getServicePath())) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"The ServicePath [" + serviceConfig.getServicePath() + "] is duplicated.");
|
||||
}
|
||||
servicePathSet.add(serviceConfig.getServicePath());
|
||||
if (StrUtil.isBlank(serviceConfig.getPort())) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"The Port Field in Service [" + serviceName + "] is NULL.");
|
||||
}
|
||||
this.verifyServiceControllerConfig(serviceConfig.getControllerInfoList(), serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyServiceControllerConfig(
|
||||
List<ApiCodeConfig.ControllerInfo> controllerInfoList, String serviceName) {
|
||||
if (CollUtil.isEmpty(controllerInfoList)) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"The ControllerInfoList Field of Service [" + serviceName + "] is EMPTY");
|
||||
}
|
||||
for (ApiCodeConfig.ControllerInfo controllerInfo : controllerInfoList) {
|
||||
if (StrUtil.isBlank(controllerInfo.getPath())) {
|
||||
throw new ApiCodeConfigParseException(
|
||||
"One of the ControllerInfo.Path Field of Service [" + serviceName + "] is EMPTY");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeConfigPath() {
|
||||
config.setProjectRootPath(normalizePath(config.getProjectRootPath()));
|
||||
for (ApiCodeConfig.ServiceConfig serviceConfig : config.getServiceList()) {
|
||||
serviceConfig.setServicePath(config.getProjectRootPath() + normalizePath(serviceConfig.getServicePath()));
|
||||
for (ApiCodeConfig.ControllerInfo controllerInfo : serviceConfig.getControllerInfoList()) {
|
||||
controllerInfo.setPath(serviceConfig.getServicePath() + normalizePath(controllerInfo.getPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizePath(String path) {
|
||||
if (!path.startsWith(PATH_SEPERATOR)) {
|
||||
path = PATH_SEPERATOR + path;
|
||||
}
|
||||
return StrUtil.removeSuffix(path, PATH_SEPERATOR);
|
||||
}
|
||||
|
||||
private boolean isCollectionType(String typeName) {
|
||||
return "List".equals(typeName) || "Set".equals(typeName) || "Collection".equals(typeName);
|
||||
}
|
||||
|
||||
private boolean isServletArgument(String typeName) {
|
||||
return "HttpServletResponse".equals(typeName) || "HttpServletRequest".equals(typeName);
|
||||
}
|
||||
|
||||
private boolean isController(String annotationName) {
|
||||
return "Controller".equals(annotationName)
|
||||
|| "org.springframework.stereotype.Controller".equals(annotationName)
|
||||
|| "RestController".equals(annotationName)
|
||||
|| "org.springframework.web.bind.annotation.RestController".equals(annotationName);
|
||||
}
|
||||
|
||||
private boolean isRequiredColumn(String annotationName) {
|
||||
return "NotNull".equals(annotationName)
|
||||
|| "javax.validation.constraints.NotNull".equals(annotationName)
|
||||
|| "NotBlank".equals(annotationName)
|
||||
|| "javax.validation.constraints.NotBlank".equals(annotationName)
|
||||
|| "NotEmpty".equals(annotationName)
|
||||
|| "javax.validation.constraints.NotEmpty".equals(annotationName);
|
||||
}
|
||||
|
||||
private boolean isRequestMapping(String name) {
|
||||
return REQUEST_MAPPING.equals(name) || FULL_REQUEST_MAPPING.equals(name);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiProject {
|
||||
private String projectName;
|
||||
private Boolean microService;
|
||||
private List<ApiService> serviceList;
|
||||
private Map<String, ApiModel> fullNameModelMap = new HashMap<>(32);
|
||||
private Map<String, ApiModel> simpleNameModelMap = new HashMap<>(32);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiService {
|
||||
private String serviceName;
|
||||
private String showName;
|
||||
private String port;
|
||||
private Set<ApiClass> defaultGroupClassSet = new TreeSet<>();
|
||||
private Map<String, Set<ApiClass>> groupedClassMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiClass implements Comparable<ApiClass> {
|
||||
private String name;
|
||||
private String fullName;
|
||||
private String groupName;
|
||||
private String comment;
|
||||
private String requestPath;
|
||||
private List<ApiMethod> methodList;
|
||||
|
||||
@Override
|
||||
public int compareTo(ApiClass o) {
|
||||
return this.name.compareTo(o.name);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiMethod {
|
||||
private String name;
|
||||
private String comment;
|
||||
private String returnString;
|
||||
private String requestPath;
|
||||
private String httpMethod;
|
||||
private boolean listDictUrl = false;
|
||||
private boolean listUrl = false;
|
||||
private boolean loginUrl = false;
|
||||
private List<String> pathList = new LinkedList<>();
|
||||
private List<ApiArgument> argumentList;
|
||||
private List<ApiArgument> queryParamArgumentList = new LinkedList<>();
|
||||
private List<ApiArgument> jsonParamArgumentList = new LinkedList<>();
|
||||
private List<ApiArgument> uploadParamArgumentList = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiArgument {
|
||||
private String name;
|
||||
private String typeName;
|
||||
private String fullTypeName;
|
||||
private String comment;
|
||||
private Integer annotationType;
|
||||
private boolean required = true;
|
||||
private boolean uploadFileParam = false;
|
||||
private boolean collectionParam = false;
|
||||
private boolean orderParam = false;
|
||||
private boolean pageParam = false;
|
||||
private boolean groupParam = false;
|
||||
private boolean queryParam = false;
|
||||
private boolean aggregationParam = false;
|
||||
private boolean jsonData = false;
|
||||
private ApiModel modelData;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiArgumentAnnotation {
|
||||
private String name;
|
||||
private Integer type;
|
||||
private boolean required = true;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiModel {
|
||||
private String name;
|
||||
private String fullName;
|
||||
private String comment;
|
||||
private List<ApiField> fieldList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ApiField {
|
||||
private String name;
|
||||
private String comment;
|
||||
private String typeName;
|
||||
private boolean requiredColumn = false;
|
||||
}
|
||||
|
||||
public static final class ApiArgumentAnnotationType {
|
||||
public static final int REQUEST_PARAM = 0;
|
||||
public static final int REQUEST_BODY = 1;
|
||||
public static final int MY_REQUEST_BODY = 2;
|
||||
|
||||
private ApiArgumentAnnotationType() {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class InternalServiceData {
|
||||
private String serviceRequestPath = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.flow.demo.apidoc.tools.exception;
|
||||
|
||||
/**
|
||||
* 解析接口信息配置对象中的异常。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class ApiCodeConfigParseException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*/
|
||||
public ApiCodeConfigParseException() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param msg 错误信息。
|
||||
*/
|
||||
public ApiCodeConfigParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.flow.demo.apidoc.tools.exception;
|
||||
|
||||
/**
|
||||
* 解析Mybatis XML Mapper中的异常。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class MapperParseException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*/
|
||||
public MapperParseException() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造函数。
|
||||
*
|
||||
* @param msg 错误信息。
|
||||
*/
|
||||
public MapperParseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.flow.demo.apidoc.tools.export;
|
||||
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeParser;
|
||||
import com.flow.demo.apidoc.tools.util.FreeMarkerUtils;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 根据代码解析后的工程对象数据,导出到Markdown格式的接口文档文件。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class ApiDocExporter {
|
||||
|
||||
private final Configuration config;
|
||||
|
||||
public ApiDocExporter() throws TemplateModelException {
|
||||
config = new Configuration(Configuration.VERSION_2_3_28);
|
||||
config.setNumberFormat("0.####");
|
||||
config.setClassicCompatible(true);
|
||||
config.setAPIBuiltinEnabled(true);
|
||||
config.setClassForTemplateLoading(ApiPostmanExporter.class, "/templates/");
|
||||
config.setDefaultEncoding("UTF-8");
|
||||
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||
config.setSharedVariable("freemarkerUtils", new FreeMarkerUtils());
|
||||
config.unsetCacheStorage();
|
||||
config.clearTemplateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Markdown格式的API接口文档。
|
||||
*
|
||||
* @param apiProject 解析后的工程对象。
|
||||
* @param outputFile 生成后的、包含全路径的输出文件名。
|
||||
* @throws IOException 文件操作异常。
|
||||
* @throws TemplateException 模板实例化异常。
|
||||
*/
|
||||
public void doGenerate(ApiCodeParser.ApiProject apiProject, String outputFile) throws IOException, TemplateException {
|
||||
Map<String, Object> paramMap = new HashMap<>(1);
|
||||
paramMap.put("project", apiProject);
|
||||
List<ApiCodeParser.ApiService> newServiceList = new LinkedList<>();
|
||||
if (apiProject.getMicroService()) {
|
||||
// 在微服务场景中,我们需要把upms服务放到最前面显示。
|
||||
for (ApiCodeParser.ApiService apiService : apiProject.getServiceList()) {
|
||||
if ("upms".equals(apiService.getServiceName())) {
|
||||
newServiceList.add(apiService);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (ApiCodeParser.ApiService apiService : apiProject.getServiceList()) {
|
||||
if (!"upms".equals(apiService.getServiceName())) {
|
||||
newServiceList.add(apiService);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ApiCodeParser.ApiService appService = apiProject.getServiceList().get(0);
|
||||
ApiCodeParser.ApiService newUpmsService = new ApiCodeParser.ApiService();
|
||||
newUpmsService.setDefaultGroupClassSet(appService.getGroupedClassMap().get("upms"));
|
||||
newUpmsService.setServiceName("upms");
|
||||
newUpmsService.setShowName("用户权限模块");
|
||||
newServiceList.add(newUpmsService);
|
||||
ApiCodeParser.ApiService newAppService = new ApiCodeParser.ApiService();
|
||||
newAppService.setDefaultGroupClassSet(appService.getGroupedClassMap().get("app"));
|
||||
newAppService.setServiceName("app");
|
||||
newAppService.setShowName("业务应用模块");
|
||||
newServiceList.add(newAppService);
|
||||
}
|
||||
apiProject.setServiceList(newServiceList);
|
||||
FileUtils.forceMkdirParent(new File(outputFile));
|
||||
config.getTemplate("./api-doc.md.ftl").process(paramMap, new FileWriter(outputFile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.flow.demo.apidoc.tools.export;
|
||||
|
||||
import com.flow.demo.apidoc.tools.codeparser.ApiCodeParser;
|
||||
import com.flow.demo.apidoc.tools.util.FreeMarkerUtils;
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.TemplateException;
|
||||
import freemarker.template.TemplateExceptionHandler;
|
||||
import freemarker.template.TemplateModelException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 根据代码解析后的工程对象数据,导出到Postman支持的JSON格式的文件。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class ApiPostmanExporter {
|
||||
|
||||
private final Configuration config;
|
||||
|
||||
public ApiPostmanExporter() throws TemplateModelException {
|
||||
config = new Configuration(Configuration.VERSION_2_3_28);
|
||||
config.setNumberFormat("0.####");
|
||||
config.setClassicCompatible(true);
|
||||
config.setAPIBuiltinEnabled(true);
|
||||
config.setClassForTemplateLoading(ApiPostmanExporter.class, "/templates/");
|
||||
config.setDefaultEncoding("UTF-8");
|
||||
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||
config.setSharedVariable("freemarkerUtils", new FreeMarkerUtils());
|
||||
config.unsetCacheStorage();
|
||||
config.clearTemplateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成Postman支持的JSON文档。
|
||||
* @param apiProject 解析后的工程对象。
|
||||
* @param outputFile 生成后的、包含全路径的输出文件名。
|
||||
* @throws IOException 文件操作异常。
|
||||
* @throws TemplateException 模板实例化异常。
|
||||
*/
|
||||
public void doGenerate(ApiCodeParser.ApiProject apiProject, String outputFile) throws IOException, TemplateException {
|
||||
Map<String, Object> paramMap = new HashMap<>(1);
|
||||
paramMap.put("project", apiProject);
|
||||
FileUtils.forceMkdirParent(new File(outputFile));
|
||||
config.getTemplate("./postman_collection.json.ftl").process(paramMap, new FileWriter(outputFile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.flow.demo.apidoc.tools.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 仅供Freemarker模板内部使用的Java工具函数。
|
||||
*
|
||||
* @author Jerry
|
||||
* @date 2021-06-06
|
||||
*/
|
||||
public class FreeMarkerUtils {
|
||||
|
||||
/**
|
||||
* 生成GUID。
|
||||
*
|
||||
* @return 生成后的GUID。
|
||||
*/
|
||||
public static String generateGuid() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,明确标识该常量类的作用。
|
||||
*/
|
||||
public FreeMarkerUtils() {
|
||||
// FreeMarker的工具对象,Sonarqube建议给出空构造的注释。
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"projectName": "橙单在线表单",
|
||||
"basePackage": "com.flow.demo",
|
||||
"projectRootPath": "这里请使用当前工程的根目录,如:e:/xxx/OrangeDemo 或者 /Users/xxx/OrangeDemo",
|
||||
"microService": "false",
|
||||
"serviceList": [
|
||||
{
|
||||
"serviceName": "application-webadmin",
|
||||
"showName": "后台管理服务",
|
||||
"servicePath": "/application-webadmin",
|
||||
"port": "8082",
|
||||
"controllerInfoList": [
|
||||
{
|
||||
"path": "/src/main/java/com/flow/demo/webadmin/app/controller",
|
||||
"groupName": "app"
|
||||
},
|
||||
{
|
||||
"path": "/src/main/java/com/flow/demo/webadmin/upms/controller",
|
||||
"groupName": "upms"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
## 用户登录
|
||||
### 登录接口
|
||||
#### 登录
|
||||
- **URI:** /admin/upms/login/doLogin
|
||||
- **Type:** GET
|
||||
- **Content-Type:** multipart/form-data
|
||||
- **Request-Headers:**
|
||||
Name|Type|Description
|
||||
--|--|--
|
||||
Authorization|String|身份验证的Token
|
||||
- **Request-Parameters:**
|
||||
Parameter|Type|Required|Description
|
||||
--|--|--|--
|
||||
loginName|string|true|用户名
|
||||
password|string|true|加密后的用户密码
|
||||
|
||||
#### 退出
|
||||
- **URI:** /admin/upms/login/logout
|
||||
- **Type:** POST
|
||||
- **Content-Type:** application/json; chartset=utf-8
|
||||
- **Request-Headers:**
|
||||
Name|Type|Description
|
||||
--|--|--
|
||||
Authorization|String|身份验证的Token
|
||||
|
||||
#### 修改密码
|
||||
- **URI:** /admin/upms/login/changePassword
|
||||
- **Type:** POST
|
||||
- **Content-Type:** application/json; chartset=utf-8
|
||||
- **Request-Headers:**
|
||||
Name|Type|Description
|
||||
--|--|--
|
||||
Authorization|String|身份验证的Token
|
||||
- **Request-Parameters:**
|
||||
Parameter|Type|Required|Description
|
||||
--|--|--|--
|
||||
oldPass|string|true|加密后的原用户密码
|
||||
newPass|string|true|加密后的新用户密码
|
||||
<#list project.serviceList as service>
|
||||
|
||||
## ${service.showName}
|
||||
<#list service.defaultGroupClassSet as apiClass>
|
||||
### ${apiClass.name}
|
||||
<#list apiClass.methodList as apiMethod>
|
||||
#### ${apiMethod.name}
|
||||
- **URI:** ${apiMethod.requestPath}
|
||||
- **Type:** ${apiMethod.httpMethod}
|
||||
- **Content-Type:** <#if apiMethod.httpMethod == "GET" || apiMethod.queryParamArgumentList?size gt 0 || apiMethod.uploadParamArgumentList?size gt 0>multipart/form-data<#else>application/json; chartset=utf-8</#if>
|
||||
- **Request-Headers:**
|
||||
Name|Type|Description
|
||||
--|--|--
|
||||
Authorization|String|身份验证的Token
|
||||
<#if apiMethod.queryParamArgumentList?size gt 0 || apiMethod.uploadParamArgumentList?size gt 0>
|
||||
- **Request-Parameters:**
|
||||
Parameter|Type|Required|Description
|
||||
--|--|--|--
|
||||
<#list apiMethod.queryParamArgumentList as apiArgument>
|
||||
<#if apiArgument.modelData??>
|
||||
<#list apiArgument.modelData.tableFieldList as apiField>
|
||||
${apiField.name}|${apiField.typeName}|<#if apiMethod.listDictUrl>false<#else><#if apiField.requiredColumn>true<#else>false</#if></#if>|${apiField.comment}
|
||||
</#list>
|
||||
<#else>
|
||||
${apiArgument.name}|${apiArgument.typeName}|<#if apiMethod.listDictUrl>false<#else><#if apiArgument.required>true<#else>false</#if></#if>|${apiArgument.comment}
|
||||
</#if><#-- apiArgument.modelData?? -->
|
||||
</#list>
|
||||
</#if>
|
||||
<#list apiMethod.uploadParamArgumentList as apiArgument>
|
||||
${apiArgument.name}|File|true|${apiArgument.comment}
|
||||
</#list>
|
||||
<#if apiMethod.jsonParamArgumentList?size gt 0>
|
||||
- **Request-Body:**
|
||||
``` json
|
||||
{
|
||||
<#list apiMethod.jsonParamArgumentList as apiArgument>
|
||||
<#if apiArgument.modelData??>
|
||||
<#if apiArgument.collectionParam>
|
||||
"${apiArgument.name}" : [
|
||||
{
|
||||
<#if apiMethod.listUrl>
|
||||
<#list apiArgument.modelData.filteredFieldList as apiField>
|
||||
"${apiField.name}" : "${apiField.typeName} | false | <#if apiField.name == "searchString">模糊搜索字符串。<#else>${apiField.comment}</#if>"<#if apiField_has_next>,</#if>
|
||||
</#list>
|
||||
<#else><#-- apiMethod.listUrl -->
|
||||
<#list apiArgument.modelData.tableFieldList as apiField>
|
||||
<#if !apiMethod.addUrl || !apiField.primaryKey>
|
||||
"${apiField.name}" : "${apiField.typeName} | <#if apiField.requiredColumn>true<#else>false</#if> | ${apiField.comment}"<#if apiField_has_next>,</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if><#-- apiMethod.listUrl -->
|
||||
}
|
||||
]<#if apiArgument_has_next>,</#if>
|
||||
<#else><#-- apiArgument.collectionParam -->
|
||||
"${apiArgument.name}" : {
|
||||
<#if apiMethod.listUrl>
|
||||
<#list apiArgument.modelData.filteredFieldList as apiField>
|
||||
"${apiField.name}" : "${apiField.typeName} | false | <#if apiField.name == "searchString">模糊搜索字符串。<#else>${apiField.comment}</#if>"<#if apiField_has_next>,</#if>
|
||||
</#list>
|
||||
<#else><#-- apiMethod.listUrl -->
|
||||
<#list apiArgument.modelData.tableFieldList as apiField>
|
||||
<#if !apiMethod.addUrl || !apiField.primaryKey>
|
||||
"${apiField.name}" : "${apiField.typeName} | <#if apiField.requiredColumn>true<#else>false</#if> | ${apiField.comment}"<#if apiField_has_next>,</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if><#-- apiMethod.listUrl -->
|
||||
}<#if apiArgument_has_next>,</#if>
|
||||
</#if><#-- apiArgument.collectionParam -->
|
||||
<#elseif apiArgument.orderParam>
|
||||
"${apiArgument.name}" : [
|
||||
{
|
||||
"fieldName" : "String | false | 排序字段名",
|
||||
"asc" : "Boolean | false | 是否升序"
|
||||
}
|
||||
]<#if apiArgument_has_next>,</#if>
|
||||
<#elseif apiArgument.groupParam>
|
||||
"${apiArgument.name}" : [
|
||||
{
|
||||
"fieldName" : "String | false | 分组字段名",
|
||||
"aliasName" : "String | false | 分组字段别名",
|
||||
"dateAggregateBy" : "String | false | 是否按照日期聚合,可选项(day|month|year)"
|
||||
}
|
||||
]<#if apiArgument_has_next>,</#if>
|
||||
<#elseif apiArgument.pageParam>
|
||||
"${apiArgument.name}" : {
|
||||
"pageNum": "Integer | false | 分页页号",
|
||||
"pageSize": "Integer | false | 每页数据量"
|
||||
}<#if apiArgument_has_next>,</#if>
|
||||
<#elseif apiArgument.queryParam || apiArgument.aggregationParam>
|
||||
${apiArgument.name}" : {
|
||||
|
||||
}<#if apiArgument_has_next>,</#if>
|
||||
<#else><#-- apiArgument.modelData?? -->
|
||||
<#if apiArgument.collectionParam>
|
||||
"${apiArgument.name}" : [ "${apiArgument.typeName} | ${apiArgument.required}<#if apiArgument.comment??> | ${apiArgument.comment}</#if>" ]<#if apiArgument_has_next>,</#if>
|
||||
<#else>
|
||||
"${apiArgument.name}" : "${apiArgument.typeName} | ${apiArgument.required}<#if apiArgument.comment??> | ${apiArgument.comment}</#if>"<#if apiArgument_has_next>,</#if>
|
||||
</#if>
|
||||
</#if><#-- apiArgument.modelData?? -->
|
||||
</#list>
|
||||
}
|
||||
```
|
||||
</#if>
|
||||
</#list><#-- apiClass.methodList as apiMethod -->
|
||||
</#list><#-- upmsClassList as apiClass -->
|
||||
</#list>
|
||||
@@ -0,0 +1,42 @@
|
||||
<#import "postman_common.ftl" as Common>
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "92b51dc5-3611-49ac-8d94-a0718dba5bf1",
|
||||
"name": "${project.projectName}",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
<#list project.serviceList as service>
|
||||
{
|
||||
"name": "${service.serviceName}",
|
||||
"item": [
|
||||
<#if service.groupedClassMap?size gt 0>
|
||||
<#list service.groupedClassMap?keys as groupName>
|
||||
<#assign groupedClassList=service.groupedClassMap[groupName] />
|
||||
{
|
||||
"name": "${groupName}",
|
||||
"item": [
|
||||
<#list groupedClassList as apiClass>
|
||||
{
|
||||
<@Common.generateControllerRequest service apiClass 7/>
|
||||
}<#if apiClass_has_next>,</#if>
|
||||
</#list>
|
||||
],
|
||||
"protocolProfileBehavior": {},
|
||||
"_postman_isSubFolder": true
|
||||
}<#if groupName_has_next>,</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
<#list service.defaultGroupClassSet as apiClass>
|
||||
{
|
||||
<@Common.generateControllerRequest service apiClass 5/>
|
||||
}<#if apiClass_has_next>,</#if>
|
||||
</#list>
|
||||
],
|
||||
"protocolProfileBehavior": {},
|
||||
"_postman_isSubFolder": true
|
||||
}<#if service_has_next>,</#if>
|
||||
</#list><#-- project.serviceList as service -->
|
||||
],
|
||||
"protocolProfileBehavior": {}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<#macro doIndent level><#if level != 0><#list 0..(level-1) as i> </#list></#if></#macro>
|
||||
|
||||
<#macro generateControllerRequest service apiClass indentLevel>
|
||||
<@doIndent indentLevel/>"name": "${apiClass.name}",
|
||||
<@doIndent indentLevel/>"item": [
|
||||
<#list apiClass.methodList as apiMethod>
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "name": "${apiMethod.name}",
|
||||
<#if apiMethod.loginUrl>
|
||||
<@doIndent indentLevel/> "event": [
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "listen": "test",
|
||||
<@doIndent indentLevel/> "script": {
|
||||
<@doIndent indentLevel/> "id": "${freemarkerUtils.generateGuid()}",
|
||||
<@doIndent indentLevel/> "type": "text/javascript",
|
||||
<@doIndent indentLevel/> "exec": [
|
||||
<@doIndent indentLevel/> "pm.test(\"登录操作\", function () {",
|
||||
<@doIndent indentLevel/> " var jsonData = pm.response.json();",
|
||||
<@doIndent indentLevel/> " var token = jsonData.data.tokenData;",
|
||||
<@doIndent indentLevel/> " pm.environment.set(\"token\", token);",
|
||||
<@doIndent indentLevel/> " console.log(\"login token \" + token);",
|
||||
<@doIndent indentLevel/> "});",
|
||||
<@doIndent indentLevel/> ""
|
||||
<@doIndent indentLevel/> ]
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> },
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "listen": "prerequest",
|
||||
<@doIndent indentLevel/> "script": {
|
||||
<@doIndent indentLevel/> "id": "${freemarkerUtils.generateGuid()}",
|
||||
<@doIndent indentLevel/> "type": "text/javascript",
|
||||
<@doIndent indentLevel/> "exec": [
|
||||
<@doIndent indentLevel/> ""
|
||||
<@doIndent indentLevel/> ]
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> ],
|
||||
</#if>
|
||||
<@doIndent indentLevel/> "request": {
|
||||
<@doIndent indentLevel/> "method": "${apiMethod.httpMethod}",
|
||||
<#if apiMethod.loginUrl>
|
||||
<@doIndent indentLevel/> "header": [],
|
||||
<#else>
|
||||
<@doIndent indentLevel/> "header": [
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "key": "Authorization",
|
||||
<@doIndent indentLevel/> "value": "{{token}}",
|
||||
<@doIndent indentLevel/> "type": "text"
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> ],
|
||||
</#if>
|
||||
<@doIndent indentLevel/> "url": {
|
||||
<@doIndent indentLevel/> "raw": "http://{{host}}:${service.port}/${apiMethod.requestPath}",
|
||||
<@doIndent indentLevel/> "protocol": "http",
|
||||
<@doIndent indentLevel/> "host": [
|
||||
<@doIndent indentLevel/> "{{host}}"
|
||||
<@doIndent indentLevel/> ],
|
||||
<@doIndent indentLevel/> "port": "${service.port}",
|
||||
<@doIndent indentLevel/> "path": [
|
||||
<#list apiMethod.pathList as path>
|
||||
<@doIndent indentLevel/> "${path}"<#if path_has_next>,</#if>
|
||||
</#list>
|
||||
<@doIndent indentLevel/> ]<#if apiMethod.queryParamArgumentList?size gt 0>,</#if>
|
||||
<#if apiMethod.queryParamArgumentList?size gt 0>
|
||||
<@doIndent indentLevel/> "query": [
|
||||
<#list apiMethod.queryParamArgumentList as apiArgument>
|
||||
<#if apiArgument.modelData??>
|
||||
<#list apiArgument.modelData.tableFieldList as apiField>
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "key": "${apiField.name}",
|
||||
<@doIndent indentLevel/> "value": ""
|
||||
<@doIndent indentLevel/> }<#if apiArgument_has_next || apiField_has_next>,</#if>
|
||||
</#list>
|
||||
<#else>
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "key": "${apiArgument.name}",
|
||||
<@doIndent indentLevel/> "value": ""
|
||||
<@doIndent indentLevel/> }<#if apiArgument_has_next>,</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<@doIndent indentLevel/> ]
|
||||
</#if>
|
||||
<@doIndent indentLevel/> }<#if (apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0) || apiMethod.uploadParamArgumentList?size gt 0>,</#if>
|
||||
<#if apiMethod.uploadParamArgumentList?size gt 0>
|
||||
<@doIndent indentLevel/> "body": {
|
||||
<@doIndent indentLevel/> "mode": "formdata",
|
||||
<@doIndent indentLevel/> "formdata": [
|
||||
<#list apiMethod.uploadParamArgumentList as apiArgument>
|
||||
<@doIndent indentLevel/> {
|
||||
<@doIndent indentLevel/> "key": "${apiArgument.name}",
|
||||
<@doIndent indentLevel/> "type": "file",
|
||||
<@doIndent indentLevel/> "src": []
|
||||
<@doIndent indentLevel/> }<#if apiArgument_has_next>,</#if>
|
||||
</#list>
|
||||
<@doIndent indentLevel/> ]
|
||||
<@doIndent indentLevel/> }<#if apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0>,</#if>
|
||||
</#if><#-- apiMethod.uploadParamArgumentList?size gt 0 -->
|
||||
<#if apiMethod.httpMethod == "POST" && apiMethod.jsonParamArgumentList?size gt 0>
|
||||
<@doIndent indentLevel/> "body": {
|
||||
<@doIndent indentLevel/> "mode": "raw",
|
||||
<#if !apiMethod.loginUrl>
|
||||
<@doIndent indentLevel/> "raw": "{\n<#list apiMethod.jsonParamArgumentList as apiArgument><#if apiArgument.modelData??><#if apiArgument.collectionParam>\t\"${apiArgument.name}\" : [\n\t\t{\n<#list apiArgument.modelData.fieldList as apiField><#if apiMethod.listUrl>\t\t\t\"${apiField.name}\" : \"\"<#if apiField_has_next>,</#if>\n<#else>\t\t\t\"${apiField.name}\" : \"<#if apiField.typeName == "Integer" || apiField.typeName == "Long">0</#if>\"<#if apiField_has_next>,</#if>\n</#if><#-- apiMethod.listUrl --></#list>\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#else><#-- apiArgument.collectionParam -->\t\"${apiArgument.name}\" : {\n<#list apiArgument.modelData.fieldList as apiField><#if apiMethod.listUrl>\t\t\"${apiField.name}\" : \"\"<#if apiField_has_next>,</#if>\n<#else>\t\t\"${apiField.name}\" : \"<#if apiField.typeName == "Integer" || apiField.typeName == "Long">0</#if>\"<#if apiField_has_next>,</#if>\n</#if><#-- apiMethod.listUrl --></#list>\t}<#if apiArgument_has_next>,</#if>\n</#if><#-- apiArgument.collectionParam --><#elseif apiArgument.orderParam>\t\"${apiArgument.name}\" : [\n\t\t{\n\t\t\t\"fieldName\" : \"\",\n\t\t\t\"asc\" : \"true\"\n\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.groupParam>\t\"${apiArgument.name}\" : [\n\t\t{\n\t\t\t\"fieldName\" : \"\",\n\t\t\t\"aliasName\" : \"\",\n\t\t\t\"dateAggregateBy\" : \"\"\n\t\t}\n\t]<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.pageParam>\t\"${apiArgument.name}\" : {\n\t\t\"pageNum\": \"1\",\n\t\t\"pageSize\": \"10\"\n\t}<#if apiArgument_has_next>,</#if>\n<#elseif apiArgument.queryParam || apiArgument.aggregationParam>\t\"${apiArgument.name}\" : {\n\t}<#if apiArgument_has_next>,</#if>\n<#else><#if apiArgument.collectionParam>\t\"${apiArgument.name}\" : [ ]<#if apiArgument_has_next>,</#if>\n<#else>\t\"${apiArgument.name}\" : \"\"<#if apiArgument_has_next>,</#if>\n</#if></#if><#-- apiArgument.modelData?? --></#list><#-- apiMethod.jsonParamArgumentList?size gt 0 -->}\n",
|
||||
<#else>
|
||||
<@doIndent indentLevel/> "raw": "{\n \"loginName\":\"admin\",\n \"password\":\"IP3ccke3GhH45iGHB5qP9p7iZw6xUyj28Ju10rnBiPKOI35sc%2BjI7%2FdsjOkHWMfUwGYGfz8ik31HC2Ruk%2Fhkd9f6RPULTHj7VpFdNdde2P9M4mQQnFBAiPM7VT9iW3RyCtPlJexQ3nAiA09OqG%2F0sIf1kcyveSrulxembARDbDo%3D\"\n}",
|
||||
</#if>
|
||||
<@doIndent indentLevel/> "options": {
|
||||
<@doIndent indentLevel/> "raw": {
|
||||
<@doIndent indentLevel/> "language": "json"
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> }
|
||||
<@doIndent indentLevel/> }
|
||||
</#if>
|
||||
<@doIndent indentLevel/> },
|
||||
<@doIndent indentLevel/> "response": []
|
||||
<@doIndent indentLevel/> }<#if apiMethod_has_next>,</#if>
|
||||
</#list><#-- apiClass.methodList as apiMethod -->
|
||||
<@doIndent indentLevel/>],
|
||||
<@doIndent indentLevel/>"protocolProfileBehavior": {},
|
||||
<@doIndent indentLevel/>"_postman_isSubFolder": true
|
||||
</#macro>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>com.flow.demo</groupId>
|
||||
<artifactId>DemoFlow</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>framework</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>apidoc-tools</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -43,6 +43,7 @@
|
||||
<modules>
|
||||
<module>application-webadmin</module>
|
||||
<module>common</module>
|
||||
<module>framework</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |