1、数据库文档功能实现

pull/254/head
xjs 3 years ago
parent 46c2f0726e
commit 7f9e0e1159

@ -20,7 +20,7 @@
<spring-cloud.version>2020.0.4</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
<alibaba.nacos.version>2.0.3</alibaba.nacos.version>
<spring-boot-admin.version>2.6.0</spring-boot-admin.version>
<spring-boot-admin.version>2.6.3</spring-boot-admin.version>
<spring-boot.mybatis>2.2.0</spring-boot.mybatis>
<swagger.fox.version>3.0.0</swagger.fox.version>
<swagger.core.version>1.6.2</swagger.core.version>
@ -53,6 +53,7 @@
<spring-cloud-alicloud-oss.version>2.2.0.RELEASE</spring-cloud-alicloud-oss.version>
<elasticsearch.version>7.12.1</elasticsearch.version>
<redisson.version>3.12.0</redisson.version>
<screw.version>1.0.5</screw.version>
</properties>
@ -122,6 +123,20 @@
<version>${redisson.version}</version>
</dependency>
<!-- 实现数据库文档 -->
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>${screw.version}</version>
<exclusions>
<!-- 移除 Freemarker 依赖,采用 Velocity 作为模板引擎 -->
<exclusion>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.xjs</groupId>
<artifactId>xjs-business-common</artifactId>

@ -1,15 +1,10 @@
package com.ruoyi.common.core.utils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.text.Convert;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -18,15 +13,22 @@ import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.text.Convert;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
*
*
* @author ruoyi
*/
public class ServletUtils
@ -158,7 +160,7 @@ public class ServletUtils
/**
*
*
*
* @param response
* @param string
* @return null
@ -181,7 +183,7 @@ public class ServletUtils
/**
* Ajax
*
*
* @param request
*/
public static boolean isAjaxRequest(HttpServletRequest request)
@ -214,7 +216,7 @@ public class ServletUtils
/**
*
*
*
* @param str
* @return
*/
@ -232,7 +234,7 @@ public class ServletUtils
/**
*
*
*
* @param str
* @return
*/
@ -305,4 +307,20 @@ public class ServletUtils
DataBuffer dataBuffer = response.bufferFactory().wrap(JSONObject.toJSONString(result).getBytes());
return response.writeWith(Mono.just(dataBuffer));
}
/**
*
*
* @param response
* @param filename
* @param content
* @throws IOException
*/
public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
// 设置 header 和 contentType
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
// 输出附件
IoUtil.write(response.getOutputStream(), false, content);
}
}

@ -0,0 +1,35 @@
import request from '@/utils/request'
export function exportHtml(key) {
return request({
url: '/monitor/db-doc/export-html',
method: 'get',
responseType: 'blob',
params:key
})
}
export function exportWord(key) {
return request({
url: '/monitor/db-doc/export-word',
method: 'get',
responseType: 'blob',
params:key
})
}
export function exportMarkdown(key) {
return request({
url: '/monitor/db-doc/export-markdown',
method: 'get',
responseType: 'blob',
params:key
})
}
export function getDataSource() {
return request({
url: '/monitor/db-doc/getDataSource',
method: 'get',
})
}

@ -18,7 +18,7 @@ export default {
},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + "px;",
height: document.documentElement.clientHeight - 94.5 + "px",
loading: true,
url: this.src
};
@ -29,7 +29,7 @@ export default {
}, 300);
const that = this;
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + "px;";
that.height = document.documentElement.clientHeight - 94.5 + "px";
};
}
};

@ -1,38 +1,77 @@
import axios from 'axios'
import { Message } from 'element-ui'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from "@/utils/ruoyi";
const baseURL = process.env.VUE_APP_BASE_API
export default {
zip(url, name) {
var url = baseURL + url
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then(async (res) => {
const isLogin = await blobValidate(res.data);
if (isLogin) {
const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
}
})
},
saveAs(text, name, opts) {
saveAs(text, name, opts);
},
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
}
}
import axios from 'axios'
import { Message } from 'element-ui'
import { saveAs } from 'file-saver'
import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import { blobValidate } from "@/utils/ruoyi";
const baseURL = process.env.VUE_APP_BASE_API
export default {
zip(url, name) {
var url = baseURL + url
axios({
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
}).then(async (res) => {
const isLogin = await blobValidate(res.data);
if (isLogin) {
const blob = new Blob([res.data], { type: 'application/zip' })
this.saveAs(blob, name)
} else {
this.printErrMsg(res.data);
}
})
},
saveAs(text, name, opts) {
saveAs(text, name, opts);
},
async printErrMsg(data) {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
Message.error(errMsg);
},
// 下载 Excel 方法
excel(data, fileName) {
this.download0(data, fileName, 'application/vnd.ms-excel');
},
// 下载 Word 方法
word(data, fileName) {
this.download0(data, fileName, 'application/msword');
},
// 下载 Zip 方法
/*zip(data, fileName) {
this.download0(data, fileName, 'application/zip');
},*/
// 下载 Html 方法
html(data, fileName) {
this.download0(data, fileName, 'text/html');
},
// 下载 Markdown 方法
markdown(data, fileName) {
this.download0(data, fileName, 'text/markdown');
},
download0(data, fileName, mineType) {
// 创建 blob
let blob = new Blob([data], {type: mineType});
// 创建 href 超链接,点击进行下载
window.URL = window.URL || window.webkitURL;
let href = URL.createObjectURL(blob);
let downA = document.createElement("a");
downA.href = href;
downA.download = fileName;
downA.click();
// 销毁超连接
window.URL.revokeObjectURL(href);
},
}

@ -1,138 +1,140 @@
import auth from '@/plugins/auth'
import router, { constantRoutes, dynamicRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
const permission = {
state: {
routes: [],
addRoutes: [],
defaultRoutes: [],
topbarRouters: [],
sidebarRouters: []
},
mutations: {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_DEFAULT_ROUTES: (state, routes) => {
state.defaultRoutes = constantRoutes.concat(routes)
},
SET_TOPBAR_ROUTES: (state, routes) => {
// 顶部导航菜单默认添加统计报表栏指向首页
const index = [{
path: 'index',
meta: { title: '统计报表', icon: 'dashboard' }
}]
state.topbarRouters = routes.concat(index);
},
SET_SIDEBAR_ROUTERS: (state, routes) => {
state.sidebarRouters = routes
},
},
actions: {
// 生成路由
GenerateRoutes({ commit }) {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
})
}
}
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type)
} else {
delete route['children']
delete route['redirect']
}
return true
})
}
function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView' && !lastRouter) {
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path
}
children = children.concat(el)
})
return children
}
// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) {
res.push(route)
}
} else if (route.roles) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
}
})
return res
}
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') {
return (resolve) => require([`@/views/${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载
return () => import(`@/views/${view}`)
}
}
export default permission
import auth from '@/plugins/auth'
import router, {constantRoutes, dynamicRoutes} from '@/router'
import {getRouters} from '@/api/menu'
import Layout from '@/layout/index'
import ParentView from '@/components/ParentView'
import InnerLink from '@/layout/components/InnerLink'
const permission = {
state: {
routes: [],
addRoutes: [],
defaultRoutes: [],
topbarRouters: [],
sidebarRouters: []
},
mutations: {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
},
SET_DEFAULT_ROUTES: (state, routes) => {
state.defaultRoutes = constantRoutes.concat(routes)
},
SET_TOPBAR_ROUTES: (state, routes) => {
// 顶部导航菜单默认添加统计报表栏指向首页
const index = [
/*{
path: 'index',
meta: {title: '统计报表', icon: 'dashboard'}
}*/
]
state.topbarRouters = routes.concat(index);
},
SET_SIDEBAR_ROUTERS: (state, routes) => {
state.sidebarRouters = routes
},
},
actions: {
// 生成路由
GenerateRoutes({commit}) {
return new Promise(resolve => {
// 向后端请求路由数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
rewriteRoutes.push({path: '*', redirect: '/404', hidden: true})
router.addRoutes(asyncRoutes);
commit('SET_ROUTES', rewriteRoutes)
commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
commit('SET_DEFAULT_ROUTES', sidebarRoutes)
commit('SET_TOPBAR_ROUTES', sidebarRoutes)
resolve(rewriteRoutes)
})
})
}
}
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type)
} else {
delete route['children']
delete route['redirect']
}
return true
})
}
function filterChildren(childrenMap, lastRouter = false) {
var children = []
childrenMap.forEach((el, index) => {
if (el.children && el.children.length) {
if (el.component === 'ParentView' && !lastRouter) {
el.children.forEach(c => {
c.path = el.path + '/' + c.path
if (c.children && c.children.length) {
children = children.concat(filterChildren(c.children, c))
return
}
children.push(c)
})
return
}
}
if (lastRouter) {
el.path = lastRouter.path + '/' + el.path
}
children = children.concat(el)
})
return children
}
// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) {
res.push(route)
}
} else if (route.roles) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
}
})
return res
}
export const loadView = (view) => {
if (process.env.NODE_ENV === 'development') {
return (resolve) => require([`@/views/${view}`], resolve)
} else {
// 使用 import 实现生产环境的路由懒加载
return () => import(`@/views/${view}`)
}
}
export default permission

@ -0,0 +1,128 @@
<template>
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportHtml"> HTML</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportWord"> Word</el-button>
<el-button type="primary" icon="el-icon-plus" size="mini" @click="handleExportMarkdown"> Markdown</el-button>
<el-select @change="getHtml"
v-model="selectValue"
placeholder="请选择数据源"
size="mini"
style="margin-left: 30px;width: 150px;margin-right: 15px">
<el-option
v-for="item in dataSourceKey"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
<el-tooltip content="选择需要展示的数据库源" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
</el-col>
</el-row>
<!-- 展示文档 -->
<div v-loading="loading" :style="'height:'+ height">
<i-frame :src="src"/>
</div>
</div>
</template>
<script>
import {exportHtml, exportWord, exportMarkdown, getDataSource} from "@/api/business/monitor/db/dbDoc";
import iFrame from "@/components/iFrame/index";
export default {
name: "DBDoc",
components: {iFrame},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + "px",
loading: true,
src: '',
dataSourceKey: [
{
value: '',
label: ''
}
],
selectValue: ''
};
},
mounted: function () {
setTimeout(() => {
this.loading = false;
}, 100);
const that = this;
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + "px";
};
},
created() {
this.getDataSource()
},
methods: {
// key
getDataSource() {
getDataSource().then(res => {
this.dataSourceKey = res.data
//
this.selectValue=this.dataSourceKey[0].value
//
this.getHtml()
})
},
getHtml() {
// Html
let param={
dataSourceKey:this.selectValue
}
exportHtml(param).then(response => {
let blob = new Blob([response], {type: 'text/html'});
this.src = window.URL.createObjectURL(blob);
})
},
/** 处理导出 HTML */
handleExportHtml() {
let param={
dataSourceKey:this.selectValue
}
exportHtml(param).then(response => {
this.$download.html(response, '数据库文档.html');
})
},
/** 处理导出 Word */
handleExportWord() {
let param={
dataSourceKey:this.selectValue
}
exportWord(param).then(response => {
this.$download.word(response, '数据库文档.doc');
})
},
/** 处理导出 Markdown */
handleExportMarkdown() {
let param={
dataSourceKey:this.selectValue
}
exportMarkdown(param).then(response => {
this.$download.markdown(response, '数据库文档.md');
})
}
}
};
</script>

@ -24,6 +24,17 @@
<artifactId>oshi-core</artifactId>
</dependency>
<!-- 实现数据库文档 -->
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
</dependency>
<!--生成数据库文档模板-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<dependency>
<groupId>com.xjs</groupId>
<artifactId>xjs-business-common</artifactId>
@ -31,4 +42,4 @@
</dependencies>
</project>
</project>

@ -1,6 +1,5 @@
package com.xjs;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration;
import com.ruoyi.common.security.annotation.EnableRyFeignClients;
import com.ruoyi.common.security.config.ApplicationConfig;
import com.ruoyi.common.security.feign.FeignAutoConfiguration;
@ -16,11 +15,11 @@ import org.springframework.scheduling.annotation.EnableAsync;
/**
* @author xiejs
* @desc
* @desc
* @create 2022-01-02
*/
//排除两个关于数据源的自动配置类、及seata配置类
@SpringBootApplication(exclude = {DynamicDataSourceAutoConfiguration.class,
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
SeataAutoConfiguration.class})
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@ -28,9 +27,9 @@ import org.springframework.scheduling.annotation.EnableAsync;
// 开启线程异步执行
@EnableAsync
// 自动加载类
@Import({ ApplicationConfig.class, FeignAutoConfiguration.class })
@Import({ApplicationConfig.class, FeignAutoConfiguration.class})
//自定义bean扫描添加xjs路径下的bean
@ComponentScan(basePackages = {"com.ruoyi","com.xjs"})
@ComponentScan(basePackages = {"com.ruoyi", "com.xjs"})
@EnableCustomSwagger2
@EnableRyFeignClients
public class XjsMonitorApp {

@ -0,0 +1,182 @@
package com.xjs.dbmonitor;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.smallbun.screw.core.Configuration;
import cn.smallbun.screw.core.engine.EngineConfig;
import cn.smallbun.screw.core.engine.EngineFileType;
import cn.smallbun.screw.core.engine.EngineTemplateType;
import cn.smallbun.screw.core.execute.DocumentationExecute;
import cn.smallbun.screw.core.process.ProcessConfig;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @author xiejs
* @since 2022-04-16
*/
@Api(tags = "业务模块 - 数据库文档")
@RestController
@RequestMapping("/db-doc")
public class DbDocController {
@Resource
private DynamicDataSourceProperties dynamicDataSourceProperties;
private static final String FILE_OUTPUT_DIR = System.getProperty("java.io.tmpdir") + File.separator
+ "db-doc";
private static final String DOC_FILE_NAME = "数据库文档";
private static final String DOC_VERSION = "1.0.0";
private static final String DOC_DESCRIPTION = "谢哥数据库文档";
@GetMapping("getDataSource")
@ApiOperation("获取所有数据源")
public AjaxResult getDataSource() {
Map<String, DataSourceProperty> datasource = dynamicDataSourceProperties.getDatasource();
ArrayList<Map<String,String>> list = new ArrayList<>();
for (Map.Entry<String, DataSourceProperty> propertyEntry : datasource.entrySet()) {
Map<String,String> map = new HashMap();
map.put("value", propertyEntry.getKey());
map.put("label", propertyEntry.getKey());
list.add(map);
}
return AjaxResult.success(list);
}
@GetMapping("/export-html")
@ApiOperation("导出 html 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportHtml(@RequestParam(defaultValue = "true") Boolean deleteFile,
@RequestParam(defaultValue = "xjs-business") String dataSourceKey,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.HTML, deleteFile, dataSourceKey,response);
}
@GetMapping("/export-word")
@ApiOperation("导出 word 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportWord(@RequestParam(defaultValue = "true") Boolean deleteFile,
@RequestParam(defaultValue = "xjs-business") String dataSourceKey,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.WORD, deleteFile,dataSourceKey, response);
}
@GetMapping("/export-markdown")
@ApiOperation("导出 markdown 格式的数据文档")
@ApiImplicitParam(name = "deleteFile", value = "是否删除在服务器本地生成的数据库文档", example = "true",
dataTypeClass = Boolean.class)
public void exportMarkdown(@RequestParam(defaultValue = "true") Boolean deleteFile,
@RequestParam(defaultValue = "xjs-business") String dataSourceKey,
HttpServletResponse response) throws IOException {
doExportFile(EngineFileType.MD, deleteFile,dataSourceKey, response);
}
private void doExportFile(EngineFileType fileOutputType, Boolean deleteFile, String dataSourceKey,
HttpServletResponse response) throws IOException {
String docFileName = DOC_FILE_NAME + "_" + IdUtil.fastSimpleUUID();
String filePath = doExportFile(fileOutputType, docFileName,dataSourceKey);
String downloadFileName = DOC_FILE_NAME + fileOutputType.getFileSuffix(); //下载后的文件名
try {
// 读取,返回
ServletUtils.writeAttachment(response, downloadFileName, FileUtil.readBytes(filePath));
} finally {
handleDeleteFile(deleteFile, filePath);
}
}
/**
*
*
* @param fileOutputType
* @param fileName , ".docx"
* @return
*/
private String doExportFile(EngineFileType fileOutputType, String fileName,String dataSourceKey) {
try (HikariDataSource dataSource = buildDataSource(dataSourceKey)) {
// 创建 screw 的配置
Configuration config = Configuration.builder()
.version(DOC_VERSION) // 版本
.description(DOC_DESCRIPTION) // 描述
.dataSource(dataSource) // 数据源
.engineConfig(buildEngineConfig(fileOutputType, fileName)) // 引擎配置
.produceConfig(buildProcessConfig()) // 处理配置
.build();
// 执行 screw生成数据库文档
new DocumentationExecute(config).execute();
return FILE_OUTPUT_DIR + File.separator + fileName + fileOutputType.getFileSuffix();
}
}
private void handleDeleteFile(Boolean deleteFile, String filePath) {
if (!deleteFile) {
return;
}
FileUtil.del(filePath);
}
/**
*
* @param dataSourceKey key
*/
private HikariDataSource buildDataSource(String dataSourceKey) {
// 获得 DataSource 数据源,目前只支持首个
DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(dataSourceKey);
// 创建 HikariConfig 配置类
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(dataSourceProperty.getUrl());
hikariConfig.setUsername(dataSourceProperty.getUsername());
hikariConfig.setPassword(dataSourceProperty.getPassword());
hikariConfig.addDataSourceProperty("useInformationSchema", "true"); // 设置可以获取 tables remarks 信息
// 创建数据源'
return new HikariDataSource(hikariConfig);
}
/**
* screw
*/
private static EngineConfig buildEngineConfig(EngineFileType fileOutputType, String docFileName) {
return EngineConfig.builder()
.fileOutputDir(FILE_OUTPUT_DIR) // 生成文件路径
.openOutputDir(false) // 打开目录
.fileType(fileOutputType) // 文件类型
.produceType(EngineTemplateType.velocity) // 文件类型
.fileName(docFileName) // 自定义文件名称
.build();
}
/**
* screw
*
*/
private static ProcessConfig buildProcessConfig() {
return ProcessConfig.builder()
.ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_")) // 忽略表前缀
.build();
}
}
Loading…
Cancel
Save