引言

原文出处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!贴完代码后的目录结构如下:
image.png

由于我们之前 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!目录结构如下:
image.png
现在我们在 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
image.png
好!接下来我们看看swagger文档会有什么不同!
image.png
GlobalString 中的参数自动读取到了 swagger 中,我们直接执行接口访问:
image.png
并查看数据库是否有新增记录:
查看数据库表

完美!至此我们swagger再也不用手填参数数值了,直接丢给前端,你get到了吗!


isWulongbo
228 声望26 粉丝

在人生的头三十年,你培养习惯,后三十年,习惯铸就你