引言
原文出处Swagger2 关于JSONObject参数在API文档中展示详细参数以及参数说明
类似的 Swagger2 关于Map参数在API文档中展示详细参数以及参数说明
原理就不再做说明,读者可以查看该文章即可。作者稍作了些改变,也是花了很多时间整理,接下来我们只负责搬砖就好,话不多说直接看代码。
代码实现
自定义注解类 ApiJsonObject2
package com.mybatis.plus.swagger;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author wulongbo
* @Date 2020/9/14 17:20
* @Version 1.0
*/
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonObject2 {
ApiJsonProperty2[] value(); //对象属性值
ApiJsonResult result() default @ApiJsonResult({});
String name() default "";
}
自定义注解类ApiJsonProperty2
package com.mybatis.plus.swagger;
import io.swagger.annotations.Example;
import io.swagger.annotations.ExampleProperty;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author wulongbo
* @Date 2020/9/14 17:23
* @Version 1.0
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonProperty2 {
String name();
String defaultValue() default "";
String description() default "";
String allowableValues() default "";
boolean required() default false;
String access() default "";
boolean allowMultiple() default false;
Class<?> type() default String.class ;
String paramType() default "";
String example() default "";
Example examples() default @Example(value = @ExampleProperty(mediaType = "", value = ""));
String format() default "";
boolean readOnly() default false;
String collectionFormat() default "";
}
自定义注解类 ApiJsonResult
package com.mybatis.plus.swagger;
/**
* Created by yueh on 2018/9/7.
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiJsonResult {
String[] value();
String name() default "";
String type() default CommonData.RESULT_TYPE_NORMAL_FINAL;
}
ApiListingJsonScanner
package com.mybatis.plus.swagger;
/**
* Created by yueh on 2018/9/12. */
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.base.*;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import springfox.documentation.PathProvider;
import springfox.documentation.builders.ApiListingBuilder;
import springfox.documentation.schema.Model;
import springfox.documentation.service.*;
import springfox.documentation.spi.service.contexts.ApiListingContext;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import springfox.documentation.spi.service.contexts.RequestMappingContext;
import springfox.documentation.spring.web.paths.PathMappingAdjuster;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
import springfox.documentation.spring.web.scanners.ApiDescriptionReader;
import springfox.documentation.spring.web.scanners.ApiListingScanner;
import springfox.documentation.spring.web.scanners.ApiListingScanningContext;
import springfox.documentation.spring.web.scanners.ApiModelReader;
import java.util.*;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
import static java.util.stream.Collectors.toList;
import static springfox.documentation.builders.BuilderDefaults.nullToEmptyList;
import static springfox.documentation.spi.service.contexts.Orderings.methodComparator;
import static springfox.documentation.spi.service.contexts.Orderings.resourceGroupComparator;
@Component
@Primary
public class ApiListingJsonScanner extends ApiListingScanner {
private final ApiDescriptionReader apiDescriptionReader;
private final ApiModelReader apiModelReader;
private final DocumentationPluginsManager pluginsManager;
public static final String ROOT = "/";
@Autowired
public ApiListingJsonScanner(
ApiDescriptionReader apiDescriptionReader,
ApiModelReader apiModelReader,
DocumentationPluginsManager pluginsManager) {
super(apiDescriptionReader,apiModelReader,pluginsManager);
this.apiDescriptionReader = apiDescriptionReader;
this.apiModelReader = apiModelReader;
this.pluginsManager = pluginsManager;
}
static Optional<String> longestCommonPath(List<ApiDescription> apiDescriptions) {
List<String> commons = newArrayList();
if (null == apiDescriptions || apiDescriptions.isEmpty()) {
return Optional.absent();
}
List<String> firstWords = urlParts(apiDescriptions.get(0));
for (int position = 0; position < firstWords.size(); position++) {
String word = firstWords.get(position);
boolean allContain = true;
for (int i = 1; i < apiDescriptions.size(); i++) {
List<String> words = urlParts(apiDescriptions.get(i));
if (words.size() < position + 1 || !words.get(position).equals(word)) {
allContain = false;
break; }
}
if (allContain) {
commons.add(word);
}
}
Joiner joiner = Joiner.on("/").skipNulls();
return Optional.of("/" + joiner.join(commons));
}
static List<String> urlParts(ApiDescription apiDescription) {
return Splitter.on('/')
.omitEmptyStrings()
.trimResults()
.splitToList(apiDescription.getPath());
}
public Multimap<String, ApiListing> scan(ApiListingScanningContext context) {
final Multimap<String, ApiListing> apiListingMap = LinkedListMultimap.create();
int position = 0;
Map<ResourceGroup, List<RequestMappingContext>> requestMappingsByResourceGroup
= context.getRequestMappingsByResourceGroup();
Collection<ApiDescription> additionalListings = pluginsManager.additionalListings(context);
Set<ResourceGroup> allResourceGroups = FluentIterable.from(collectResourceGroups(additionalListings))
.append(requestMappingsByResourceGroup.keySet())
.toSet();
List<SecurityReference> securityReferences = newArrayList();
for (final ResourceGroup resourceGroup : sortedByName(allResourceGroups)) {
DocumentationContext documentationContext = context.getDocumentationContext();
Set<String> produces = new LinkedHashSet<String>(documentationContext.getProduces());
Set<String> consumes = new LinkedHashSet<String>(documentationContext.getConsumes());
String host = documentationContext.getHost();
Set<String> protocols = new LinkedHashSet<String>(documentationContext.getProtocols());
Set<ApiDescription> apiDescriptions = newHashSet();
Map<String, Model> models = new LinkedHashMap<String, Model>();
List<RequestMappingContext> requestMappings = nullToEmptyList(requestMappingsByResourceGroup.get(resourceGroup));
for (RequestMappingContext each : sortedByMethods(requestMappings)) {//url
Map<String, Model> knownModels = new HashMap<>();
models.putAll(apiModelReader.read(each.withKnownModels(models)));
apiDescriptions.addAll(apiDescriptionReader.read(each));
}
//
models.putAll(ModelCache.getInstance().getKnownModels());
List<ApiDescription> additional = from(additionalListings)
.filter(and(
belongsTo(resourceGroup.getGroupName()),
onlySelectedApis(documentationContext)))
.toList();
apiDescriptions.addAll(additional);
List<ApiDescription> sortedApis = FluentIterable.from(apiDescriptions)
.toSortedList(documentationContext.getApiDescriptionOrdering());
Optional<String> o = longestCommonPath(sortedApis);
String resourcePath = new ResourcePathProvider(resourceGroup)
.resourcePath()
.or(o)
.orNull();
PathProvider pathProvider = documentationContext.getPathProvider();
String basePath = pathProvider.getApplicationBasePath();
PathAdjuster adjuster = new PathMappingAdjuster(documentationContext);
ApiListingBuilder apiListingBuilder = new ApiListingBuilder(context.apiDescriptionOrdering())
.apiVersion(documentationContext.getApiInfo().getVersion())
.basePath(adjuster.adjustedPath(basePath))
.resourcePath(resourcePath)
.produces(produces)
.consumes(consumes)
.host(host)
.protocols(protocols)
.securityReferences(securityReferences)
.apis(sortedApis)
.models(models)
.position(position++)
.availableTags(documentationContext.getTags());
ApiListingContext apiListingContext = new ApiListingContext(
context.getDocumentationType(),
resourceGroup,
apiListingBuilder);
apiListingMap.put(resourceGroup.getGroupName(), pluginsManager.apiListing(apiListingContext));
}
return apiListingMap;
}
private Predicate<ApiDescription> onlySelectedApis(final DocumentationContext context) {
return new Predicate<ApiDescription>() {
@Override
public boolean apply(ApiDescription input) {
return context.getApiSelector().getPathSelector().apply(input.getPath());
}
};
}
private Iterable<RequestMappingContext> sortedByMethods(List<RequestMappingContext> contexts) {
return contexts.stream().sorted(methodComparator()).collect(toList());
}
static Iterable<ResourceGroup> collectResourceGroups(Collection<ApiDescription> apiDescriptions) {
return from(apiDescriptions)
.transform(toResourceGroups());
}
static Iterable<ResourceGroup> sortedByName(Set<ResourceGroup> resourceGroups) {
return from(resourceGroups).toSortedList(resourceGroupComparator());
}
static Predicate<ApiDescription> belongsTo(final String groupName) {
return new Predicate<ApiDescription>() {
@Override
public boolean apply(ApiDescription input) {
return !input.getGroupName().isPresent()
|| groupName.equals(input.getGroupName().get());
}
};
}
private static Function<ApiDescription, ResourceGroup> toResourceGroups() {
return new Function<ApiDescription, ResourceGroup>() {
@Override
public ResourceGroup apply(ApiDescription input) {
return new ResourceGroup(
input.getGroupName().or(Docket.DEFAULT_GROUP_NAME),
null);
}
};
}
class ResourcePathProvider {
private final ResourceGroup resourceGroup;
ResourcePathProvider(ResourceGroup resourceGroup) {
this.resourceGroup = resourceGroup;
}
public Optional<String> resourcePath() {
return Optional.fromNullable(
Strings.emptyToNull(controllerClass()
.transform(resourcePathExtractor())
.or("")));
}
private Function<Class<?>, String> resourcePathExtractor() {
return new Function<Class<?>, String>() {
@Override
public String apply(Class<?> input) {
String path = Iterables.getFirst(Arrays.asList(paths(input)), "");
if (Strings.isNullOrEmpty(path)) {
return "";
}
if (path.startsWith("/")) {
return path;
}
return "/" + path;
}
};
}
@VisibleForTesting
String[] paths(Class<?> controller) {
RequestMapping annotation
= AnnotationUtils.findAnnotation(controller, RequestMapping.class);
if (annotation != null) {
return annotation.path();
}
return new String[]{};
}
private Optional<? extends Class<?>> controllerClass() {
return resourceGroup.getControllerClass();
}
}
}
自定义注解类 ApiSingleParam
package com.mybatis.plus.swagger;
/**
* Created by yueh on 2018/9/7.
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiSingleParam {
Sring name() default "";
String value() default "";
Class<?> type() default String.class;
String example() default "";
boolean allowMultiple() default false;
}
CommonData
package com.mybatis.plus.swagger;
/**
* Created by yueh on 2018/9/18.
*/
public class CommonData {
private static String RESULT_TYPE_NORMAL = "normal";
private static String RESULT_TYPE_PAGE = "page";
private static String RESULT_TYPE_LIST = "list";
private static String RESULT_TYPE_OTHER = "other";
private static String JSON_ERROR_CODE = "errorCode";
private static String JSON_ERROR_MSG = "errorMsg";
private static String JSON_START_PAGE_NUM = "startPageNum";
private static String JSON_PAGE_SIZE = "pageSize";
private static String JSON_PAGE_COUNT = "pageCount";
private static String JSON_TOTAL_COUNT = "totalCount";
public static final String RESULT_TYPE_NORMAL_FINAL = "normal";
public static String getResultTypeNormal() {
return RESULT_TYPE_NORMAL;
}
public static void setResultTypeNormal(String resultTypeNormal) {
RESULT_TYPE_NORMAL = resultTypeNormal;
}
public static String getResultTypePage() {
return RESULT_TYPE_PAGE;
}
public static void setResultTypePage(String resultTypePage) {
RESULT_TYPE_PAGE = resultTypePage;
}
public static String getResultTypeList() {
return RESULT_TYPE_LIST;
}
public static void setResultTypeList(String resultTypeList) {
RESULT_TYPE_LIST = resultTypeList;
}
public static String getResultTypeOther() {
return RESULT_TYPE_OTHER;
}
public static void setResultTypeOther(String resultTypeOther) {
RESULT_TYPE_OTHER = resultTypeOther;
}
public static String getJsonErrorCode() {
return JSON_ERROR_CODE;
}
public static void setJsonErrorCode(String jsonErrorCode) {
JSON_ERROR_CODE = jsonErrorCode;
}
public static String getJsonErrorMsg() {
return JSON_ERROR_MSG;
}
public static void setJsonErrorMsg(String jsonErrorMsg) {
JSON_ERROR_MSG = jsonErrorMsg;
}
public static String getJsonStartPageNum() {
return JSON_START_PAGE_NUM;
}
public static void setJsonStartPageNum(String jsonStartPageNum) {
JSON_START_PAGE_NUM = jsonStartPageNum;
}
public static String getJsonPageSize() {
return JSON_PAGE_SIZE;
}
public static void setJsonPageSize(String jsonPageSize) {
JSON_PAGE_SIZE = jsonPageSize;
}
public static String getJsonPageCount() {
return JSON_PAGE_COUNT;
}
public static void setJsonPageCount(String jsonPageCount) {
JSON_PAGE_COUNT = jsonPageCount;
}
public static String getJsonTotalCount() {
return JSON_TOTAL_COUNT;
}
public static void setJsonTotalCount(String jsonTotalCount) {
JSON_TOTAL_COUNT = jsonTotalCount;
}
}
全局字符串 GlobalString
package com.mybatis.plus.swagger;
/**
* @Author wulongbo
* @Date 2020/9/14 17:34
* @Version 1.0
*/
public class GlobalString {
@ApiSingleParam(value = "姓名", example = "isWulongbo")
public static final String JSON_NAME = "name";
@ApiSingleParam(value = "年龄", example = "25")
public static final String JSON_AGE = "age";
@ApiSingleParam(value = "邮箱", example = "1191935532@qq.com")
public static final String JSON_EMAIL = "email";
@ApiSingleParam(value = "错误码", example = "0", type = Integer.class)
public static final String JSON_ERROR_CODE = "errorCode";
@ApiSingleParam(value = "错误信息", example = "OK")
public static final String JSON_ERROR_MSG = "errorMsg";
}
ModelCache
package com.mybatis.plus.swagger;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import org.springframework.plugin.core.OrderAwarePluginRegistry;
import org.springframework.plugin.core.PluginRegistry;
import springfox.documentation.schema.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.TypeNameProviderPlugin;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.contexts.DocumentationContext;
import java.lang.reflect.Field;
import java.util.*;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
import static com.mybatis.plus.swagger.CommonData.*;
import static org.springframework.util.ObjectUtils.isEmpty;
import static springfox.documentation.schema.Collections.collectionElementType;
import static springfox.documentation.spi.schema.contexts.ModelContext.inputParam;
/**
* Created by yueh on 2018/9/13. */public class ModelCache {
private Map<String, Model> knownModels = new HashMap<>();
private DocumentationContext context;
private Function<ResolvedType, ? extends ModelReference> factory;
private TypeResolver typeResolver = new TypeResolver();
private Map<String, ApiSingleParam> paramMap = new HashMap<>();
private Class<?> cls;
private ModelCache() {
}
public static ModelCache getInstance() {
return ModelCacheSub.instance;
}
public ModelCache setParamMap(Map<String, ApiSingleParam> paramMap) {
this.paramMap = paramMap;
return getInstance();
}
public ModelCache setParamClass(Class<?> cls) {
this.cls = cls;
return getInstance();
}
public Class<?> getParamClass() {
return cls;
}
public ModelCache setFactory(Function<ResolvedType, ? extends ModelReference> factory) {
this.factory = factory;
return getInstance();
}
public void setContext(DocumentationContext context) {
this.context = context;
}
public DocumentationContext getContext() {
return context;
}
public Map<String, Model> getKnownModels() {
return knownModels;
}
public ModelCache addModel(ApiJsonObject2 jsonObj) {
String modelName = jsonObj.name();
knownModels.put(modelName,
new Model(modelName,
modelName,
new TypeResolver().resolve(String.class),
"com.mybatis.plus.swagger.CommonData",
toPropertyMap(jsonObj.value()),
"POST参数",
"",
"",
newArrayList(), null, null
));
String resultName = jsonObj.name() + "-" + "result";
knownModels.put(resultName,
new Model(resultName,
resultName,
new TypeResolver().resolve(String.class),
"com.mybatis.plus.swagger.CommonData",
toResultMap(jsonObj.result(), resultName),
"返回模型",
"",
"",
newArrayList(), null, null
));
return ModelCacheSub.instance;
}
public Map<String, ModelProperty> toResultMap(ApiJsonResult jsonResult, String groupName) {
// System.out.println("--- toResultMap ---");
List<String> values = Arrays.asList(jsonResult.value());
List<String> outer = new ArrayList<>();
if (!getResultTypeOther().equals(jsonResult.type())) {
outer.add(getJsonErrorCode());
outer.add(getJsonErrorMsg());
if (!getResultTypeNormal().equals(jsonResult.type())) {
//model
String subModelName = groupName + "-" + jsonResult.name();
knownModels.put(subModelName,
new Model(subModelName,
subModelName,
new TypeResolver().resolve(String.class),
"com.mybatis.plus.swagger.CommonData",
transResultMap(values),
"返回模型",
"",
"",
newArrayList(), null, null
));
//prop
Map<String, ModelProperty> propertyMap = new HashMap<>();
// outer.add(jsonResult.name());
ResolvedType type = new TypeResolver().resolve(List.class);
ModelProperty mp = new ModelProperty(
jsonResult.name(),
type,
"",
0,
false, false, true, false, "",
null, "",
null, "",
null, newArrayList()
);// new AllowableRangeValues("1", "2000"),//.allowableValues(new AllowableListValues(["ABC", "ONE", "TWO"], "string"))
mp.updateModelRef(getModelRef());
ResolvedType collectionElementType = collectionElementType(type);
try {
Field f = ModelProperty.class.getDeclaredField("modelRef");
f.setAccessible(true);
f.set(mp, new ModelRef("List", new ModelRef(subModelName)));
} catch (Exception e) {
e.printStackTrace();
}
propertyMap.put(jsonResult.name(), mp);
if (getResultTypePage().equals(jsonResult.type())) {
outer.add(getJsonStartPageNum());
outer.add(getJsonPageSize());
outer.add(getJsonTotalCount());
}
propertyMap.putAll(transResultMap(outer));
return propertyMap;
}
outer.addAll(values);
return transResultMap(outer);
}
return transResultMap(values);
}
public Map<String, ModelProperty> transResultMap(List<String> values) {
Map<String, ModelProperty> propertyMap = new HashMap<>();
for (String resultName : values) {
ApiSingleParam param = paramMap.get(resultName);
if (isEmpty(param)) {
continue;
}
Class<?> type = param.type();
if (!isEmpty(param)) {
type = param.type();
} else if (isEmpty(type)) {
type = String.class;
}
boolean allowMultiple = param.allowMultiple();
if (!isEmpty(param)) {
allowMultiple = param.allowMultiple();
}
ResolvedType resolvedType = null;
if (allowMultiple) {
resolvedType = new TypeResolver().resolve(List.class, type);
} else {
resolvedType = new TypeResolver().resolve(type);
}
ModelProperty mp = new ModelProperty(
resultName,
resolvedType,
param.type().getName(),
0,
false, false, true, false, param.value(),
null, param.example(),
null, "",
null, newArrayList()
);// new AllowableRangeValues("1", "2000"),//.allowableValues(new AllowableListValues(["ABC", "ONE", "TWO"], "string"))
mp.updateModelRef(getModelRef());
propertyMap.put(resultName, mp);
}
return propertyMap;
}
public Map<String, ModelProperty> toPropertyMap(ApiJsonProperty2[] jsonProp) {
// System.out.println("--- toPropertyMap ---");
Map<String, ModelProperty> propertyMap = new HashMap<String, ModelProperty>();
for (ApiJsonProperty2 property : jsonProp) {
String propertyName = property.name();
ApiSingleParam param = paramMap.get(propertyName);
String description = property.description();
if (isNullOrEmpty(description) && !isEmpty(param)) {
description = param.value();
}
String example = property.description();
if (isNullOrEmpty(example) && !isEmpty(param)) {
example = param.example();
}
Class<?> type = property.type();
if (!isEmpty(param)) {
type = param.type();
} else if (isEmpty(type)) {
type = String.class;
}
boolean allowMultiple = property.allowMultiple();
if (!isEmpty(param)) {
allowMultiple = param.allowMultiple();
}
ResolvedType resolvedType = null;
if (allowMultiple) {
resolvedType = new TypeResolver().resolve(List.class, type);
} else {
resolvedType = new TypeResolver().resolve(type);
}
// System.out.println("----- example: " + example);
// System.out.println("----- description: " + description);
ModelProperty mp = new ModelProperty(
propertyName,
resolvedType,
type.toString(),
0,
property.required(),
false, property.readOnly(),
null, description,
null, example,
null, property.defaultValue(),
null, newArrayList()
);// new AllowableRangeValues("1", "2000"),//.allowableValues(new AllowableListValues(["ABC", "ONE", "TWO"], "string"))
mp.updateModelRef(getModelRef());
propertyMap.put(property.name(), mp);
}
return propertyMap;
}
private static class ModelCacheSub {
private static ModelCache instance = new ModelCache();
}
private Function<ResolvedType, ? extends ModelReference> getModelRef() {
Function<ResolvedType, ? extends ModelReference> factory = getFactory();
// ModelReference stringModel = factory.apply(typeResolver.resolve(List.class, String.class));
return factory;
}
public Function<ResolvedType, ? extends ModelReference> getFactory() {
if (factory == null) {
List<DefaultTypeNameProvider> providers = newArrayList();
providers.add(new DefaultTypeNameProvider());
PluginRegistry<TypeNameProviderPlugin, DocumentationType> modelNameRegistry =
OrderAwarePluginRegistry.create(providers);
TypeNameExtractor typeNameExtractor = new TypeNameExtractor(
typeResolver,
modelNameRegistry,
new JacksonEnumTypeDeterminer());
ModelContext modelContext = inputParam(
context.getGroupName(),
String.class,
context.getDocumentationType(),
context.getAlternateTypeProvider(),
context.getGenericsNamingStrategy(),
context.getIgnorableParameterTypes());
factory = ResolvedTypes.modelRefFactory(modelContext, typeNameExtractor);
}
return factory;
}
}
注意:在 ModelCache 类中 CommonData的路径一定要改为你真实的项目路径
ParametersReader
package com.mybatis.plus.swagger;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.ResolvedTypes;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.ResolvedMethodParameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.spi.service.contexts.ParameterContext;
import springfox.documentation.spring.web.DescriptionResolver;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static springfox.documentation.spi.schema.contexts.ModelContext.inputParam;
import static springfox.documentation.swagger.common.SwaggerPluginSupport.pluginDoesApply;
/**
* Created by yueh on 2018/9/10.
*/
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ParametersReader implements OperationBuilderPlugin {
private final DescriptionResolver descriptions;
private final TypeNameExtractor nameExtractor;
private final TypeResolver resolver;
@Autowired
public ParametersReader(DescriptionResolver descriptions, TypeNameExtractor nameExtractor, TypeResolver resolver) {
this.nameExtractor = nameExtractor;
this.resolver = resolver;
this.descriptions = descriptions;
}
@Override
public void apply(OperationContext context) {
context.operationBuilder().parameters(readParameters(context));
}
@Override
public boolean supports(DocumentationType delimiter) {
return pluginDoesApply(delimiter);
}
private List<Parameter> readParameters(OperationContext context) {
List<Parameter> parameters = Lists.newArrayList();
List<ResolvedMethodParameter> methodParameters = context.getParameters();
Map<String, ApiSingleParam> paramMap = new HashMap<>();
Field[] fields = ModelCache.getInstance().getParamClass().getDeclaredFields();
String type = new String();
for (Field field : fields) {
if (field.isAnnotationPresent(ApiSingleParam.class)) {
ApiSingleParam param = field.getAnnotation(ApiSingleParam.class);
try {
String name = (String) field.get(type);
paramMap.put(name, param);
} catch (Exception e) {
}
}
}
for (ResolvedMethodParameter methodParameter : methodParameters) {
ParameterContext parameterContext = new ParameterContext(methodParameter,
new ParameterBuilder(),
context.getDocumentationContext(),
context.getGenericsNamingStrategy(),
context);
Function<ResolvedType, ? extends ModelReference> factory = createModelRefFactory(parameterContext);
Optional<ApiJsonObject2> annotation = context.findAnnotation(ApiJsonObject2.class);
if (annotation.isPresent()) {
ModelCache.getInstance().setFactory(factory)
.setParamMap(paramMap)
.addModel(annotation.get());
}
}
return parameters;
}
private Function<ResolvedType, ? extends ModelReference> createModelRefFactory(ParameterContext context) {
ModelContext modelContext = inputParam(
context.getGroupName(),
context.resolvedMethodParameter().getParameterType(),
context.getDocumentationType(),
context.getAlternateTypeProvider(),
context.getGenericNamingStrategy(),
context.getIgnorableParameterTypes());
return ResolvedTypes.modelRefFactory(modelContext, nameExtractor);
}
}
OK!贴完代码后的目录结构如下:
由于我们之前 mybatis-plus
项目没有引入json依赖
在 pom.xml
文件引入fastjson
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
封装返回值
新建一个 base
文件夹,贴入常用的封装类BaseApiService
package com.mybatis.plus.base;
import lombok.Data;
import org.springframework.stereotype.Component;
@Data
@Component
public class BaseApiService<T> {
public BaseResponse<T> setResultError(Integer code, String msg) {
return setResult(code, msg, null);
}
// 返回错误,可以传msg
public BaseResponse<T> setResultError(String msg) {
return setResult(PageCodeEnum.HTTP_RES_500.getCode(), msg, null);
}
// 返回成功,可以传data值
public BaseResponse<T> setResultSuccess(T data) {
return setResult(PageCodeEnum.HTTP_RES_200.getCode(), PageCodeEnum.HTTP_RES_200.getMsg(), data);
}
// 返回成功,沒有data值
public BaseResponse<T> setResultSuccess() {
return setResult(PageCodeEnum.HTTP_RES_200.getCode(), PageCodeEnum.HTTP_RES_200.getMsg(), null);
}
// 返回成功,沒有data值
public BaseResponse<T> setResultSuccess(String msg) {
return setResult(PageCodeEnum.HTTP_RES_200.getCode(), msg, null);
}
// 通用封装
public BaseResponse<T> setResult(Integer code, String msg, T data) {
return new BaseResponse<T>(code, msg, data);
}
//调用数据库封装
public Boolean toDaoResult(int result){
return result>0?true:false;
}
}
BaseResponse
package com.mybatis.plus.base;
import lombok.Data;
@Data
public class BaseResponse<T> {
/**
* 返回码
*/
private Integer code;
/**
* 消息(最好用简写,不占用宽带)
*/ private String msg;
/**
* 数据
*/
private T data;
public BaseResponse() {
}
public BaseResponse(Integer code, String msg, T data) {
super();
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 未登录返回结果
*/
public static <T> BaseResponse<T> unauthorized(T data) {
return new BaseResponse<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
}
/**
* 未授权返回结果
*/
public static <T> BaseResponse<T> forbidden(T data) {
return new BaseResponse<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
}
}
IErrorCode
package com.mybatis.plus.base;
/**
* 封装API的错误码
*/
public interface IErrorCode {
Integer getCode();
String getMessage();
}
PageCodeEnum
package com.mybatis.plus.base;
/**
* @Author wulongbo
* @Date 2020/9/14 15:38
* @Version 1.0
*/public enum PageCodeEnum {
// 公共
HTTP_RES_200(200,"success"),
HTTP_RES_500(500,"fail"),
SUCCESS(200, "成功"),
UNKNOWN_ERROR(9998,"未知异常"),
SYSTEM_ERROR(9999, "系统异常"),
INSUFFICIENT_PERMISSION(4003, "权限不足"),
WARN(9000, "失败"),
REQUEST_PARAMETER_ERROR(1002, "请求参数错误"),
// 分页
PAGE_CURRENTPAGE_EMPTY(5000, "当前页码不能为空!"),
PAGE_PAGESIZE_EMPTY(5001, "每页显示的条数不能为空!");
private Integer code;
private String msg;
// 构造方法,注意:构造方法不能为public,因为enum并不可以被实例化
private PageCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
//不需要定义set方法,防止修改
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
ResultCode
package com.mybatis.plus.base;
/**
* 枚举了一些常用API操作码
*/
public enum ResultCode implements IErrorCode {
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private Integer code;
private String message;
private ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
OK!目录结构如下:
现在我们在 UserController
中加入我们自定义的注解
package com.mybatis.plus.controller;
import com.alibaba.fastjson.JSONObject;
import com.mybatis.plus.base.BaseApiService;
import com.mybatis.plus.base.BaseResponse;
import com.mybatis.plus.base.PageCodeEnum;
import com.mybatis.plus.entity.User;
import com.mybatis.plus.service.IUserService;
import com.mybatis.plus.swagger.ApiJsonObject2;
import com.mybatis.plus.swagger.ApiJsonProperty2;
import com.mybatis.plus.swagger.ApiJsonResult;
import com.mybatis.plus.swagger.GlobalString;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 前端控制器
* </p>
*
* @author wulongbo
* @since 2020-11-09
*/
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController extends BaseApiService {
@Autowired
private IUserService iUserService;
@ApiOperation(value="添加用户信息", notes="添加用户信息")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "name", dataType = "String", required = true, value = "姓名"),
@ApiImplicitParam(paramType = "query", name = "age", dataType = "int", required = true, value = "年龄"),
@ApiImplicitParam(paramType = "query", name = "email", dataType = "String", required = true, value = "邮箱") })
@PostMapping("/addUser")
public boolean addUser(String name,Integer age,String email) {
try {
User user=new User();
user.setName(name);
user.setAge(age);
user.setEmail(email);
iUserService.recordJob(user);
}catch (Exception e){
return false;
}
return true;
}
@ApiOperation(value="删除用户信息", notes="删除用户信息")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", name = "id", dataType = "long", required = true, value = "用户id")})
@PostMapping("/delUser")
public boolean delUser(Long id) {
try {
User user=new User();
user.setId(id);
iUserService.delJob(user);
}catch (Exception e){
return false;
}
return true;
}
/**
* JSONObject参数添加用户信息
* @param json
* @return
*/
@ApiOperation(value="JSONObject参数添加用户信息", notes="JSONObject参数添加用户信息")
@ApiJsonObject2(name = "addUserByJson", value = {
@ApiJsonProperty2(name = GlobalString.JSON_NAME),
@ApiJsonProperty2(name = GlobalString.JSON_AGE),
@ApiJsonProperty2(name = GlobalString.JSON_EMAIL)},
result = @ApiJsonResult({}))
@ApiImplicitParam(name = "json", required = true, dataType = "addUserByJson")
@ApiResponses({@ApiResponse(code = 200, message = "OK", reference = "addUserByJson")})
@RequestMapping(value = "/addUserByJson", method = RequestMethod.POST, consumes ="application/json", produces = "application/json")
public BaseResponse<JSONObject> addUserByJson(@RequestBody String json){
JSONObject jsonObject=JSONObject.parseObject(json);
String name=jsonObject.getString("name"); // 姓名
Integer age=jsonObject.getInteger("age");// 年龄
String email=jsonObject.getString("email");// 邮箱
try {
User user=new User();
user.setName(name);
user.setAge(age);
user.setEmail(email);
iUserService.recordJob(user);
}catch (Exception e){
return setResultError(PageCodeEnum.HTTP_RES_500.getMsg());
}
return setResultSuccess(PageCodeEnum.HTTP_RES_200);
}
}
启动 MybatisPlusApplication
并访问 http://localhost:8080/swagger-ui.html
好!接下来我们看看swagger文档会有什么不同!GlobalString
中的参数自动读取到了 swagger
中,我们直接执行接口访问:
并查看数据库是否有新增记录:
完美!至此我们swagger再也不用手填参数数值了,直接丢给前端,你get到了吗!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。