SOP接入笔记

SOP开发手册

1.项目下载SOP开源地址

2.更改sop-gateway项目注册中心地址,打包部署到服务器上

3.cd到/SOP/sop-common目录,执行命令mvn clean deploy,把jar上传到maven私服,如果没有maven私服,可以打包到本地mvn clean install 本地打包添加AVG属性

4.更改sop-website项目中的注册中心,打包部署到服务器上

5.业务项目中pom.xml中添加

    <!-- swagger2  有依赖的不用加 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>swagger-bootstrap-ui</artifactId>
            <version>1.9.5</version>
        </dependency>
        <!-- sop -->
        <dependency>
            <groupId>你的SOP groupId</groupId>
            <artifactId>你的SOP artifactId</artifactId>
            <version>你的SOP version</version>
        </dependency>

6.业务项目中添加config文件OpenServiceConfig.java

@Configuration
public class OpenServiceConfig extends AlipayServiceConfiguration {
    static {
        ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error");
    }
    /**
     * 开启文档,本地微服务文档地址:http://localhost:2222/doc.html http://ip:port/v2/api-docs
     */
    @Configuration
    @EnableSwagger2
    public static class Swagger2 extends SwaggerSupport {
        @Override
        protected String getDocTitle() {
            return "文档名称-API";
        }

        @Override
        protected boolean swaggerAccessProtected() {
            return false;
        }
    }
}

一定要检查gateway,web-site,业务项目的注册中心,以及注册中心中的分组

7.一些核心实现

  • DocumentationPluginsManagerExt.java
重新构造swagger文档接口返回参数 ;localhost:8080/v2/api-docs接口
private void setVendorExtension(Operation operation, OperationContext operationContext) {
        List<VendorExtension> vendorExtensions = operation.getVendorExtensions();
        Optional<Open> mappingOptional = operationContext.findAnnotation(Open.class);
        if (mappingOptional.isPresent()) {
            Open open = mappingOptional.get();
            String name = open.value();
            String version = buildVersion(open.version());
            vendorExtensions.add(new StringVendorExtension(SOP_NAME, name));
            vendorExtensions.add(new StringVendorExtension(SOP_VERSION, version));
            this.setBizCode(open, vendorExtensions);
            this.setResultExtProperties(operationContext);
        }
        Optional<Api> apiOptional = operationContext.findControllerAnnotation(Api.class);
        int order = 0;
        if (apiOptional.isPresent()) {
            order = apiOptional.get().position();
        } else {
            Optional<Order> orderOptional = operationContext.findControllerAnnotation(Order.class);
            if (orderOptional.isPresent()) {
                order = orderOptional.get().value();
            }
        }
        vendorExtensions.add(new StringVendorExtension(MODULE_ORDER, String.valueOf(order)));
        Optional<ApiOperation> apiOperationOptional = operationContext.findAnnotation(ApiOperation.class);
        int methodOrder = 0;
        if (apiOperationOptional.isPresent()) {
            methodOrder = apiOperationOptional.get().position();
        }
        vendorExtensions.add(new StringVendorExtension(API_ORDER, String.valueOf(methodOrder)));
    }
  • NacosRegistryListener.java

加载服务路由,nacos实现
其中startupTime变量的赋值在ServiceConfiguration.java中,必要条件

/**
     * 获取建康的服务实例
     *
     * @return 没有返回空的list
     */
    private List<NacosServiceHolder> getServiceList() {
        NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
        ListView<String> servicesOfServer = null;
        try {
            servicesOfServer = namingService.getServicesOfServer(1, Integer.MAX_VALUE, nacosGroup);
        } catch (NacosException e) {
            log.error("namingService.getServicesOfServer()错误", e);
        }
        if (servicesOfServer == null || CollectionUtils.isEmpty(servicesOfServer.getData())) {
            return Collections.emptyList();
        }
        return servicesOfServer
                .getData()
                .stream()
                .map(serviceName -> {
                    List<Instance> allInstances;
                    try {
                        // 获取服务实例
                       allInstances = namingService.getAllInstances(serviceName, nacosGroup);
                    } catch (NacosException e) {
                        log.error("namingService.getAllInstances(serviceName)错误,serviceName:{}", serviceName, e);
                        return null;
                    }
                    if (CollectionUtils.isEmpty(allInstances)) {
                        return null;
                    }
                    return allInstances.stream()
                            // 只获取建康实例
                            .filter(Instance::isHealthy)
                            .map(instance -> {
                                String startupTime = instance.getMetadata().get(SopConstants.METADATA_KEY_TIME_STARTUP);
                                if (startupTime == null) {
                                    return null;
                                }
                                long time = NumberUtils.toLong(startupTime, 0);
                                return new NacosServiceHolder(serviceName, time, instance);
                            })
                            .filter(Objects::nonNull)
                            .max(Comparator.comparing(ServiceHolder::getLastUpdatedTimestamp))
                            .orElse(null);

                })
                .filter(Objects::nonNull)
                .filter(this::canOperator)
                .collect(Collectors.toList());
    }
  • ServiceConfiguration.java

一些metadata信息,其中server.startup-time与上面的nacos类的getServiceList有关系

public class ServiceConfiguration extends SpringmvcConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty("spring.cloud.nacos.discovery.server-addr")
    public NacosWatch nacosWatch(
            NacosServiceManager nacosServiceManager,
            NacosDiscoveryProperties nacosDiscoveryProperties,
            ObjectProvider<ThreadPoolTaskScheduler> taskScheduler,
            Environment environment
    ) {
        Map<String, String> metadata = nacosDiscoveryProperties.getMetadata();
        String contextPath = environment.getProperty(METADATA_SERVER_CONTEXT_PATH);
        // 将context-path信息加入到metadata中
        if (contextPath != null) {
            metadata.put(METADATA_SERVER_CONTEXT_PATH, contextPath);
        }
        // 在元数据中新增启动时间,不能修改这个值,不然网关拉取接口会有问题
        // 如果没有这个值,网关会忽略这个服务
        metadata.put("server.startup-time", String.valueOf(System.currentTimeMillis()));
        return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties, taskScheduler);
    }

}
  • ServiceDocListener.java

文档加载监听类

public class ServiceDocListener extends BaseServiceListener {

    private static final String SECRET = "b749a2ec000f4f29";

    @Autowired
    private DocManager docManager;

    @Override
    public void onRemoveService(String serviceId) {
        log.info("服务下线,删除文档,serviceId: {}", serviceId);
        docManager.remove(serviceId);
    }

    @Override
    public void onAddInstance(InstanceDefinition instance) {
        String serviceId = instance.getServiceId();
        String url = getRouteRequestUrl(instance);
        ResponseEntity<String> responseEntity = getRestTemplate().getForEntity(url, String.class);
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            String body = responseEntity.getBody();
            docManager.addDocInfo(serviceId, body, callback -> log.info("加载服务文档,serviceId={}, 机器={}", serviceId,
                    instance.getIp() + ":" + instance.getPort()));
        } else {
            log.error("加载文档失败, status:{}, body:{}", responseEntity.getStatusCodeValue(), responseEntity.getBody());
        }
    }

    private String getRouteRequestUrl(InstanceDefinition instance) {
        String query = buildQuery(SECRET);
        String contextPath = this.getContextPath(instance.getMetadata());
        return "http://" + instance.getIp() + ":" + instance.getPort() + contextPath + "/v2/api-docs" + query;
    }
}
  • SwaggerDocParser.java

解析swagger的json,这时候额外的属性是在DocumentationPluginsManagerExt.java中做的实现

public class SwaggerDocParser implements DocParser {
    @Override
    public DocInfo parseJson(JSONObject docRoot) {
        String title = docRoot.getJSONObject("info").getString("title");
        List<DocItem> docItems = new ArrayList<>();

        JSONObject paths = docRoot.getJSONObject("paths");
        if (paths == null) {
            paths = new JSONObject();
        }
        Set<String> pathNameSet = paths.keySet();
        for (String apiPath : pathNameSet) {
            JSONObject pathInfo = paths.getJSONObject(apiPath);
            // key: get,post,head...
            Collection<String> httpMethodList = getHttpMethods(pathInfo);
            Optional<String> first = httpMethodList.stream().findFirst();
            if (first.isPresent()) {
                String method = first.get();
                JSONObject docInfo = pathInfo.getJSONObject(method);
                DocItem docItem = buildDocItem(docInfo, docRoot);
                if (docItem == null) {
                    continue;
                }
                if (docItem.isUploadRequest()) {
                    docItem.setHttpMethodList(Sets.newHashSet("post"));
                } else {
                    docItem.setHttpMethodList(httpMethodList);
                }
                docItems.add(docItem);
            }
        }

        docItems.sort(Comparator.comparing(DocItem::getApiOrder).thenComparing(DocItem::getNameVersion));

        List<DocModule> docModuleList = docItems.stream().collect(Collectors.groupingBy(DocItem::getModule)).entrySet()
                .stream().map(entry -> {
                    List<DocItem> docItemList = entry.getValue();
                    DocModule docModule = new DocModule();
                    docModule.setModule(entry.getKey());
                    docModule.setDocItems(docItemList);
                    docModule.setOrder(getMuduleOrder(docItemList));
                    return docModule;
                }).sorted(Comparator.comparing(DocModule::getOrder)).collect(Collectors.toList());

        DocInfo docInfo = new DocInfo();
        docInfo.setTitle(title);
        docInfo.setDocModuleList(docModuleList);
        return docInfo;
    }

    private int getMuduleOrder(List<DocItem> items) {
        if (CollectionUtils.isEmpty(items)) {
            return Integer.MAX_VALUE;
        }
        List<DocItem> docItemList = new ArrayList<>(items);
        docItemList.sort(Comparator.comparing(DocItem::getModuleOrder));
        return docItemList.get(0).getModuleOrder();
    }

    protected Collection<String> getHttpMethods(JSONObject pathInfo) {
        // key: get,post,head...
        List<String> retList;
        Set<String> httpMethodList = pathInfo.keySet();
        if (httpMethodList.size() <= 2) {
            retList = new ArrayList<>(httpMethodList);
        } else {
            Set<String> ignoreHttpMethods = DocParserContext.ignoreHttpMethods;
            retList = httpMethodList.stream().filter(method -> !ignoreHttpMethods.contains(method.toLowerCase()))
                    .collect(Collectors.toList());
        }
        Collections.sort(retList);
        return retList;
    }

    protected DocItem buildDocItem(JSONObject docInfo, JSONObject docRoot) {
        String apiName = docInfo.getString("sop_name");
        // 非开放接口
        if (StringUtils.isBlank(apiName)) {
            return null;
        }
        DocItem docItem = new DocItem();
        docItem.setId(UUID.randomUUID().toString());
        docItem.setName(apiName);
        docItem.setVersion(docInfo.getString("sop_version"));
        docItem.setSummary(docInfo.getString("summary"));
        docItem.setDescription(docInfo.getString("description"));
        docItem.setMultiple(docInfo.getString("multiple") != null);
        docItem.setProduces(docInfo.getJSONArray("produces").toJavaList(String.class));
        String bizCodeStr = docInfo.getString("biz_code");
        if (bizCodeStr != null) {
            docItem.setBizCodeList(JSON.parseArray(bizCodeStr, BizCode.class));
        }
        docItem.setModuleOrder(NumberUtils.toInt(docInfo.getString("module_order"), 0));
        docItem.setApiOrder(NumberUtils.toInt(docInfo.getString("api_order"), 0));
        String moduleName = this.buildModuleName(docInfo, docRoot);
        docItem.setModule(moduleName);
        List<DocParameter> docParameterList = this.buildRequestParameterList(docInfo, docRoot);
        docItem.setRequestParameters(docParameterList);

        List<DocParameter> responseParameterList = this.buildResponseParameterList(docInfo, docRoot);
        docItem.setResponseParameters(responseParameterList);

        return docItem;
    }

    protected String buildModuleName(JSONObject docInfo, JSONObject docRoot) {
        String title = docRoot.getJSONObject("info").getString("title");
        JSONArray tags = docInfo.getJSONArray("tags");
        if (tags != null && tags.size() > 0) {
            return tags.getString(0);
        }
        return title;
    }

    protected List<DocParameter> buildRequestParameterList(JSONObject docInfo, JSONObject docRoot) {
        Optional<JSONArray> parametersOptional = Optional.ofNullable(docInfo.getJSONArray("parameters"));
        JSONArray parameters = parametersOptional.orElse(new JSONArray());
        List<DocParameter> docParameterList = new ArrayList<>();
        for (int i = 0; i < parameters.size(); i++) {
            JSONObject fieldJson = parameters.getJSONObject(i);
            JSONObject schema = fieldJson.getJSONObject("schema");
            if (schema != null) {
                RefInfo refInfo = getRefInfo(schema);
                if (refInfo != null) {
                    List<DocParameter> parameterList = this.buildDocParameters(refInfo.ref, docRoot, true);
                    docParameterList.addAll(parameterList);
                }
            } else {
                DocParameter docParameter = fieldJson.toJavaObject(DocParameter.class);
                docParameterList.add(docParameter);
            }
        }

        Map<String, List<DocParameter>> collect = docParameterList.stream()
                .filter(docParameter -> docParameter.getName().contains(".")).map(docParameter -> {
                    String name = docParameter.getName();
                    int index = name.indexOf('.');
                    String module = name.substring(0, index);
                    String newName = name.substring(index + 1);
                    DocParameter ret = new DocParameter();
                    BeanUtils.copyProperties(docParameter, ret);
                    ret.setName(newName);
                    ret.setModule(module);
                    return ret;
                }).collect(Collectors.groupingBy(DocParameter::getModule));

        collect.forEach((key, value) -> {
            DocParameter moduleDoc = new DocParameter();
            moduleDoc.setName(key);
            moduleDoc.setType("object");
            moduleDoc.setRefs(value);
            docParameterList.add(moduleDoc);
        });

        return docParameterList.stream().filter(docParameter -> !docParameter.getName().contains("."))
                .collect(Collectors.toList());
    }

    protected List<DocParameter> buildResponseParameterList(JSONObject docInfo, JSONObject docRoot) {
        RefInfo refInfo = getResponseRefInfo(docInfo);
        List<DocParameter> respParameterList = Collections.emptyList();
        if (refInfo != null) {
            String responseRef = refInfo.ref;
            respParameterList = this.buildDocParameters(responseRef, docRoot, true);
            // 如果返回数组
            if (refInfo.isArray) {
                DocParameter docParameter = new DocParameter();
                docParameter.setName("items");
                docParameter.setType("array");
                docParameter.setRefs(respParameterList);
                respParameterList = Collections.singletonList(docParameter);
            }
        }
        return respParameterList;
    }

    protected List<DocParameter> buildDocParameters(String ref, JSONObject docRoot, boolean doSubRef) {
        JSONObject responseObject = docRoot.getJSONObject("definitions").getJSONObject(ref);
        String className = responseObject.getString("title");
        JSONObject extProperties = docRoot.getJSONObject(className);
        JSONArray requiredProperties = responseObject.getJSONArray("required");
        JSONObject properties = responseObject.getJSONObject("properties");
        List<DocParameter> docParameterList = new ArrayList<>();
        if (properties == null) {
            return docParameterList;
        }
        Set<String> fieldNames = properties.keySet();
        for (String fieldName : fieldNames) {
            /*
             * { "description": "分类故事", "$ref": "#/definitions/StoryVO" }
             */
            JSONObject fieldInfo = properties.getJSONObject(fieldName);
            DocParameter docParameter = fieldInfo.toJavaObject(DocParameter.class);
            docParameter.setName(fieldName);
            docParameter.setRequired(
                    !CollectionUtils.isEmpty(requiredProperties) && requiredProperties.contains(fieldName));
            if (extProperties != null) {
                JSONObject prop = extProperties.getJSONObject(fieldName);
                if (prop != null) {
                    String maxLength = prop.getString("maxLength");
                    docParameter.setMaxLength(maxLength == null ? "-" : maxLength);
                    String required = prop.getString("required");
                    if (required != null) {
                        docParameter.setRequired(Boolean.parseBoolean(required));
                    }
                }
            }
            docParameterList.add(docParameter);
            RefInfo refInfo = this.getRefInfo(fieldInfo);
            if (refInfo != null && doSubRef) {
                String subRef = refInfo.ref;
                boolean nextDoRef = !Objects.equals(ref, subRef);
                List<DocParameter> refs = buildDocParameters(subRef, docRoot, nextDoRef);
                docParameter.setRefs(refs);
            }
        }
        return docParameterList;
    }

    /**
     * 简单对象返回: "responses": { "200": { "description": "OK", "schema": { "$ref":
     * "#/definitions/FileUploadVO" } }, "401": { "description": "Unauthorized" },
     * "403": { "description": "Forbidden" }, "404": { "description": "Not Found" }
     * } 纯数组返回: "responses": { "200": { "description": "OK", "schema": { "type":
     * "array", "items": { "$ref": "#/definitions/StoryVO" } } }, "401": {
     * "description": "Unauthorized" }, "403": { "description": "Forbidden" },
     * "404": { "description": "Not Found" } }
     * 
     * @param docInfo
     * @return
     */
    protected RefInfo getResponseRefInfo(JSONObject docInfo) {
        return Optional.ofNullable(docInfo.getJSONObject("responses"))
                .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("200")))
                .flatMap(jsonObject -> Optional.ofNullable(jsonObject.getJSONObject("schema"))).map(this::getRefInfo)
                .orElse(null);
    }

    private RefInfo getRefInfo(JSONObject jsonObject) {
        String ref;
        boolean isArray = "array".equals(jsonObject.getString("type"));
        if (isArray) {
            ref = jsonObject.getJSONObject("items").getString("$ref");
        } else {
            // #/definitions/Category
            ref = jsonObject.getString("$ref");
        }
        if (ref == null) {
            return null;
        }
        int index = ref.lastIndexOf("/");
        if (index > -1) {
            ref = ref.substring(index + 1);
        }
        RefInfo refInfo = new RefInfo();
        refInfo.isArray = isArray;
        refInfo.ref = ref;
        return refInfo;
    }

    private static class RefInfo {
        private boolean isArray;
        private String ref;
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

推荐阅读更多精彩内容