From 7f9e0e11590fc12e64dacdbd0bd2f833bceaacf2 Mon Sep 17 00:00:00 2001 From: xjs <1294405880@qq.com> Date: Sat, 16 Apr 2022 14:28:44 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E6=95=B0=E6=8D=AE=E5=BA=93=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 17 +- .../ruoyi/common/core/utils/ServletUtils.java | 56 ++-- ruoyi-ui/src/api/business/monitor/db/dbDoc.js | 35 +++ ruoyi-ui/src/components/iFrame/index.vue | 4 +- ruoyi-ui/src/plugins/download.js | 115 +++++--- ruoyi-ui/src/store/modules/permission.js | 278 +++++++++--------- .../src/views/business/monitor/db/index.vue | 128 ++++++++ xjs-business/xjs-business-monitor/pom.xml | 13 +- .../src/main/java/com/xjs/XjsMonitorApp.java | 9 +- .../com/xjs/dbmonitor/DbDocController.java | 182 ++++++++++++ 10 files changed, 633 insertions(+), 204 deletions(-) create mode 100644 ruoyi-ui/src/api/business/monitor/db/dbDoc.js create mode 100644 ruoyi-ui/src/views/business/monitor/db/index.vue create mode 100644 xjs-business/xjs-business-monitor/src/main/java/com/xjs/dbmonitor/DbDocController.java diff --git a/pom.xml b/pom.xml index 74eb7086..c75807af 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 2020.0.4 2021.1 2.0.3 - 2.6.0 + 2.6.3 2.2.0 3.0.0 1.6.2 @@ -53,6 +53,7 @@ 2.2.0.RELEASE 7.12.1 3.12.0 + 1.0.5 @@ -122,6 +123,20 @@ ${redisson.version} + + + cn.smallbun.screw + screw-core + ${screw.version} + + + + org.freemarker + freemarker + + + + com.xjs xjs-business-common diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java index c7bec5da..e82da4dc 100644 --- a/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java +++ b/ruoyi-common/ruoyi-common-core/src/main/java/com/ruoyi/common/core/utils/ServletUtils.java @@ -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); + } } diff --git a/ruoyi-ui/src/api/business/monitor/db/dbDoc.js b/ruoyi-ui/src/api/business/monitor/db/dbDoc.js new file mode 100644 index 00000000..dfa0251c --- /dev/null +++ b/ruoyi-ui/src/api/business/monitor/db/dbDoc.js @@ -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', + }) +} diff --git a/ruoyi-ui/src/components/iFrame/index.vue b/ruoyi-ui/src/components/iFrame/index.vue index 426857fb..f0485520 100644 --- a/ruoyi-ui/src/components/iFrame/index.vue +++ b/ruoyi-ui/src/components/iFrame/index.vue @@ -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"; }; } }; diff --git a/ruoyi-ui/src/plugins/download.js b/ruoyi-ui/src/plugins/download.js index cfb7c246..31a28037 100644 --- a/ruoyi-ui/src/plugins/download.js +++ b/ruoyi-ui/src/plugins/download.js @@ -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); + }, +} + diff --git a/ruoyi-ui/src/store/modules/permission.js b/ruoyi-ui/src/store/modules/permission.js index 8c3c3390..966a81d2 100644 --- a/ruoyi-ui/src/store/modules/permission.js +++ b/ruoyi-ui/src/store/modules/permission.js @@ -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 diff --git a/ruoyi-ui/src/views/business/monitor/db/index.vue b/ruoyi-ui/src/views/business/monitor/db/index.vue new file mode 100644 index 00000000..03242df8 --- /dev/null +++ b/ruoyi-ui/src/views/business/monitor/db/index.vue @@ -0,0 +1,128 @@ + + diff --git a/xjs-business/xjs-business-monitor/pom.xml b/xjs-business/xjs-business-monitor/pom.xml index 146bfabf..31e8de64 100644 --- a/xjs-business/xjs-business-monitor/pom.xml +++ b/xjs-business/xjs-business-monitor/pom.xml @@ -24,6 +24,17 @@ oshi-core + + + cn.smallbun.screw + screw-core + + + + org.apache.velocity + velocity-engine-core + + com.xjs xjs-business-common @@ -31,4 +42,4 @@ - \ No newline at end of file + diff --git a/xjs-business/xjs-business-monitor/src/main/java/com/xjs/XjsMonitorApp.java b/xjs-business/xjs-business-monitor/src/main/java/com/xjs/XjsMonitorApp.java index 83e24173..06e12e3b 100644 --- a/xjs-business/xjs-business-monitor/src/main/java/com/xjs/XjsMonitorApp.java +++ b/xjs-business/xjs-business-monitor/src/main/java/com/xjs/XjsMonitorApp.java @@ -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 { diff --git a/xjs-business/xjs-business-monitor/src/main/java/com/xjs/dbmonitor/DbDocController.java b/xjs-business/xjs-business-monitor/src/main/java/com/xjs/dbmonitor/DbDocController.java new file mode 100644 index 00000000..ef1d0226 --- /dev/null +++ b/xjs-business/xjs-business-monitor/src/main/java/com/xjs/dbmonitor/DbDocController.java @@ -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 datasource = dynamicDataSourceProperties.getDatasource(); + ArrayList> list = new ArrayList<>(); + for (Map.Entry propertyEntry : datasource.entrySet()) { + Map 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(); + } +}