springfox + swagger2自定义JsonObject/Map参数文档

说明

该改动不影响swagger原来的使用,Object/JsonObject 都可以兼容

Controller

image.png

Model

image.png

最终结果

request:


image.png

request model:


image.png

response:


image.png

response model:


image.png

代码实现

首先根据官方文档,写一个OperationBuilderPlugin类型的插件,这个插件用来读取接口的参数

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ParametersReader implements OperationBuilderPlugin {
...
 @Override
 public void apply(OperationContext context) {
        context.operationBuilder().parameters(readParameters(context));
 }
 private List<Parameter> readParameters(OperationContext context) {
        List<Parameter> parameters = Lists.newArrayList();
        List<ResolvedMethodParameter> methodParameters = context.getParameters();

        //1. 先读取GlobalString类中我们定义的参数单元,用一个Map来保存
        Map<String, ApiSingleParam> paramMap = new HashMap<>();
        Field[] fields = GlobalString.class.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) {
                }
            }
        }

        //遍历controller中的方法
        for (ResolvedMethodParameter methodParameter : methodParameters) {
            ParameterContext parameterContext = new ParameterContext(methodParameter,
                    new ParameterBuilder(),
                    context.getDocumentationContext(),
                    context.getGenericsNamingStrategy(),
                    context);
            Function<ResolvedType, ? extends ModelReference> factory = createModelRefFactory(parameterContext);

            //读取自定义的注释类
            Optional<ApiJsonObject> annotation = context.findAnnotation(ApiJsonObject.class);

            if (annotation.isPresent()) {
                //2. 自定义的注释类里包含参数列表,我们把它合成一个请求Model和应答Model,放在ModelCache缓存里面
                ModelCache.getInstance().setFactory(factory)
                        .setParamMap(paramMap)
                        .addModel(annotation.get());
            }
        }
        return parameters;
    }
}

然后重写 ApiListingScanner 类,将我们的Model加入到swagger的Model列表中

@Component
@Primary
public class ApiListingPluginsScanner extends ApiListingScanner {
...
    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));
            }
            //加入自己的Model
            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;
    }
}

这样request的部分就完成了,下面是response的实现

先重写 SwaggerResponseMessageReader 类

@Primary
@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 3)//一定要大一点
public class ResponseMessageReader extends SwaggerResponseMessageReader {
    
    private final TypeNameExtractor typeNameExtractor;
    private final TypeResolver typeResolver;

    public ResponseMessageReader(TypeNameExtractor typeNameExtractor, TypeResolver typeResolver) {
        super(typeNameExtractor, typeResolver);
        this.typeNameExtractor = typeNameExtractor;
        this.typeResolver = typeResolver;
    }

    @Override
    protected Set<ResponseMessage> read(OperationContext context) {
        ResolvedType defaultResponse = context.getReturnType();
        Optional<ApiOperation> operationAnnotation = context.findAnnotation(ApiOperation.class);
        Optional<ResolvedType> operationResponse =
                operationAnnotation.transform(resolvedTypeFromOperation(typeResolver, defaultResponse));
        Optional<ResponseHeader[]> defaultResponseHeaders = operationAnnotation.transform(responseHeaders());
        Map<String, Header> defaultHeaders = newHashMap();
        if (defaultResponseHeaders.isPresent()) {
            defaultHeaders.putAll(headers(defaultResponseHeaders.get()));
        }

        List<ApiResponses> allApiResponses = context.findAllAnnotations(ApiResponses.class);
        Set<ResponseMessage> responseMessages = newHashSet();

        Map<Integer, ApiResponse> seenResponsesByCode = newHashMap();
        for (ApiResponses apiResponses : allApiResponses) {
            ApiResponse[] apiResponseAnnotations = apiResponses.value();
            for (ApiResponse apiResponse : apiResponseAnnotations) {

                if (!seenResponsesByCode.containsKey(apiResponse.code())) {
                    seenResponsesByCode.put(apiResponse.code(), apiResponse);

                    java.util.Optional<ModelReference> responseModel = java.util.Optional.empty();
                    java.util.Optional<ResolvedType> type = resolvedType(null, apiResponse);
                    if (isSuccessful(apiResponse.code())) {
                        type = java.util.Optional.ofNullable(type.orElseGet(operationResponse::get));
                    }
                    if (type.isPresent()) {
                        //将返回的模型ID修改成自定义的,这里我取@apiResponse中的reference参数加"-result"组合
                        responseModel = java.util.Optional.of(new ModelRef(apiResponse.reference()+"-result"));
                    }
                    Map<String, Header> headers = newHashMap(defaultHeaders);
                    headers.putAll(headers(apiResponse.responseHeaders()));

                    responseMessages.add(new ResponseMessageBuilder()
                            .code(apiResponse.code())
                            .message(apiResponse.message())
                            .responseModel(responseModel.orElse(null))
                            .headersWithDescription(headers)
                            .build());
                }
            }
        }
        if (operationResponse.isPresent()) {
            ModelContext modelContext = returnValue(
                    context.getGroupName(),
                    operationResponse.get(),
                    context.getDocumentationType(),
                    context.getAlternateTypeProvider(),
                    context.getGenericsNamingStrategy(),
                    context.getIgnorableParameterTypes());
            ResolvedType resolvedType = context.alternateFor(operationResponse.get());

            ModelReference responseModel = modelRefFactory(modelContext, typeNameExtractor).apply(resolvedType);
            context.operationBuilder().responseModel(responseModel);
            ResponseMessage defaultMessage = new ResponseMessageBuilder()
                    .code(httpStatusCode(context))
                    .message(message(context))
                    .responseModel(responseModel)
                    .build();
            if (!responseMessages.contains(defaultMessage) && !"void".equals(responseModel.getType())) {
                responseMessages.add(defaultMessage);
            }
        }

        return responseMessages;
    }
    ...
}

ModelCache生成Model


    public ModelCache addModel(ApiJsonObject jsonObj) {
        String modelName =jsonObj.name();

        knownModels.put(modelName,
                new Model(modelName,
                        modelName,
                        new TypeResolver().resolve(String.class),
                        "xin.bee.model.entity.BusinessUser",
                        toPropertyMap(jsonObj.value()),
                        "POST参数",
                        "",
                        "",
                        newArrayList(), null, null
                ));
        String resultName = jsonObj.name() + "-" + "result";

        knownModels.put(resultName,
                new Model(resultName,
                        resultName,
                        new TypeResolver().resolve(String.class),
                        "xin.bee.model.entity.BusinessUser",
                        toResultMap(jsonObj.result(), resultName),
                        "返回模型",
                        "",
                        "",
                        newArrayList(), null, null
                ));
        return ModelCacheSub.instance;
    }

ModelProperty的制作

  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();
                }

这样就搞定了!!!

Map

map的话比较简单,参考这篇
https://blog.csdn.net/hellopeng1/article/details/82227942

git:https://github.com/cyjishuang/swagger-mode

jar:
build.gradle:

allprojects {
    repositories {
        //maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
        maven{ url "https://oss.sonatype.org/content/groups/staging"}
        //mavenCentral()
    }
}
dependencies {
    compile  group: 'io.github.cyjishuang' ,name: 'swagger-mode', version: '1.0'
}

spring-context.xml 添加扫描

    <context:component-scan base-package="io.github.cyjishuang"/>

在创建文档的时候设置存放参数的类ModelCache.getInstance().setParamClass(XXX.class);

 public Docket createRestApi() {
        ModelCache.getInstance().setParamClass(GlobalString.class);
        return new Docket(DocumentationType.SWAGGER_2)...

这样配置就完成了,后面只需要对Controller和参数的类添加说明即可,示例

@RequestMapping(value = "/api/v1/manager")
@RestController
@Api(description = "管理员身份接口")
public class ManagerController {
   @ApiOperation(value = "管理员-预判是否存在",notes ="预判管理员是否存在" )
    @ApiJsonObject(name = "manager-checkManager", value = {
            @ApiJsonProperty(name = JSON_USER_NAME),
            @ApiJsonProperty(name = JSON_USER_EMAIL)},
            result = @ApiJsonResult({}))
    @ApiImplicitParam(name = "params", required = true, dataType = "manager-checkManager")
    @ApiResponses({@ApiResponse(code = 200, message = "OK", reference = "manager-checkManager")})

    @RequestMapping(value = "/checkManager", method = RequestMethod.POST, consumes = MSG_FORMAT_JSON_UTF8, produces = MSG_FORMAT_JSON_UTF8)
    public String checkManager(@RequestBody String params) {
        return new ControllerCallBack()
                .addCommonService(managerService::checkUser)
                .build(params);
    }
}
public class GlobalString {
    @ApiSingleParam(value = "用户姓名", example = "test1")
    public static final String JSON_USER_NAME = "userName";

    @ApiSingleParam(value = "用户邮箱", example = "17721026877@qq.com")
    public static final String JSON_USER_EMAIL = "userEmail";

    @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";
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容