swagger文档管理改为API接口文档管理

This commit is contained in:
暮光:城中城
2021-11-26 23:51:14 +08:00
parent 4408525b45
commit 91aef6f9f4
165 changed files with 3892 additions and 5744 deletions

View File

@@ -0,0 +1,186 @@
package com.zyplayer.doc.api.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.zyplayer.doc.api.framework.utils.SwaggerDocUtil;
import com.zyplayer.doc.api.service.SwaggerHttpRequestService;
import com.zyplayer.doc.core.annotation.AuthMan;
import com.zyplayer.doc.core.json.DocResponseJson;
import com.zyplayer.doc.core.json.ResponseJson;
import com.zyplayer.doc.data.config.security.DocUserDetails;
import com.zyplayer.doc.data.config.security.DocUserUtil;
import com.zyplayer.doc.data.repository.manage.entity.ApiDoc;
import com.zyplayer.doc.data.service.manage.ApiDocService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.swagger.web.SwaggerResource;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 文档控制器
*
* @author 暮光:城中城
* @since 2021年10月16日
*/
@AuthMan
@RestController
@RequestMapping("/doc-api/doc")
public class ApiDocumentController {
private static Logger logger = LoggerFactory.getLogger(ApiDocumentController.class);
@Resource
private ApiDocService swaggerDocService;
@Resource
private SwaggerHttpRequestService swaggerHttpRequestService;
/**
* 获取所有的文档地址
*
* @return 文档内容
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/list")
public ResponseJson<List<ApiDoc>> list(ApiDoc apiDoc, Integer pageNum, Integer pageSize) {
IPage<ApiDoc> docList = swaggerDocService.getApiDocList(apiDoc, pageNum, pageSize);
return DocResponseJson.ok(docList);
}
/**
* 获取文档内容
*
* @return 文档内容
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/detail")
public ResponseJson<List<ApiDoc>> detail(Long id) {
ApiDoc apiDoc = swaggerDocService.getById(id);
return DocResponseJson.ok(apiDoc);
}
/**
* 添加文档
*
* @return 文档内容
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/add")
public ResponseJson<List<ApiDoc>> add(HttpServletRequest request, ApiDoc apiDoc) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
apiDoc.setYn(1);
apiDoc.setCreateTime(new Date());
apiDoc.setCreateUserId(currentUser.getUserId());
apiDoc.setCreateUserName(currentUser.getUsername());
// url类型
if (Objects.equals(apiDoc.getDocType(), 1)) {
// UI地址替换为文档json地址
String docUrl = SwaggerDocUtil.replaceSwaggerResources(apiDoc.getDocUrl());
if (SwaggerDocUtil.isSwaggerResources(docUrl)) {
String swaggerDomain = SwaggerDocUtil.getSwaggerResourceDomain(docUrl);
List<SwaggerResource> resourceList;
try {
String resourcesStr = swaggerHttpRequestService.requestSwaggerUrl(request, docUrl, swaggerDomain);
resourceList = JSON.parseArray(resourcesStr, SwaggerResource.class);
} catch (Exception e) {
e.printStackTrace();
return DocResponseJson.warn("解析文档地址失败:" + e.getMessage());
}
if (resourceList == null || resourceList.isEmpty()) {
return DocResponseJson.warn("该地址未找到文档");
}
// 删除原有文档
if (apiDoc.getId() != null) {
swaggerDocService.removeById(apiDoc.getId());
}
// 存明细地址
for (SwaggerResource resource : resourceList) {
apiDoc.setId(null);
apiDoc.setDocUrl(swaggerDomain + resource.getUrl());
apiDoc.setName(resource.getName());
swaggerDocService.save(apiDoc);
}
} else {
swaggerDocService.saveOrUpdate(apiDoc);
}
} else if (Objects.equals(apiDoc.getDocType(), 2) || Objects.equals(apiDoc.getDocType(), 4)) {
swaggerDocService.saveOrUpdate(apiDoc);
} else {
return DocResponseJson.warn("暂不支持的文档类型");
}
return DocResponseJson.ok();
}
/**
* 修改文档基本信息
*
* @return 无
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/update")
public ResponseJson<List<ApiDoc>> update(ApiDoc apiDoc) {
if (apiDoc.getId() == null) {
return DocResponseJson.warn("请指定修改的记录ID");
}
ApiDoc swaggerDocUp = new ApiDoc();
swaggerDocUp.setId(apiDoc.getId());
swaggerDocUp.setDocStatus(apiDoc.getDocStatus());
swaggerDocUp.setDocUrl(apiDoc.getDocUrl());
swaggerDocUp.setJsonContent(apiDoc.getJsonContent());
swaggerDocUp.setName(apiDoc.getName());
swaggerDocUp.setOpenVisit(apiDoc.getOpenVisit());
swaggerDocUp.setRewriteDomain(apiDoc.getRewriteDomain());
swaggerDocUp.setYn(apiDoc.getYn());
swaggerDocService.updateById(swaggerDocUp);
return DocResponseJson.ok();
}
@RequestMapping("/apis")
public ResponseJson<List<ApiDoc>> resources() {
QueryWrapper<ApiDoc> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("yn", 1);
queryWrapper.eq("doc_status", 1);
queryWrapper.orderByAsc("id");
queryWrapper.select("id", "name", "doc_type", "doc_url", "rewrite_domain", "open_visit", "doc_status");
List<ApiDoc> docList = swaggerDocService.list(queryWrapper);
return DocResponseJson.ok(docList);
}
@RequestMapping("/apis/detail")
public ResponseJson<Object> detail(HttpServletRequest request, Long id) {
ApiDoc apiDoc = swaggerDocService.getById(id);
if (apiDoc == null) {
return DocResponseJson.warn("文档不存在");
}
if (Objects.equals(apiDoc.getDocType(), 1)) {
try {
String docsDomain = SwaggerDocUtil.getV2ApiDocsDomain(apiDoc.getDocUrl());
String contentStr = swaggerHttpRequestService.requestSwaggerUrl(request, apiDoc.getDocUrl(), docsDomain);
return DocResponseJson.ok(contentStr);
} catch (Exception e) {
e.printStackTrace();
return DocResponseJson.warn("请求文档失败");
}
}
if (Objects.equals(apiDoc.getDocType(), 2) || Objects.equals(apiDoc.getDocType(), 4)) {
return DocResponseJson.ok(apiDoc.getJsonContent());
}
return DocResponseJson.warn("暂不支持的文档类型");
}
}

View File

@@ -0,0 +1,94 @@
package com.zyplayer.doc.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zyplayer.doc.core.annotation.AuthMan;
import com.zyplayer.doc.core.json.DocResponseJson;
import com.zyplayer.doc.core.json.ResponseJson;
import com.zyplayer.doc.data.config.security.DocUserDetails;
import com.zyplayer.doc.data.config.security.DocUserUtil;
import com.zyplayer.doc.data.repository.manage.entity.ApiDoc;
import com.zyplayer.doc.data.repository.manage.entity.ApiGlobalParam;
import com.zyplayer.doc.data.service.manage.ApiGlobalParamService;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
/**
* 全局参数控制器
*
* @author 暮光:城中城
* @since 2021年10月16日
*/
@AuthMan
@RestController
@RequestMapping("/doc-api/global-param")
public class ApiGlobalParamController {
private static Logger logger = LoggerFactory.getLogger(ApiGlobalParamController.class);
@Resource
private ApiGlobalParamService apiGlobalParamService;
/**
* 获取所有的全局参数
*
* @return 全局参数列表
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/list")
public ResponseJson<List<ApiGlobalParam>> list() {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
QueryWrapper<ApiGlobalParam> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("yn", 1);
queryWrapper.eq("create_user_id", currentUser.getUserId());
queryWrapper.orderByDesc("id");
List<ApiGlobalParam> globalParamList = apiGlobalParamService.list(queryWrapper);
return DocResponseJson.ok(globalParamList);
}
/**
* 修改全局参数
*
* @return 无
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/update")
public ResponseJson<List<ApiDoc>> update(ApiGlobalParam globalParam) {
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
if (globalParam.getId() == null) {
globalParam.setYn(1);
globalParam.setCreateTime(new Date());
globalParam.setCreateUserId(currentUser.getUserId());
globalParam.setCreateUserName(currentUser.getUsername());
} else {
ApiGlobalParam param = apiGlobalParamService.getById(globalParam.getId());
if (param == null || !Objects.equals(param.getCreateUserId(), currentUser.getUserId())) {
return DocResponseJson.warn("目标全局参数不存在");
}
}
QueryWrapper<ApiGlobalParam> wrapper = new QueryWrapper<>();
wrapper.eq("yn", 1);
wrapper.eq("param_key", globalParam.getParamKey());
wrapper.eq("create_user_id", currentUser.getUserId());
List<ApiGlobalParam> paramList = apiGlobalParamService.list(wrapper);
if (CollectionUtils.isNotEmpty(paramList)) {
if (paramList.size() > 1 || !Objects.equals(paramList.get(0).getId(), globalParam.getId())) {
return DocResponseJson.warn("全局参数名称不能重复");
}
}
apiGlobalParamService.saveOrUpdate(globalParam);
return DocResponseJson.ok();
}
}

View File

@@ -0,0 +1,62 @@
package com.zyplayer.doc.api.controller;
import com.zyplayer.doc.core.annotation.AuthMan;
import com.zyplayer.doc.core.json.DocResponseJson;
import com.zyplayer.doc.core.json.ResponseJson;
import com.zyplayer.doc.api.controller.param.ProxyRequestParam;
import com.zyplayer.doc.api.controller.vo.ProxyRequestResultVo;
import com.zyplayer.doc.api.service.SwaggerHttpRequestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 请求参数控制器
*
* @author 暮光:城中城
* @since 2021年10月16日
*/
@AuthMan
@RestController
@RequestMapping("/doc-api/proxy")
public class ApiPoxyRequestController {
private static Logger logger = LoggerFactory.getLogger(ApiPoxyRequestController.class);
@Resource
private SwaggerHttpRequestService swaggerHttpRequestService;
/**
* 代理接口请求
*
* @return 请求参数
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/request")
public ResponseJson<ProxyRequestResultVo> request(HttpServletRequest request, ProxyRequestParam requestParam) {
ProxyRequestResultVo requestResult = swaggerHttpRequestService.proxyRequest(request, requestParam);
return DocResponseJson.ok(requestResult);
}
/**
* 代理接口下载请求
*
* @return 请求参数
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/download")
public ResponseJson<ProxyRequestResultVo> download(HttpServletRequest request, HttpServletResponse response, ProxyRequestParam requestParam) {
swaggerHttpRequestService.proxyDownload(request, response, requestParam);
return DocResponseJson.ok();
}
}

View File

@@ -0,0 +1,75 @@
package com.zyplayer.doc.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zyplayer.doc.core.annotation.AuthMan;
import com.zyplayer.doc.core.json.DocResponseJson;
import com.zyplayer.doc.core.json.ResponseJson;
import com.zyplayer.doc.data.config.security.DocUserDetails;
import com.zyplayer.doc.data.config.security.DocUserUtil;
import com.zyplayer.doc.data.repository.manage.entity.ApiDoc;
import com.zyplayer.doc.data.repository.manage.entity.ApiRequestParam;
import com.zyplayer.doc.data.service.manage.ApiRequestParamService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
* 请求参数控制器
*
* @author 暮光:城中城
* @since 2021年10月16日
*/
@AuthMan
@RestController
@RequestMapping("/doc-api/request-param")
public class ApiRequestParamController {
private static Logger logger = LoggerFactory.getLogger(ApiRequestParamController.class);
@Resource
private ApiRequestParamService apiRequestParamService;
/**
* 获取所有的请求参数
*
* @return 请求参数
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/query")
public ResponseJson<ApiRequestParam> query(String docUrl) {
QueryWrapper<ApiRequestParam> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("yn", 1);
queryWrapper.eq("doc_url", docUrl);
ApiRequestParam requestParam = apiRequestParamService.getOne(queryWrapper);
return DocResponseJson.ok(requestParam);
}
/**
* 修改请求参数
*
* @return 无
* @author 暮光:城中城
* @since 2021年10月16日
*/
@ResponseBody
@PostMapping(value = "/update")
public ResponseJson<List<ApiDoc>> update(ApiRequestParam apiRequestParam) {
QueryWrapper<ApiRequestParam> updateWrapper = new QueryWrapper<>();
updateWrapper.eq("doc_url", apiRequestParam.getDocUrl());
DocUserDetails currentUser = DocUserUtil.getCurrentUser();
apiRequestParam.setYn(1);
apiRequestParam.setCreateTime(new Date());
apiRequestParam.setCreateUserId(currentUser.getUserId());
apiRequestParam.setCreateUserName(currentUser.getUsername());
apiRequestParamService.update(apiRequestParam, updateWrapper);
return DocResponseJson.ok();
}
}

View File

@@ -0,0 +1,99 @@
package com.zyplayer.doc.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zyplayer.doc.core.annotation.AuthMan;
import com.zyplayer.doc.core.exception.ConfirmException;
import com.zyplayer.doc.core.json.DocResponseJson;
import com.zyplayer.doc.data.repository.manage.entity.ApiDoc;
import com.zyplayer.doc.data.service.manage.ApiDocService;
import com.zyplayer.doc.api.controller.vo.SwaggerResourceVo;
import com.zyplayer.doc.api.framework.utils.SwaggerDocUtil;
import com.zyplayer.doc.api.service.SwaggerHttpRequestService;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.spring.web.json.Json;
import springfox.documentation.swagger.web.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* 承接了所有的ApiResourceController的接口
*
* @author 暮光:城中城
* @since 2021年10月16日
*/
@AuthMan
@RestController
public class ApiSwaggerProxyController {
private static final String HAL_MEDIA_TYPE = "application/hal+json";
@Resource
private ApiDocService swaggerDocService;
@Resource
private SwaggerHttpRequestService swaggerHttpRequestService;
@ResponseBody
@RequestMapping("/swagger-resources")
public List<SwaggerResourceVo> swaggerResources() {
List<SwaggerResourceVo> resourceList = new LinkedList<>();
QueryWrapper<ApiDoc> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("yn", 1);
queryWrapper.eq("doc_status", 1);
queryWrapper.in("doc_type", 1, 2);
queryWrapper.orderByAsc("id");
queryWrapper.select("id", "name", "rewrite_domain");
List<ApiDoc> docList = swaggerDocService.list(queryWrapper);
for (ApiDoc swaggerDoc : docList) {
SwaggerResourceVo resource = new SwaggerResourceVo();
resource.setUrl("/v2/api-docs?id=" + swaggerDoc.getId());
resource.setName(swaggerDoc.getName());
resource.setSwaggerVersion("2.0");
resource.setRewriteDomain(swaggerDoc.getRewriteDomain());
resourceList.add(resource);
}
return resourceList;
}
@ResponseBody
@RequestMapping(value = "/v2/api-docs", produces = {MimeTypeUtils.APPLICATION_JSON_VALUE, HAL_MEDIA_TYPE})
public ResponseEntity<Object> content(HttpServletRequest request, Long id) {
ApiDoc swaggerDoc = swaggerDocService.getById(id);
if (swaggerDoc == null) {
throw new ConfirmException("文档不存在");
}
if (Objects.equals(swaggerDoc.getDocType(), 1)) {
try {
String docsDomain = SwaggerDocUtil.getV2ApiDocsDomain(swaggerDoc.getDocUrl());
String contentStr = swaggerHttpRequestService.requestSwaggerUrl(request, swaggerDoc.getDocUrl(), docsDomain);
return new ResponseEntity<>(new Json(contentStr), HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(DocResponseJson.warn("请求文档失败"), HttpStatus.OK);
}
}
return new ResponseEntity<>(new Json(swaggerDoc.getJsonContent()), HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = "/swagger-resources/configuration/security")
public ResponseEntity<SecurityConfiguration> securityConfiguration() {
SecurityConfiguration securityConfiguration = SecurityConfigurationBuilder.builder().build();
return new ResponseEntity<>(securityConfiguration, HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = "/swagger-resources/configuration/ui")
public ResponseEntity<UiConfiguration> uiConfiguration() {
UiConfiguration uiConfiguration = UiConfigurationBuilder.builder().build();
return new ResponseEntity<>(uiConfiguration, HttpStatus.OK);
}
}

View File

@@ -0,0 +1,28 @@
package com.zyplayer.doc.api.controller.param;
/**
* 参数信息
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public class ParamData {
private String code;
private String value;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,111 @@
package com.zyplayer.doc.api.controller.param;
import com.alibaba.fastjson.JSON;
import java.util.List;
/**
* 代理请求参数
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public class ProxyRequestParam {
private String url;
private String host;
private String method;
private String contentType;
private String headerParam;
private String cookieParam;
private String formParam;
private String formEncodeParam;
private String bodyParam;
public List<ParamData> getHeaderParamData() {
return JSON.parseArray(headerParam, ParamData.class);
}
public List<ParamData> getCookieParamData() {
return JSON.parseArray(cookieParam, ParamData.class);
}
public List<ParamData> getFormParamData() {
return JSON.parseArray(formParam, ParamData.class);
}
public List<ParamData> getFormEncodeParamData() {
return JSON.parseArray(formEncodeParam, ParamData.class);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHeaderParam() {
return headerParam;
}
public void setHeaderParam(String headerParam) {
this.headerParam = headerParam;
}
public String getCookieParam() {
return cookieParam;
}
public void setCookieParam(String cookieParam) {
this.cookieParam = cookieParam;
}
public String getFormParam() {
return formParam;
}
public void setFormParam(String formParam) {
this.formParam = formParam;
}
public String getFormEncodeParam() {
return formEncodeParam;
}
public void setFormEncodeParam(String formEncodeParam) {
this.formEncodeParam = formEncodeParam;
}
public String getBodyParam() {
return bodyParam;
}
public void setBodyParam(String bodyParam) {
this.bodyParam = bodyParam;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}

View File

@@ -0,0 +1,38 @@
package com.zyplayer.doc.api.controller.vo;
/**
* cookie返回值对象
*
* @author 暮光:城中城
* @since 2018年8月21日
*/
public class HttpCookieVo {
private String name;
private String value;
public HttpCookieVo() {
}
public HttpCookieVo(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,29 @@
package com.zyplayer.doc.api.controller.vo;
/**
* header返回值对象
*
* @author 暮光:城中城
* @since 2018年8月21日
*/
public class HttpHeaderVo {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,76 @@
package com.zyplayer.doc.api.controller.vo;
import java.util.List;
/**
* 代理请求结果
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public class ProxyRequestResultVo {
private List<HttpCookieVo> cookies;
private List<HttpHeaderVo> headers;
private Integer status;
private Long useTime;
private Integer contentLength;
private String data;
private String errorMsg;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getUseTime() {
return useTime;
}
public void setUseTime(Long useTime) {
this.useTime = useTime;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public List<HttpCookieVo> getCookies() {
return cookies;
}
public void setCookies(List<HttpCookieVo> cookies) {
this.cookies = cookies;
}
public List<HttpHeaderVo> getHeaders() {
return headers;
}
public void setHeaders(List<HttpHeaderVo> headers) {
this.headers = headers;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public Integer getContentLength() {
return contentLength;
}
public void setContentLength(Integer contentLength) {
this.contentLength = contentLength;
}
}

View File

@@ -0,0 +1,64 @@
package com.zyplayer.doc.api.controller.vo;
import com.google.common.collect.ComparisonChain;
/**
* swagger资源信息
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public class SwaggerResourceVo implements Comparable<SwaggerResourceVo> {
private String name;
private String url;
private String swaggerVersion;
private String rewriteDomain;
public SwaggerResourceVo() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLocation() {
return this.url;
}
public void setLocation(String location) {
this.url = location;
}
public String getSwaggerVersion() {
return this.swaggerVersion;
}
public void setSwaggerVersion(String swaggerVersion) {
this.swaggerVersion = swaggerVersion;
}
public int compareTo(SwaggerResourceVo other) {
return ComparisonChain.start().compare(this.swaggerVersion, other.swaggerVersion).compare(this.name, other.name).result();
}
public String getRewriteDomain() {
return rewriteDomain;
}
public void setRewriteDomain(String rewriteDomain) {
this.rewriteDomain = rewriteDomain;
}
}

View File

@@ -0,0 +1,23 @@
package com.zyplayer.doc.api.framework.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import java.lang.annotation.*;
/**
* 开启api接口文档模块注解
*
* @author 暮光:城中城
* @since 2021-11-04
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@ComponentScan(basePackages = {
"com.zyplayer.doc.api",
})
public @interface EnableDocApi {
}

View File

@@ -0,0 +1,53 @@
package com.zyplayer.doc.api.framework.utils;
/**
* swagger文档工具类
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public class SwaggerDocUtil {
public static String replaceSwaggerResources(String docUrl) {
int htmlIndex = docUrl.indexOf("/swagger-ui.html");
if (htmlIndex > 0) {
docUrl = docUrl.substring(0, htmlIndex) + "/swagger-resources";
}
return docUrl;
}
public static boolean isSwaggerResources(String docUrl) {
return docUrl.contains("/swagger-resources");
}
public static String getSwaggerResourceDomain(String docUrl) {
int index = docUrl.indexOf("/swagger-resources");
if (index >= 0) {
return docUrl.substring(0, index);
}
return "";
}
public static String getV2ApiDocsDomain(String docUrl) {
int index = docUrl.indexOf("/v2/api-docs");
if (index >= 0) {
return docUrl.substring(0, index);
}
return "";
}
public static String getDomainHost(String domain) {
domain = domain.replace("http://", "");
domain = domain.replace("https://", "");
int index = domain.indexOf("/");
if (index >= 0) {
return domain.substring(0, index);
}
return domain;
}
public static boolean isSwaggerLocation(String docUrl) {
return docUrl.contains("/v2/api-docs");
}
}

View File

@@ -0,0 +1,217 @@
package com.zyplayer.doc.api.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.zyplayer.doc.api.controller.param.ProxyRequestParam;
import com.zyplayer.doc.api.controller.vo.HttpCookieVo;
import com.zyplayer.doc.api.controller.vo.HttpHeaderVo;
import com.zyplayer.doc.api.controller.vo.ProxyRequestResultVo;
import com.zyplayer.doc.api.framework.utils.SwaggerDocUtil;
import com.zyplayer.doc.core.exception.ConfirmException;
import com.zyplayer.doc.data.repository.manage.entity.ApiGlobalParam;
import com.zyplayer.doc.data.service.manage.ApiGlobalParamService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.HttpCookie;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* swagger请求服务
*
* @author 暮光:城中城
* @since 2021-11-04
*/
@Service
public class SwaggerHttpRequestService {
@Resource
private ApiGlobalParamService apiGlobalParamService;
private static final Map<String, Method> requestMethodMap = Stream.of(Method.values()).collect(Collectors.toMap(val -> val.name().toLowerCase(), val -> val));
List<String> domainHeaderKeys = Arrays.asList("referer", "origin");
List<String> needRequestHeaderKeys = Arrays.asList("user-agent");
/**
* 请求真实的swagger文档内容
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public String requestSwaggerUrl(HttpServletRequest request, String docUrl, String docDomain) {
List<ApiGlobalParam> globalParamList = apiGlobalParamService.getGlobalParamList();
Map<String, Object> globalFormParamMap = globalParamList.stream().filter(item -> Objects.equals(item.getParamType(), 1))
.collect(Collectors.toMap(ApiGlobalParam::getParamKey, ApiGlobalParam::getParamValue, (val1, val2) -> val1));
Map<String, String> globalHeaderParamMap = globalParamList.stream().filter(item -> Objects.equals(item.getParamType(), 2))
.collect(Collectors.toMap(ApiGlobalParam::getParamKey, ApiGlobalParam::getParamValue, (val1, val2) -> val1));
Map<String, String> globalCookieParamMap = globalParamList.stream().filter(item -> Objects.equals(item.getParamType(), 3))
.collect(Collectors.toMap(ApiGlobalParam::getParamKey, ApiGlobalParam::getParamValue, (val1, val2) -> val1));
Map<String, String> requestHeaders = this.getHttpHeader(request, globalHeaderParamMap);
if (StringUtils.isNotBlank(docDomain)) {
domainHeaderKeys.forEach(key -> requestHeaders.put(key, docDomain));
requestHeaders.put("host", SwaggerDocUtil.getDomainHost(docDomain));
}
// 执行请求
String resultStr = HttpRequest.get(docUrl)
.form(globalFormParamMap)
.addHeaders(requestHeaders)
.header("Accept", "application/json, text/javascript, */*; q=0.01")
.cookie(this.getHttpCookie(request, globalCookieParamMap, null))
.timeout(10000).execute().body();
return resultStr;
}
/**
* 执行代理请求
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public void proxyDownload(HttpServletRequest request, HttpServletResponse response, ProxyRequestParam requestParam) {
try {
HttpResponse httpResponse = this.getHttpResponse(request, requestParam);
Map<String, List<String>> responseHeaders = httpResponse.headers();
if (MapUtils.isNotEmpty(responseHeaders)) {
for (Map.Entry<String, List<String>> httpHeader : responseHeaders.entrySet()) {
response.addHeader(httpHeader.getKey(), String.join(";", httpHeader.getValue()));
}
}
httpResponse.writeBody(response.getOutputStream(), true, null);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 执行代理请求
*
* @author 暮光:城中城
* @since 2021-11-04
*/
public ProxyRequestResultVo proxyRequest(HttpServletRequest request, ProxyRequestParam requestParam) {
ProxyRequestResultVo resultVo = new ProxyRequestResultVo();
long startTime = System.currentTimeMillis();
try {
HttpResponse httpResponse = getHttpResponse(request, requestParam);
resultVo.setData(httpResponse.body());
resultVo.setStatus(httpResponse.getStatus());
resultVo.setContentLength(httpResponse.bodyBytes().length);
// 设置返回的cookies
List<HttpCookie> responseCookies = httpResponse.getCookies();
if (CollectionUtils.isNotEmpty(responseCookies)) {
resultVo.setCookies(responseCookies.stream().map(val -> new HttpCookieVo(val.getName(), val.getValue())).collect(Collectors.toList()));
}
// 设置返回的headers
Map<String, List<String>> responseHeaders = httpResponse.headers();
if (MapUtils.isNotEmpty(responseHeaders)) {
List<HttpHeaderVo> headerList = new ArrayList<>(responseHeaders.size());
for (Map.Entry<String, List<String>> httpHeader : responseHeaders.entrySet()) {
HttpHeaderVo vo = new HttpHeaderVo();
vo.setName(httpHeader.getKey());
vo.setValue(String.join(";", httpHeader.getValue()));
headerList.add(vo);
}
resultVo.setHeaders(headerList);
}
} catch (Exception e) {
e.printStackTrace();
resultVo.setErrorMsg(e.getMessage());
}
resultVo.setUseTime(System.currentTimeMillis() - startTime);
return resultVo;
}
private HttpResponse getHttpResponse(HttpServletRequest request, ProxyRequestParam requestParam) {
// 执行请求
Method method = requestMethodMap.get(requestParam.getMethod());
if (method == null) {
throw new ConfirmException("不支持的请求方式:" + requestParam.getMethod());
}
HttpRequest httpRequest = HttpUtil.createRequest(method, requestParam.getUrl());
// header获取
Map<String, String> headerParam = new HashMap<>();
requestParam.getHeaderParamData().forEach(item -> headerParam.put(item.getCode(), item.getValue()));
Map<String, String> requestHeaders = this.getHttpHeader(request, headerParam);
if (StringUtils.isNotBlank(requestParam.getHost())) {
domainHeaderKeys.forEach(key -> requestHeaders.put(key, requestParam.getHost()));
requestHeaders.put("host", SwaggerDocUtil.getDomainHost(requestParam.getHost()));
}
// http自带参数
httpRequest.addHeaders(requestHeaders);
// 用户输入的参数
requestParam.getFormParamData().forEach(data -> httpRequest.form(data.getCode(), data.getValue()));
requestParam.getFormEncodeParamData().forEach(data -> httpRequest.form(data.getCode(), data.getValue()));
// cookie参数
Map<String, String> cookieParam = new HashMap<>();
String headerCookie = headerParam.getOrDefault("Cookie", headerParam.get("cookie"));
requestParam.getCookieParamData().forEach(item -> cookieParam.put(item.getCode(), item.getValue()));
httpRequest.cookie(this.getHttpCookie(request, cookieParam, headerCookie));
if (StringUtils.isNotBlank(requestParam.getBodyParam())) {
httpRequest.body(requestParam.getBodyParam());
}
// 强制设置类型貌似不用刻意设置如果写的application/json参数是表单传过去收不到值先注释这个
// if (StringUtils.isNotBlank(requestParam.getContentType())) {
// httpRequest.contentType(requestParam.getContentType());
// }
// 执行请求
return httpRequest.timeout(10000).execute();
}
/**
* 获取http的cookie
*
* @author 暮光:城中城
* @since 2021-11-04
*/
private List<HttpCookie> getHttpCookie(HttpServletRequest request, Map<String, String> globalCookieParamMap, String headerCookie) {
List<HttpCookie> httpCookies = new LinkedList<>();
for (Cookie cookie : request.getCookies()) {
httpCookies.add(new HttpCookie(cookie.getName(), cookie.getValue()));
}
if (StringUtils.isNotBlank(headerCookie)) {
Arrays.stream(headerCookie.split(";")).map(String::trim).forEach(cookie -> {
String[] cookieArr = StringUtils.split(cookie, "=", 2);
if (ArrayUtils.getLength(cookieArr) == 2) {
httpCookies.add(new HttpCookie(cookieArr[0], cookieArr[1]));
}
});
}
if (MapUtils.isNotEmpty(globalCookieParamMap)) {
globalCookieParamMap.forEach((key, value) -> httpCookies.add(new HttpCookie(key, value)));
}
return httpCookies;
}
/**
* 获取http的header
*
* @author 暮光:城中城
* @since 2021-11-04
*/
private Map<String, String> getHttpHeader(HttpServletRequest request, Map<String, String> globalHeaderParamMap) {
Map<String, String> headerParamMap = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = StringUtils.lowerCase(headerNames.nextElement());
if (needRequestHeaderKeys.contains(headerName)) {
headerParamMap.put(headerName, request.getHeader(headerName));
}
}
if (MapUtils.isNotEmpty(globalHeaderParamMap)) {
headerParamMap.putAll(globalHeaderParamMap);
}
return headerParamMap;
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
import{u as S,V as w,r as c,o,c as l,w as t,a,m as f,t as n,b as i,F as g,k as s,e as m,d as V}from"./vendor.d3f949c4.js";import{m as B}from"./index.ea6b273d.js";import{_ as C}from"./main.c59b3205.js";const N={setup(){const _=S(),D=w(()=>_.state.swaggerDoc),u=w(()=>_.state.swaggerDoc.info),e=w(()=>_.state.methodStatistic);return{swaggerDoc:D,swaggerDocInfo:u,methodStatistic:e,getDescription:I=>B.exports.markdownIt.render(I||"")}}},j=["href"],L=["href"],O=["href"],T=["innerHTML"],E={key:1,style:{"text-align":"center"}};function F(_,D,u,e,p,I){const r=c("a-form-item"),h=c("a-divider"),b=c("a-statistic"),k=c("a-card"),v=c("a-col"),y=c("a-row"),x=c("a-form");return o(),l(k,null,{default:t(()=>[e.swaggerDocInfo?(o(),l(x,{key:0,"label-col":{span:4},"wrapper-col":{span:20}},{default:t(()=>[a(r,{label:"\u6807\u9898"},{default:t(()=>[f(n(e.swaggerDocInfo.title),1)]),_:1}),a(r,{label:"\u7248\u672C"},{default:t(()=>[f(n(e.swaggerDocInfo.version),1)]),_:1}),e.swaggerDocInfo.contact?(o(),l(r,{key:0,label:"\u4F5C\u8005"},{default:t(()=>[e.swaggerDocInfo.contact.name?(o(),i(g,{key:0},[f(n(e.swaggerDocInfo.contact.name),1)],64)):s("",!0),e.swaggerDocInfo.contact.email?(o(),i(g,{key:1},[a(h,{type:"vertical"}),f(n(e.swaggerDocInfo.contact.email),1)],64)):s("",!0),e.swaggerDocInfo.contact.url?(o(),i(g,{key:2},[a(h,{type:"vertical"}),m("a",{href:e.swaggerDocInfo.contact.url,target:"_blank"},n(e.swaggerDocInfo.contact.url),9,j)],64)):s("",!0)]),_:1})):s("",!0),a(r,{label:"host"},{default:t(()=>[f(n(e.swaggerDoc.host),1)]),_:1}),e.swaggerDocInfo.license?(o(),l(r,{key:1,label:"\u8BB8\u53EF\u8BC1"},{default:t(()=>[m("a",{href:e.swaggerDocInfo.license.url,target:"_blank"},n(e.swaggerDocInfo.license.name),9,L)]),_:1})):s("",!0),e.swaggerDocInfo.termsOfService?(o(),l(r,{key:2,label:"\u670D\u52A1\u6761\u6B3E"},{default:t(()=>[m("a",{href:e.swaggerDocInfo.termsOfService,target:"_blank"},n(e.swaggerDocInfo.termsOfService),9,O)]),_:1})):s("",!0),a(r,{label:"\u6587\u6863\u8BF4\u660E"},{default:t(()=>[m("div",{class:"markdown-body",innerHTML:e.getDescription(e.swaggerDocInfo.description)},null,8,T)]),_:1}),a(r,{label:"\u63A5\u53E3\u7EDF\u8BA1"},{default:t(()=>[a(y,{gutter:[16,16]},{default:t(()=>[(o(),i(g,null,V(["get","post","put","delete","head","patch","options","trace","total"],d=>(o(),i(g,null,[e.methodStatistic[d]?(o(),l(v,{key:0,span:6},{default:t(()=>[a(k,{size:"small"},{default:t(()=>[a(b,{title:d==="total"?"\u603B\u8BA1":d.toUpperCase()+"\u65B9\u6CD5",value:e.methodStatistic[d],suffix:"\u4E2A"},null,8,["title","value"])]),_:2},1024)]),_:2},1024)):s("",!0)],64))),64))]),_:1})]),_:1})]),_:1})):(o(),i("div",E,"\u6682\u65E0\u6587\u6863\u4FE1\u606F\uFF0C\u8BF7\u5148\u9009\u62E9\u6587\u6863"))]),_:1})}var U=C(N,[["render",F]]);export{U as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
var G=Object.defineProperty;var V=Object.getOwnPropertySymbols;var K=Object.prototype.hasOwnProperty,N=Object.prototype.propertyIsEnumerable;var w=(c,e,l)=>e in c?G(c,e,{enumerable:!0,configurable:!0,writable:!0,value:l}):c[e]=l,I=(c,e)=>{for(var l in e||(e={}))K.call(e,l)&&w(c,l,e[l]);if(V)for(var l of V(e))N.call(e,l)&&w(c,l,e[l]);return c};import{z as D}from"./custom.914a01ae.js";import{u as P,y as x,G as U,r as v,o,b as r,e as A,a as s,w as n,F as f,B as F,c as k,t as B,k as C,m as d}from"./vendor.d3f949c4.js";import{_ as S}from"./main.c59b3205.js";const j={setup(){const c=P();let e=x([]),l=x(!1);const a=async()=>{i.value={},l.value=!0,D.docApiGlobalParamList().then(t=>{setTimeout(()=>l.value=!1,500),e.value=t.data||[],c.commit("setGlobalParam",e.value)})};let i=x({}),T=x();const p=()=>{i.value.isEdit&&y(i.value);let t={isEdit:!0};e.value.unshift(t),i.value=t,setTimeout(()=>{let _=document.getElementsByClassName("ant-table-body")[0];_.scrollTop=0},0)},b=t=>{i.value.isEdit&&y(i.value),t.isEdit=!0,i.value=I({},t)},y=t=>{t.isEdit=!1,t.id?e.value.forEach(_=>_.isEdit=!1):e.value=e.value.filter(_=>_!==t),i.value={}},L=t=>{if(!i.value.paramKey||!i.value.paramValue){F.error("\u53C2\u6570\u540D\u6216\u53C2\u6570\u503C\u4E0D\u80FD\u4E3A\u7A7A");return}D.docApiGlobalParamUpdate(i.value).then(_=>{t.isEdit=!1,a()})},h=async t=>{D.docApiGlobalParamUpdate({id:t.id,yn:0}).then(_=>{a()})};return U(()=>{a()}),{docList:e,docListLoading:l,docEdit:i,tableRef:T,searchDocList:a,deleteDoc:h,editDoc:b,saveEditDoc:L,cancelEditDoc:y,addDocLine:p,docListColumns:[{title:"\u53C2\u6570\u540D\u79F0",dataIndex:"paramKey",width:250},{title:"\u53C2\u6570\u503C",dataIndex:"paramValue"},{title:"\u53C2\u6570\u4F4D\u7F6E",dataIndex:"paramType",width:120},{title:"\u64CD\u4F5C",dataIndex:"operation",fixed:"right",width:170}]}}},z={style:{"margin-bottom":"10px","text-align":"right"}},H=d("\u5237\u65B0"),M=d("\u65B0\u5EFA"),R={key:1},q={key:1},J=d("Form"),O=d("Header"),Q=d("Cookie"),W=d("Form"),X=d("Header"),Y=d("Cookie"),Z=d("\u53D6\u6D88"),$=d("\u4FDD\u5B58"),ee=d("\u7F16\u8F91"),ae=d("\u5220\u9664");function te(c,e,l,a,i,T){const p=v("a-button"),b=v("a-input"),y=v("a-select-option"),L=v("a-select"),h=v("a-tag"),t=v("a-popconfirm"),_=v("a-table");return o(),r(f,null,[A("div",z,[s(p,{onClick:a.searchDocList,type:"primary"},{default:n(()=>[H]),_:1},8,["onClick"]),s(p,{onClick:a.addDocLine},{default:n(()=>[M]),_:1},8,["onClick"])]),s(_,{dataSource:a.docList,columns:a.docListColumns,size:"middle",id:"paramTable",loading:a.docListLoading,pagination:!1,scroll:{x:1e3,y:"calc(100vh - 240px)"}},{bodyCell:n(({column:g,text:E,record:m})=>[g.dataIndex==="paramKey"?(o(),r(f,{key:0},[m.isEdit?(o(),k(b,{key:0,placeholder:"\u8BF7\u8F93\u5165\u53C2\u6570\u540D\u79F0",value:a.docEdit.paramKey,"onUpdate:value":e[0]||(e[0]=u=>a.docEdit.paramKey=u)},null,8,["value"])):(o(),r("span",R,B(E),1))],64)):C("",!0),g.dataIndex==="paramValue"?(o(),r(f,{key:1},[m.isEdit?(o(),k(b,{key:0,rows:1,placeholder:"\u8BF7\u8F93\u5165\u53C2\u6570\u503C",value:a.docEdit.paramValue,"onUpdate:value":e[1]||(e[1]=u=>a.docEdit.paramValue=u)},null,8,["value"])):(o(),r("span",q,B(E),1))],64)):C("",!0),g.dataIndex==="paramType"?(o(),r(f,{key:2},[m.isEdit?(o(),k(L,{key:0,placeholder:"\u53C2\u6570\u4F4D\u7F6E",value:a.docEdit.paramType,"onUpdate:value":e[2]||(e[2]=u=>a.docEdit.paramType=u),style:{width:"110px"}},{default:n(()=>[s(y,{value:1},{default:n(()=>[J]),_:1}),s(y,{value:2},{default:n(()=>[O]),_:1}),s(y,{value:3},{default:n(()=>[Q]),_:1})]),_:1},8,["value"])):(o(),r(f,{key:1},[E===1?(o(),k(h,{key:0,color:"green"},{default:n(()=>[W]),_:1})):E===2?(o(),k(h,{key:1,color:"pink"},{default:n(()=>[X]),_:1})):E===3?(o(),k(h,{key:2,color:"pink"},{default:n(()=>[Y]),_:1})):C("",!0)],64))],64)):C("",!0),g.dataIndex==="operation"?(o(),r(f,{key:3},[m.isEdit?(o(),r(f,{key:0},[s(p,{type:"link",onClick:u=>a.cancelEditDoc(m)},{default:n(()=>[Z]),_:2},1032,["onClick"]),s(p,{type:"link",onClick:u=>a.saveEditDoc(m)},{default:n(()=>[$]),_:2},1032,["onClick"])],64)):(o(),r(f,{key:1},[s(p,{type:"link",onClick:u=>a.editDoc(m)},{default:n(()=>[ee]),_:2},1032,["onClick"]),s(t,{title:"\u786E\u5B9A\u8981\u5220\u9664\u5417\uFF1F",onConfirm:u=>a.deleteDoc(m)},{default:n(()=>[s(p,{type:"link",danger:""},{default:n(()=>[ae]),_:1})]),_:2},1032,["onConfirm"])],64))],64)):C("",!0)]),_:1},8,["dataSource","columns","loading","scroll"])],64)}var de=S(j,[["render",te]]);export{de as default};

View File

@@ -0,0 +1 @@
import{_ as e}from"./main.c59b3205.js";import{o as t,b as o}from"./vendor.d3f949c4.js";const n={name:"SettingView",components:{},data(){return{}},computed:{},mounted(){},methods:{}};function r(a,c,s,m,d,i){return t(),o("div",null," \u5C55\u793A\u914D\u7F6E\u9875\u9762 ")}var f=e(n,[["render",r]]);export{f as default};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2017 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="bold" unicode="&#xe800;" d="M310 1q41-18 78-18 210 0 210 187 0 64-23 101-15 24-34 41t-38 26-45 14-47 6-53 1q-40 0-56-6 0-29 0-88t-1-88q0-5 0-38t0-54 2-47 7-37z m-8 417q23-4 61-4 46 0 80 7t61 25 42 50 14 79q0 39-16 68t-45 46-60 24-69 8q-28 0-73-7 0-28 3-84t2-85q0-15 0-45t-1-44q0-26 1-38z m-302-497l1 53q9 2 48 9t59 15q4 7 7 15t4 19 4 18 1 21 0 19v36q0 548-12 572-2 5-12 8t-25 6-28 4-27 3-17 2l-2 46q55 1 190 6t208 6q13 0 38-1t38 0q39 0 76-7t72-24 60-39 41-59 16-76q0-29-9-54t-22-40-36-32-41-25-47-22q86-20 144-75t57-138q0-56-20-101t-52-72-77-48-91-27-98-8q-25 0-74 2t-74 1q-59 0-171-6t-129-7z" horiz-adv-x="785.7" />
<glyph glyph-name="italic" unicode="&#xe801;" d="M0-78l10 48q12 4 34 9t40 11 33 13q16 19 23 56 1 4 35 162t63 303 29 165v14q-13 8-30 11t-39 4-32 3l10 58q19-1 67-4t84-4 67-1q27 0 55 1t68 4 54 4q-2-22-10-50-17-6-57-16t-60-19q-5-10-8-23t-5-23-4-25-4-24q-15-82-49-234t-43-198q-1-5-7-32t-11-51-9-46-4-32l1-10q9-3 103-18-2-24-9-55-6 0-18-1t-18-1q-16 0-49 6t-48 6q-77 1-115 1-28 0-79-5t-68-7z" horiz-adv-x="571.4" />
<glyph glyph-name="thumb-tack" unicode="&#xe802;" d="M650 779q12 0 24-5 19-8 29-23t11-35v-719q0-19-11-35t-29-23q-10-4-24-4-27 0-47 18l-246 236-246-236q-20-19-46-19-13 0-25 5-18 7-29 23t-11 35v719q0 19 11 35t29 23q12 5 25 5h585z" horiz-adv-x="714.3" />
<glyph glyph-name="link" unicode="&#xe803;" d="M813 171q0 23-16 38l-116 116q-16 16-38 16-24 0-40-18 1-1 10-10t12-12 9-11 7-14 2-15q0-23-16-38t-38-16q-8 0-15 2t-14 7-11 9-12 12-10 10q-19-17-19-40 0-23 16-38l115-116q15-15 38-15 22 0 38 15l82 81q16 16 16 37z m-393 394q0 22-15 38l-115 115q-16 16-38 16-22 0-38-15l-82-82q-16-15-16-37 0-22 16-38l116-116q15-15 38-15 23 0 40 17-2 2-11 11t-12 12-8 10-7 14-2 16q0 22 15 38t38 15q9 0 16-2t14-7 11-8 12-12 10-11q18 17 18 41z m500-394q0-66-48-113l-82-81q-46-47-113-47-68 0-114 48l-115 115q-46 47-46 114 0 68 49 116l-49 49q-48-49-116-49-67 0-114 47l-116 116q-47 47-47 114t47 113l82 82q47 46 114 46 67 0 114-47l115-116q46-46 46-113 0-69-49-117l49-49q48 49 116 49 67 0 114-47l116-116q47-47 47-114z" horiz-adv-x="928.6" />
<glyph glyph-name="picture-o" unicode="&#xe804;" d="M357 529q0-45-31-76t-76-32-76 32-31 76 31 76 76 31 76-31 31-76z m572-215v-250h-786v107l178 179 90-89 285 285z m53 393h-893q-7 0-12-5t-6-13v-678q0-7 6-13t12-5h893q7 0 13 5t5 13v678q0 8-5 13t-13 5z m89-18v-678q0-37-26-63t-63-27h-893q-36 0-63 27t-26 63v678q0 37 26 63t63 27h893q37 0 63-27t26-63z" horiz-adv-x="1071.4" />
<glyph glyph-name="repeat" unicode="&#xe805;" d="M857 707v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 22 7 38l77 77q-82 77-194 77-58 0-111-23t-91-61-61-91-23-111 23-111 61-91 91-61 111-23q66 0 125 29t100 82q4 6 13 7 8 0 14-5l76-77q5-4 6-11t-5-13q-60-74-147-114t-182-41q-87 0-167 34t-136 92-92 137-34 166 34 166 92 137 136 92 167 34q82 0 158-31t137-88l72 72q17 18 39 8 22-9 22-33z" horiz-adv-x="857.1" />
<glyph glyph-name="undo" unicode="&#xe806;" d="M857 350q0-87-34-166t-91-137-137-92-166-34q-96 0-183 41t-147 114q-4 6-4 13t5 11l76 77q6 5 14 5 9-1 13-7 41-53 100-82t126-29q58 0 110 23t92 61 61 91 22 111-22 111-61 91-92 61-110 23q-55 0-105-20t-90-57l77-77q17-16 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l72-72q60 57 137 88t159 31q87 0 166-34t137-92 91-137 34-166z" horiz-adv-x="857.1" />
<glyph glyph-name="trash-o" unicode="&#xe807;" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
<glyph glyph-name="floppy-o" unicode="&#xe808;" d="M214-7h429v214h-429v-214z m500 0h72v500q0 8-6 21t-11 20l-157 156q-5 6-19 12t-22 5v-232q0-22-15-38t-38-16h-322q-22 0-37 16t-16 38v232h-72v-714h72v232q0 22 16 38t37 16h465q22 0 38-16t15-38v-232z m-214 518v178q0 8-5 13t-13 5h-107q-7 0-13-5t-5-13v-178q0-7 5-13t13-5h107q7 0 13 5t5 13z m357-18v-518q0-22-15-38t-38-16h-750q-23 0-38 16t-16 38v750q0 22 16 38t38 16h517q23 0 50-12t42-26l156-157q16-15 27-42t11-49z" horiz-adv-x="857.1" />
<glyph glyph-name="compress" unicode="&#xe809;" d="M429 314v-250q0-14-11-25t-25-10-25 10l-81 81-185-186q-5-5-13-5t-12 5l-64 64q-6 6-6 13t6 13l185 185-80 80q-11 11-11 25t11 25 25 11h250q14 0 25-11t11-25z m421 375q0-7-6-12l-185-186 80-80q11-11 11-25t-11-25-25-11h-250q-14 0-25 11t-10 25v250q0 14 10 25t25 10 25-10l81-80 185 185q6 5 13 5t13-5l63-64q6-5 6-13z" horiz-adv-x="857.1" />
<glyph glyph-name="eye" unicode="&#xe80a;" d="M929 314q-85 132-213 197 34-58 34-125 0-103-73-177t-177-73-177 73-73 177q0 67 34 125-128-65-213-197 75-114 187-182t242-68 243 68 186 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38-78-129-210-206t-279-77-279 77-210 206q-11 19-11 38t11 39q78 128 210 205t279 78 279-78 210-205q11-20 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="eye-slash" unicode="&#xe80b;" d="M310 105l43 79q-48 35-76 88t-27 114q0 67 34 125-128-65-213-197 94-144 239-209z m217 424q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-11 8-19t19-8 19 8 8 19q0 48 34 82t82 34q11 0 19 8t8 19z m202 106q0-4 0-5-59-105-176-316t-176-316l-28-50q-5-9-15-9-7 0-75 39-9 6-9 16 0 7 25 49-80 36-147 96t-117 137q-11 17-11 38t11 39q86 131 212 207t277 76q50 0 100-10l31 54q5 9 15 9 3 0 10-3t18-9 18-10 18-10 10-7q9-5 9-15z m21-249q0-78-44-142t-117-91l157 280q4-25 4-47z m250-72q0-19-11-38-22-36-61-81-84-96-194-149t-234-53l41 74q119 10 219 76t169 171q-65 100-158 164l35 63q53-36 102-85t81-103q11-19 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="question-circle" unicode="&#xe80c;" d="M500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65-95 23q-136 0-207-119-9-13 4-24l74-55q4-4 10-4 9 0 14 7 30 38 48 51 19 14 48 14 27 0 48-15t21-33q0-21-11-34t-38-25q-35-15-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19 25 27 16 34 7 45z m214-107q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="times" unicode="&#xe80d;" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
<glyph glyph-name="align-left" unicode="&#xe80f;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-714q-15 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-857q-15 0-25 11t-11 25v72q0 14 11 25t25 10h857q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-643q-15 0-25 10t-11 25v72q0 14 11 25t25 11h643q14 0 25-11t10-25z" horiz-adv-x="1000" />
<glyph glyph-name="align-center" unicode="&#xe810;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m-214 214v-71q0-15-11-25t-25-11h-500q-14 0-25 11t-11 25v71q0 15 11 25t25 11h500q15 0 25-11t11-25z m143 215v-72q0-14-11-25t-25-11h-786q-14 0-25 11t-11 25v72q0 14 11 25t25 10h786q14 0 25-10t11-25z m-215 214v-72q0-14-10-25t-25-10h-358q-14 0-25 10t-10 25v72q0 14 10 25t25 11h358q14 0 25-11t10-25z" horiz-adv-x="1000" />
<glyph glyph-name="align-right" unicode="&#xe811;" d="M1000 100v-71q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v71q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 214v-71q0-15-11-25t-25-11h-714q-14 0-25 11t-11 25v71q0 15 11 25t25 11h714q15 0 25-11t11-25z m0 215v-72q0-14-11-25t-25-11h-857q-14 0-25 11t-11 25v72q0 14 11 25t25 10h857q15 0 25-10t11-25z m0 214v-72q0-14-11-25t-25-10h-643q-14 0-25 10t-10 25v72q0 14 10 25t25 11h643q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="arrows-alt" unicode="&#xf0b2;" d="M716 548l-198-198 198-198 80 80q17 18 39 8 22-9 22-33v-250q0-14-10-25t-26-11h-250q-23 0-32 23-10 21 7 38l81 81-198 198-198-198 80-81q17-17 8-38-10-23-33-23h-250q-15 0-25 11t-11 25v250q0 24 22 33 22 10 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-14 3-22 9-22 33v250q0 14 11 25t25 11h250q23 0 33-23 9-21-8-38l-80-81 198-198 198 198-81 81q-17 17-7 38 9 23 32 23h250q15 0 26-11t10-25v-250q0-24-22-33-7-3-14-3-14 0-25 11z" horiz-adv-x="857.1" />
<glyph glyph-name="bars" unicode="&#xf0c9;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-14-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
<glyph glyph-name="list-ul" unicode="&#xf0ca;" d="M214 64q0-44-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m0 286q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 7 5 12t13 6h678q7 0 13-6t5-12z m-786 518q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="list-ol" unicode="&#xf0cb;" d="M213-54q0-45-31-70t-75-26q-60 0-96 37l31 49q28-25 60-25 16 0 28 8t12 24q0 35-59 31l-14 31q4 6 18 24t24 31 20 21v1q-9 0-27-1t-27 0v-30h-59v85h186v-49l-53-65q28-6 45-27t17-49z m1 350v-89h-202q-4 20-4 30 0 29 14 52t31 38 37 27 31 24 14 25q0 14-9 22t-22 7q-25 0-45-32l-47 33q13 28 40 44t59 16q40 0 68-23t28-63q0-28-19-51t-42-36-42-28-20-30h71v34h59z m786-178v-107q0-7-5-13t-13-5h-678q-8 0-13 5t-5 13v107q0 8 5 13t13 5h678q7 0 13-6t5-12z m-786 502v-56h-187v56h60q0 22 0 67t1 68v7h-1q-5-10-28-30l-40 42 76 71h59v-225h60z m786-216v-108q0-7-5-12t-13-5h-678q-8 0-13 5t-5 12v108q0 7 5 12t13 5h678q7 0 13-5t5-12z m0 285v-107q0-7-5-12t-13-6h-678q-8 0-13 6t-5 12v107q0 8 5 13t13 5h678q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="strikethrough" unicode="&#xf0cc;" d="M982 350q8 0 13-5t5-13v-36q0-7-5-12t-13-5h-964q-8 0-13 5t-5 12v36q0 8 5 13t13 5h964z m-712 36q-16 19-29 44-27 55-27 105 0 101 75 173 74 71 219 71 28 0 94-11 36-7 98-27 6-21 12-66 8-68 8-102 0-10-3-25l-7-2-46 4-8 1q-28 83-58 114-49 51-117 51-64 0-101-33-38-32-38-81 0-41 37-78t156-72q38-12 96-37 33-16 53-29h-414z m283-143h229q4-22 4-51 0-62-23-119-13-31-40-58-20-19-61-45-44-27-85-37-45-12-113-12-64 0-109 13l-78 23q-32 8-40 15-5 5-5 12v8q0 60-1 87 0 17 0 38l1 20v25l57 1q8-19 17-40t12-31 7-15q20-32 45-52 24-20 59-32 33-12 73-12 36 0 78 15 43 14 68 48 26 34 26 72 0 47-45 87-19 16-76 40z" horiz-adv-x="1000" />
<glyph glyph-name="underline" unicode="&#xf0cd;" d="M27 726q-21 1-25 2l-2 49q7 1 22 1 34 0 63-3 74-4 93-4 47 0 93 2 65 2 82 3 31 0 48 1l-1-8 1-36v-5q-33-5-69-5-33 0-44-14-7-7-7-73 0-7 0-18t0-15l1-127 8-157q3-69 28-112 20-33 54-52 49-26 98-26 59 0 107 16 31 10 55 28 27 20 37 36 20 31 29 63 12 41 12 128 0 44-2 72t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48h47l114-6q43-2 110 6l10-1q3-22 3-29 0-4-2-17-25-7-47-8-41-6-44-9-8-8-8-23 0-4 0-15t1-17q5-11 13-221 3-109-9-170-8-42-23-68-21-36-62-69-42-31-102-49-61-19-142-19-93 0-159 26-66 26-99 68-34 42-47 109-9 45-9 132v186q0 105-9 119-14 20-82 22z m830-787v36q0 8-5 13t-13 5h-821q-8 0-13-5t-5-13v-36q0-8 5-13t13-5h821q8 0 13 5t5 13z" horiz-adv-x="857.1" />
<glyph glyph-name="table" unicode="&#xf0ce;" d="M286 82v107q0 8-5 13t-13 5h-179q-7 0-12-5t-6-13v-107q0-8 6-13t12-5h179q8 0 13 5t5 13z m0 214v108q0 7-5 12t-13 5h-179q-7 0-12-5t-6-12v-108q0-7 6-12t12-5h179q8 0 13 5t5 12z m285-214v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m-285 429v107q0 8-5 13t-13 5h-179q-7 0-12-5t-6-13v-107q0-8 6-13t12-5h179q8 0 13 5t5 13z m285-215v108q0 7-5 12t-12 5h-179q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h179q7 0 12 5t5 12z m286-214v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m-286 429v107q0 8-5 13t-12 5h-179q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h179q7 0 12 5t5 13z m286-215v108q0 7-5 12t-13 5h-178q-8 0-13-5t-5-12v-108q0-7 5-12t13-5h178q8 0 13 5t5 12z m0 215v107q0 8-5 13t-13 5h-178q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h178q8 0 13 5t5 13z m72 178v-607q0-37-27-63t-63-26h-750q-36 0-63 26t-26 63v607q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" />
<glyph glyph-name="columns" unicode="&#xf0db;" d="M89-7h340v643h-358v-625q0-7 6-13t12-5z m768 18v625h-357v-643h339q8 0 13 5t5 13z m72 678v-678q0-37-27-63t-63-27h-750q-36 0-63 27t-26 63v678q0 37 26 63t63 27h750q37 0 63-27t27-63z" horiz-adv-x="928.6" />
<glyph glyph-name="quote-left" unicode="&#xf10d;" d="M429 314v-214q0-45-32-76t-76-31h-214q-44 0-76 31t-31 76v393q0 58 23 111t61 91 91 61 111 23h35q15 0 26-11t10-25v-72q0-14-10-25t-26-10h-35q-59 0-101-42t-42-101v-18q0-22 16-38t37-16h125q45 0 76-31t32-76z m500 0v-214q0-45-32-76t-76-31h-214q-44 0-76 31t-31 76v393q0 58 23 111t61 91 91 61 111 23h35q15 0 26-11t10-25v-72q0-14-10-25t-26-10h-35q-59 0-101-42t-42-101v-18q0-22 16-38t37-16h125q45 0 76-31t32-76z" horiz-adv-x="928.6" />
<glyph glyph-name="code" unicode="&#xf121;" d="M344 69l-28-28q-5-5-12-5t-13 5l-260 261q-6 5-6 12t6 13l260 260q5 6 13 6t12-6l28-28q6-5 6-13t-6-12l-219-220 219-219q6-6 6-13t-6-13z m330 596l-208-721q-2-7-9-11t-13-1l-34 9q-8 3-11 9t-2 14l209 720q2 8 8 11t13 2l35-10q7-2 11-9t1-13z m367-363l-260-261q-6-5-13-5t-13 5l-28 28q-5 6-5 13t5 13l219 219-219 220q-5 5-5 12t5 13l28 28q6 6 13 6t13-6l260-260q5-5 5-13t-5-12z" horiz-adv-x="1071.4" />
<glyph glyph-name="superscript" unicode="&#xf12b;" d="M501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2q0-2-1-4t-2-4-2-4q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m355 379v-115h-287l-1 15q-3 16-3 26 0 36 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-29 0-54-21-8-6-20-22l-59 52q15 20 35 37 47 36 105 36 61 0 99-33t38-89q0-31-13-57t-35-43-45-33-46-28-37-28-17-36h130v45h70z" horiz-adv-x="857.1" />
<glyph glyph-name="subscript" unicode="&#xf12c;" d="M501 86v-93h-139l-89 141-13 23q-4 5-6 12h-2q0-2-1-4t-2-4-2-4q-5-11-14-25l-86-139h-144v93h71l110 162-103 152h-76v94h154l77-127q1-2 13-24 4-5 6-11h2q1 5 6 11l14 24 78 127h143v-94h-69l-103-149 114-165h61z m356-121v-115h-287l-2 15q-2 25-2 26 0 35 15 65t36 48 47 37 47 30 36 30 15 36q0 21-17 35t-39 13q-28 0-54-21-8-6-20-22l-59 52q15 20 35 37 45 36 105 36 62 0 100-33t37-89q0-37-19-66t-47-48-55-35-49-35-23-41h130v45h70z" horiz-adv-x="857.1" />
<glyph glyph-name="header" unicode="&#xf1dc;" d="M939-79q-25 0-74 2t-75 2q-24 0-73-2t-74-2q-13 0-21 12t-7 25q0 18 9 26t22 9 29 4 25 9q18 11 18 78l0 218q0 12-1 17-7 3-28 3h-376q-22 0-29-3 0-5 0-17l-1-207q0-79 21-91 9-6 26-8t32-2 25-8 11-26q0-14-6-26t-21-13q-26 0-78 2t-77 2q-24 0-71-2t-71-2q-13 0-20 12t-7 25q0 17 9 25t20 10 26 4 24 9q18 13 18 80l-1 31v454q0 2 1 15t0 20-1 21-2 24-4 20-6 18-9 10q-8 5-25 7t-29 1-23 7-10 26q0 14 6 26t20 13q26 0 78-2t77-2q23 0 71 2t70 2q14 0 21-13t7-26q0-17-9-25t-22-8-27-2-24-7q-20-12-20-90l1-178q0-12 0-18 7-2 22-2h390q14 0 21 2 1 6 1 18l0 178q0 78-19 90-10 6-33 7t-37 7-14 28q0 14 7 26t21 13q24 0 74-2t73-2q24 0 72 2t72 2q14 0 21-13t7-26q0-17-10-25t-22-8-29-2-24-7q-20-13-20-90l1-526q0-66 19-78 9-6 25-8t30-2 23-9 10-25q0-14-6-26t-20-13z" horiz-adv-x="1000" />
<glyph glyph-name="window-maximize" unicode="&#xf2d0;" d="M143 64h714v429h-714v-429z m857 625v-678q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v678q0 37 26 63t63 27h822q37 0 63-27t26-63z" horiz-adv-x="1000" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API文档管理</title>
<script type="module" crossorigin src="assets/main.c59b3205.js"></script>
<link rel="modulepreload" href="assets/vendor.d3f949c4.js">
<link rel="stylesheet" href="assets/style.038e99c3.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB