SOP接入筆記

SOP開發(fā)手冊

1.項目下載SOP開源地址

2.更改sop-gateway項目注冊中心地址,打包部署到服務器上

3.cd到/SOP/sop-common目錄,執(zhí)行命令mvn clean deploy,把jar上傳到maven私服彼硫,如果沒有maven私服,可以打包到本地mvn clean install 本地打包添加AVG屬性

4.更改sop-website項目中的注冊中心,打包部署到服務器上

5.業(yè)務項目中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.業(yè)務項目中添加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,業(yè)務項目的注冊中心,以及注冊中心中的分組

7.一些核心實現(xiàn)

  • DocumentationPluginsManagerExt.java
重新構(gòu)造swagger文檔接口返回參數(shù) ;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實現(xiàn)
其中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有關(guān)系

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);
        }
        // 在元數(shù)據(jù)中新增啟動時間,不能修改這個值哮内,不然網(wǎng)關(guān)拉取接口會有問題
        // 如果沒有這個值盗棵,網(wǎng)關(guān)會忽略這個服務
        metadata.put("server.startup-time", String.valueOf(System.currentTimeMillis()));
        return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties, taskScheduler);
    }

}
  • ServiceDocListener.java

文檔加載監(jiān)聽類

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中做的實現(xiàn)

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);
            // 如果返回數(shù)組
            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" }
     * } 純數(shù)組返回: "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;
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者琳拨。
  • 序言:七十年代末瞭恰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狱庇,更是在濱河造成了極大的恐慌惊畏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件密任,死亡現(xiàn)場離奇詭異颜启,居然都是意外死亡,警方通過查閱死者的電腦和手機浪讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門缰盏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人淹遵,你說我怎么就攤上這事口猜。” “怎么了透揣?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵济炎,是天一觀的道長。 經(jīng)常有香客問我淌实,道長冻辩,這世上最難降的妖魔是什么猖腕? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮恨闪,結(jié)果婚禮上倘感,老公的妹妹穿的比我還像新娘。我一直安慰自己咙咽,他們只是感情好老玛,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钧敞,像睡著了一般蜡豹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溉苛,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天镜廉,我揣著相機與錄音,去河邊找鬼愚战。 笑死娇唯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的寂玲。 我是一名探鬼主播塔插,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拓哟!你這毒婦竟也來了想许?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤断序,失蹤者是張志新(化名)和其女友劉穎流纹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逢倍,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡捧颅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了较雕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡挚币,死狀恐怖亮蒋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妆毕,我是刑警寧澤慎玖,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站笛粘,受9級特大地震影響趁怔,放射性物質(zhì)發(fā)生泄漏湿硝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一润努、第九天 我趴在偏房一處隱蔽的房頂上張望关斜。 院中可真熱鬧,春花似錦铺浇、人聲如沸痢畜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丁稀。三九已至,卻和暖如春倚聚,著一層夾襖步出監(jiān)牢的瞬間线衫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工惑折, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桶雀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓唬复,卻偏偏與公主長得像矗积,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敞咧,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容