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