├── dbtree-vue ├── .eslintignore ├── babel.config.js ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── components │ │ ├── Hamburger.spec.js │ │ ├── SvgIcon.spec.js │ │ └── Breadcrumb.spec.js │ │ └── utils │ │ ├── validate.spec.js │ │ ├── parseTime.spec.js │ │ └── formatTime.spec.js ├── public │ ├── favicon.ico │ └── index.html ├── .env.production ├── .travis.yml ├── src │ ├── assets │ │ └── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ ├── views │ │ ├── nested │ │ │ ├── menu2 │ │ │ │ └── index.vue │ │ │ └── menu1 │ │ │ │ ├── menu1-3 │ │ │ │ └── index.vue │ │ │ │ ├── index.vue │ │ │ │ ├── menu1-2 │ │ │ │ ├── menu1-2-1 │ │ │ │ │ └── index.vue │ │ │ │ ├── menu1-2-2 │ │ │ │ │ └── index.vue │ │ │ │ └── index.vue │ │ │ │ └── menu1-1 │ │ │ │ └── index.vue │ │ ├── icons │ │ │ ├── svg-icons.js │ │ │ ├── element-icons.js │ │ │ └── index.vue │ │ ├── dashboard │ │ │ └── index.vue │ │ ├── tree │ │ │ └── index.vue │ │ ├── dbtree │ │ │ └── server │ │ │ │ └── index.vue │ │ ├── table │ │ │ └── index.vue │ │ ├── form │ │ │ └── index.vue │ │ └── 404.vue │ ├── layout │ │ ├── components │ │ │ ├── index.js │ │ │ ├── Sidebar │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── index.vue │ │ │ │ ├── Logo.vue │ │ │ │ └── SidebarItem.vue │ │ │ ├── AppMain.vue │ │ │ └── Navbar.vue │ │ ├── mixin │ │ │ └── ResizeHandler.js │ │ └── index.vue │ ├── App.vue │ ├── api │ │ ├── table.js │ │ ├── user.js │ │ ├── code.js │ │ └── dbtree.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── settings.js │ │ │ ├── app.js │ │ │ └── user.js │ ├── icons │ │ ├── svg │ │ │ ├── link.svg │ │ │ ├── user.svg │ │ │ ├── example.svg │ │ │ ├── table.svg │ │ │ ├── password.svg │ │ │ ├── nested.svg │ │ │ ├── folder.svg │ │ │ ├── eye.svg │ │ │ ├── eye-open.svg │ │ │ ├── tree.svg │ │ │ ├── dashboard.svg │ │ │ └── form.svg │ │ ├── index.js │ │ └── svgo.yml │ ├── utils │ │ ├── get-page-title.js │ │ ├── auth.js │ │ ├── validate.js │ │ ├── clipboard.js │ │ ├── request.js │ │ └── index.js │ ├── settings.js │ ├── styles │ │ ├── mixin.scss │ │ ├── variables.scss │ │ ├── element-ui.scss │ │ ├── element-variables.scss │ │ ├── transition.scss │ │ ├── index.scss │ │ └── sidebar.scss │ ├── main.js │ ├── components │ │ ├── Hamburger │ │ │ └── index.vue │ │ ├── SvgIcon │ │ │ └── index.vue │ │ └── Breadcrumb │ │ │ └── index.vue │ └── permission.js ├── .env.staging ├── postcss.config.js ├── .gitignore ├── .editorconfig ├── .env.development ├── mock │ ├── table.js │ ├── user.js │ ├── index.js │ └── mock-server.js ├── jest.config.js ├── build │ └── index.js ├── package.json ├── pom.xml ├── README.md ├── README-zh.md ├── vue.config.js └── .eslintrc.js ├── .gitattributes ├── dbtree_demo.gif ├── demoimg ├── archive.png ├── mybatis.png ├── table.png └── addfolder.png ├── dbtree-backend ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.properties │ │ │ ├── dbconfig │ │ │ │ ├── db-config.json │ │ │ │ └── db-server.json │ │ │ ├── application-dev.properties │ │ │ ├── application-local.properties │ │ │ └── mapping │ │ │ │ ├── SyncTaskLockMapper.xml │ │ │ │ └── TreeNodeMapper.xml │ │ ├── config │ │ │ └── dbtree.conf │ │ ├── java │ │ │ └── com │ │ │ │ └── autohome │ │ │ │ └── dbtree │ │ │ │ ├── dao │ │ │ │ ├── Duplicate.java │ │ │ │ └── mysql │ │ │ │ │ └── dbtree │ │ │ │ │ ├── mapper │ │ │ │ │ ├── SyncTaskLockMapper.java │ │ │ │ │ └── TreeNodeMapper.java │ │ │ │ │ └── domain │ │ │ │ │ ├── SyncTaskLock.java │ │ │ │ │ └── TreeNode.java │ │ │ │ ├── util │ │ │ │ ├── JacksonUtil.java │ │ │ │ ├── CommentGeneratorExt.java │ │ │ │ └── JdbcUtil.java │ │ │ │ ├── service │ │ │ │ ├── ITableExportService.java │ │ │ │ ├── IMybatisCodeGenerateService.java │ │ │ │ ├── IDbService.java │ │ │ │ ├── impl │ │ │ │ │ ├── LockService.java │ │ │ │ │ ├── TableExportService.java │ │ │ │ │ └── ConnManager.java │ │ │ │ └── ITreeService.java │ │ │ │ ├── DbtreeApplication.java │ │ │ │ ├── contract │ │ │ │ ├── DelegateRule.java │ │ │ │ ├── TableInfo.java │ │ │ │ ├── TableDetail.java │ │ │ │ ├── DbInfo.java │ │ │ │ ├── FolderClassifyInfo.java │ │ │ │ ├── DbServer.java │ │ │ │ ├── Protocol.java │ │ │ │ ├── TreeNodeVo.java │ │ │ │ └── ColumnInfo.java │ │ │ │ ├── config │ │ │ │ ├── MybatisCodeGeneratorConfig.java │ │ │ │ ├── Swagger2Config.java │ │ │ │ ├── BeanConfig.java │ │ │ │ ├── DbTreeConfig.java │ │ │ │ ├── ScheduleTask.java │ │ │ │ └── DbTreeMybatisConfig.java │ │ │ │ └── controller │ │ │ │ └── CodeController.java │ │ └── scripts │ │ │ ├── shutdown.sh │ │ │ └── startup.sh │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── autohome │ │ │ └── dbtree │ │ │ ├── DbtreeApplicationTests.java │ │ │ └── service │ │ │ └── impl │ │ │ └── TableExportServiceTest.java │ └── assembly │ │ └── assembly-descriptor.xml └── doc │ └── schema │ ├── database.sql │ ├── sync_task_lock.sql │ └── tree_node.sql ├── connector ├── sqljdbc42.jar └── mysql-connector-java-5.1.36.jar ├── .gitignore ├── pom.xml ├── LICENSE ├── README-EN.md └── README.md /dbtree-vue/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /dbtree_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/dbtree_demo.gif -------------------------------------------------------------------------------- /demoimg/archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/demoimg/archive.png -------------------------------------------------------------------------------- /demoimg/mybatis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/demoimg/mybatis.png -------------------------------------------------------------------------------- /demoimg/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/demoimg/table.png -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.output.ansi.enabled=always 2 | -------------------------------------------------------------------------------- /demoimg/addfolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/demoimg/addfolder.png -------------------------------------------------------------------------------- /connector/sqljdbc42.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/connector/sqljdbc42.jar -------------------------------------------------------------------------------- /dbtree-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/config/dbtree.conf: -------------------------------------------------------------------------------- 1 | MODE=service 2 | PID_FOLDER=. 3 | LOG_FOLDER=/data/dbtree/8080/logs -------------------------------------------------------------------------------- /dbtree-vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/dbtree-vue/public/favicon.ico -------------------------------------------------------------------------------- /dbtree-vue/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '/' 6 | 7 | -------------------------------------------------------------------------------- /dbtree-vue/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /connector/mysql-connector-java-5.1.36.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/connector/mysql-connector-java-5.1.36.jar -------------------------------------------------------------------------------- /dbtree-backend/doc/schema/database.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE dbtree 2 | DEFAULT CHARACTER SET utf8mb4 3 | DEFAULT COLLATE utf8_general_ci; -------------------------------------------------------------------------------- /dbtree-vue/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/dbtree-vue/src/assets/404_images/404.png -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/dao/Duplicate.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.dao; 2 | 3 | public class Duplicate { 4 | } 5 | -------------------------------------------------------------------------------- /dbtree-vue/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AutohomeCorp/dbtree/HEAD/dbtree-vue/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /dbtree-vue/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = '/stage-api' 8 | 9 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/util/JacksonUtil.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.util; 2 | 3 | public class JacksonUtil { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /dbtree-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /dbtree-vue/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: '/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dbtree-vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/ITableExportService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service; 2 | 3 | import java.util.List; 4 | 5 | public interface ITableExportService { 6 | 7 | String exportMarkdown(String dbName, List tableList); 8 | } 9 | -------------------------------------------------------------------------------- /dbtree-vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | tests/**/coverage/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /dbtree-vue/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name 7 | } 8 | export default getters 9 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title || 'Vue Admin Template' 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /dbtree-vue/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/icons/svg-icons.js: -------------------------------------------------------------------------------- 1 | const req = require.context('../../icons/svg', false, /\.svg$/) 2 | const requireAll = requireContext => requireContext.keys() 3 | 4 | const re = /\.\/(.*)\.svg/ 5 | 6 | const svgIcons = requireAll(req).map(i => { 7 | return i.match(re)[1] 8 | }) 9 | 10 | export default svgIcons 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | tests/**/coverage/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | *.iml 18 | /dbtree-backend/src/main/resources/public/ 19 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/IMybatisCodeGenerateService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service; 2 | 3 | import java.util.List; 4 | 5 | public interface IMybatisCodeGenerateService { 6 | 7 | String execute(String domainPackage, String mapperPackage, String dbName, List tableList, Boolean useActualColumnNames); 8 | } 9 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'vue_admin_template_token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /dbtree-vue/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | title: 'Vue Admin Template', 4 | 5 | /** 6 | * @type {boolean} true | false 7 | * @description Whether fix the header 8 | */ 9 | fixedHeader: false, 10 | 11 | /** 12 | * @type {boolean} true | false 13 | * @description Whether show the logo in sidebar 14 | */ 15 | sidebarLogo: false 16 | } 17 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/dbconfig/db-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "db_1": { 3 | "db_name": "db_1", 4 | "db_server": "mysql-127.0.0.1", 5 | "split_table_rules": [ 6 | { 7 | "delegate_table": "rule", 8 | "table_pattern": "rule_%" 9 | } 10 | ] 11 | }, 12 | "db_2": { 13 | "db_name": "db_2", 14 | "db_server": "sqlserver-127.0.0.2" 15 | } 16 | } -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/dbconfig/db-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "mysql-127.0.0.1": { 3 | "db_type": "mysql", 4 | "host": "127.0.0.1", 5 | "port": 3306, 6 | "user": "root", 7 | "password": "123456" 8 | }, 9 | "sqlserver-127.0.0.2": { 10 | "db_type": "sqlserver", 11 | "host": "127.0.0.2", 12 | "port": 1433, 13 | "user": "root", 14 | "password":"123456" 15 | } 16 | } -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/dao/mysql/dbtree/mapper/SyncTaskLockMapper.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.dao.mysql.dbtree.mapper; 2 | 3 | import com.autohome.dbtree.dao.mysql.dbtree.domain.SyncTaskLock; 4 | 5 | public interface SyncTaskLockMapper { 6 | int insert(SyncTaskLock record); 7 | 8 | SyncTaskLock selectByPrimaryKey(Integer id); 9 | 10 | int remove(Integer lockId); 11 | } -------------------------------------------------------------------------------- /dbtree-vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import app from './modules/app' 5 | import settings from './modules/settings' 6 | import user from './modules/user' 7 | 8 | Vue.use(Vuex) 9 | 10 | const store = new Vuex.Store({ 11 | modules: { 12 | app, 13 | settings, 14 | user 15 | }, 16 | getters 17 | }) 18 | 19 | export default store 20 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/scripts/shutdown.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SERVICE_NAME=dbtree 3 | 4 | if [[ -z "$JAVA_HOME" && -d /usr/java/latest/ ]]; then 5 | export JAVA_HOME=/usr/java/latest/ 6 | fi 7 | 8 | cd `dirname $0`/.. 9 | 10 | if [[ ! -f $SERVICE_NAME".jar" && -d current ]]; then 11 | cd current 12 | fi 13 | 14 | if [[ -f $SERVICE_NAME".jar" ]]; then 15 | chmod a+x $SERVICE_NAME".jar" 16 | ./$SERVICE_NAME".jar" stop 17 | fi 18 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | /** 14 | * @param {string} str 15 | * @returns {Boolean} 16 | */ 17 | export function validUsername(str) { 18 | const valid_map = ['admin', 'editor'] 19 | return valid_map.indexOf(str.trim()) >= 0 20 | } 21 | -------------------------------------------------------------------------------- /dbtree-backend/doc/schema/sync_task_lock.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS sync_task_lock 2 | ( 3 | id INT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键', 4 | lock_id INT NOT NULL UNIQUE COMMENT '锁ID', 5 | created_stime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 6 | modified_stime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间' 7 | ) ENGINE = InnoDB 8 | DEFAULT CHARSET = utf8mb4 9 | COMMENT = '表数据同步任务锁'; -------------------------------------------------------------------------------- /dbtree-vue/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(data) { 4 | return request({ 5 | url: '/user/login', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function getInfo(token) { 12 | return request({ 13 | url: '/user/info', 14 | method: 'get', 15 | params: { token } 16 | }) 17 | } 18 | 19 | export function logout() { 20 | return request({ 21 | url: '/user/logout', 22 | method: 'post' 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-backend/src/test/java/com/autohome/dbtree/DbtreeApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree; 2 | 3 | import org.junit.Ignore; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @RunWith(SpringRunner.class) 10 | @SpringBootTest 11 | @Ignore 12 | public class DbtreeApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/DbtreeApplication.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @SpringBootApplication 8 | @EnableScheduling 9 | public class DbtreeApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DbtreeApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | name: {{ name }} 4 | 5 | 6 | 7 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = '/dev-api' 6 | 7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, 8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled. 9 | # It only does one thing by converting all import() to require(). 10 | # This configuration can significantly increase the speed of hot updates, 11 | # when you have a large number of pages. 12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js 13 | 14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 15 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /dbtree-vue/mock/table.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | const data = Mock.mock({ 4 | 'items|30': [{ 5 | id: '@id', 6 | title: '@sentence(10, 20)', 7 | 'status|1': ['published', 'draft', 'deleted'], 8 | author: 'name', 9 | display_time: '@datetime', 10 | pageviews: '@integer(300, 5000)' 11 | }] 12 | }) 13 | 14 | export default [ 15 | { 16 | url: '/table/list', 17 | type: 'get', 18 | response: config => { 19 | const items = data.items 20 | return { 21 | code: 20000, 22 | data: { 23 | total: items.length, 24 | items: items 25 | } 26 | } 27 | } 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/DelegateRule.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | public class DelegateRule { 4 | 5 | private String delegate_table; 6 | 7 | private String table_pattern; 8 | 9 | public String getDelegate_table() { 10 | return delegate_table; 11 | } 12 | 13 | public void setDelegate_table(String delegate_table) { 14 | this.delegate_table = delegate_table; 15 | } 16 | 17 | public String getTable_pattern() { 18 | return table_pattern; 19 | } 20 | 21 | public void setTable_pattern(String table_pattern) { 22 | this.table_pattern = table_pattern; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /dbtree-vue/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings 4 | 5 | const state = { 6 | showSettings: showSettings, 7 | fixedHeader: fixedHeader, 8 | sidebarLogo: sidebarLogo 9 | } 10 | 11 | const mutations = { 12 | CHANGE_SETTING: (state, { key, value }) => { 13 | if (state.hasOwnProperty(key)) { 14 | state[key] = value 15 | } 16 | } 17 | } 18 | 19 | const actions = { 20 | changeSetting({ commit }, data) { 21 | commit('CHANGE_SETTING', data) 22 | } 23 | } 24 | 25 | export default { 26 | namespaced: true, 27 | state, 28 | mutations, 29 | actions 30 | } 31 | 32 | -------------------------------------------------------------------------------- /dbtree-vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 12 | We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue. 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/IDbService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service; 2 | 3 | import com.autohome.dbtree.contract.ColumnInfo; 4 | import com.autohome.dbtree.contract.TableInfo; 5 | 6 | import java.util.List; 7 | 8 | public interface IDbService { 9 | 10 | List findTablesByDb(String dbName); 11 | 12 | List findTablesByName(String dbName, List tableList); 13 | 14 | List findColumnsByTable(String dbName, String tableName); 15 | 16 | Boolean updateColumnComment(String dbName, String tableName, String column, String comment); 17 | 18 | Boolean updateTableComment(String dbName, String tableName, String newComment); 19 | } 20 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:#409EFF; 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#304156; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 210px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/components/Hamburger.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import Hamburger from '@/components/Hamburger/index.vue' 3 | describe('Hamburger.vue', () => { 4 | it('toggle click', () => { 5 | const wrapper = shallowMount(Hamburger) 6 | const mockFn = jest.fn() 7 | wrapper.vm.$on('toggleClick', mockFn) 8 | wrapper.find('.hamburger').trigger('click') 9 | expect(mockFn).toBeCalled() 10 | }) 11 | it('prop isActive', () => { 12 | const wrapper = shallowMount(Hamburger) 13 | wrapper.setProps({ isActive: true }) 14 | expect(wrapper.contains('.is-active')).toBe(true) 15 | wrapper.setProps({ isActive: false }) 16 | expect(wrapper.contains('.is-active')).toBe(false) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/components/SvgIcon.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import SvgIcon from '@/components/SvgIcon/index.vue' 3 | describe('SvgIcon.vue', () => { 4 | it('iconClass', () => { 5 | const wrapper = shallowMount(SvgIcon, { 6 | propsData: { 7 | iconClass: 'test' 8 | } 9 | }) 10 | expect(wrapper.find('use').attributes().href).toBe('#icon-test') 11 | }) 12 | it('className', () => { 13 | const wrapper = shallowMount(SvgIcon, { 14 | propsData: { 15 | iconClass: 'test' 16 | } 17 | }) 18 | expect(wrapper.classes().length).toBe(1) 19 | wrapper.setProps({ className: 'test' }) 20 | expect(wrapper.classes().includes('test')).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /dbtree-backend/doc/schema/tree_node.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS tree_node 2 | ( 3 | id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '自增主键', 4 | db_name VARCHAR(100) NOT NULL COMMENT '数据库名', 5 | folder INTEGER NOT NULL DEFAULT 0 COMMENT '0:表节点; 1: 目录节点', 6 | node_name VARCHAR(200) NOT NULL COMMENT '表名', 7 | parent_id BIGINT NOT NULL DEFAULT 0 COMMENT '父亲节点id', 8 | created_stime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 9 | modified_stime DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', 10 | is_del INTEGER NOT NULL DEFAULT 0 COMMENT '逻辑删除标识' 11 | ) ENGINE = InnoDB 12 | DEFAULT CHARSET = utf8mb4 13 | COMMENT = '树形数据表'; -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Clipboard from 'clipboard' 3 | 4 | function clipboardSuccess() { 5 | Vue.prototype.$message({ 6 | message: 'Copy successfully', 7 | type: 'success', 8 | duration: 1500 9 | }) 10 | } 11 | 12 | function clipboardError() { 13 | Vue.prototype.$message({ 14 | message: 'Copy failed', 15 | type: 'error' 16 | }) 17 | } 18 | 19 | export default function handleClipboard(text, event) { 20 | const clipboard = new Clipboard(event.target, { 21 | text: () => text 22 | }) 23 | clipboard.on('success', () => { 24 | clipboardSuccess() 25 | clipboard.destroy() 26 | }) 27 | clipboard.on('error', () => { 28 | clipboardError() 29 | clipboard.destroy() 30 | }) 31 | clipboard.onClick(event) 32 | } 33 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/utils/validate.spec.js: -------------------------------------------------------------------------------- 1 | import { validUsername, isExternal } from '@/utils/validate.js' 2 | 3 | describe('Utils:validate', () => { 4 | it('validUsername', () => { 5 | expect(validUsername('admin')).toBe(true) 6 | expect(validUsername('editor')).toBe(true) 7 | expect(validUsername('xxxx')).toBe(false) 8 | }) 9 | it('isExternal', () => { 10 | expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) 11 | expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) 12 | expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false) 13 | expect(isExternal('/dashboard')).toBe(false) 14 | expect(isExternal('./dashboard')).toBe(false) 15 | expect(isExternal('dashboard')).toBe(false) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/TableInfo.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | @ApiModel 7 | public class TableInfo { 8 | 9 | @ApiModelProperty(value = "表名") 10 | private String tableName; 11 | 12 | @ApiModelProperty(value = "表说明") 13 | private String description; 14 | 15 | public String getTableName() { 16 | return tableName; 17 | } 18 | 19 | public void setTableName(String tableName) { 20 | this.tableName = tableName; 21 | } 22 | 23 | public String getDescription() { 24 | return description; 25 | } 26 | 27 | public void setDescription(String description) { 28 | this.description = description; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | 19 | // to fixed https://github.com/ElemeFE/element/issues/2461 20 | .el-dialog { 21 | transform: none; 22 | left: 0; 23 | position: relative; 24 | margin: 0 auto; 25 | } 26 | 27 | // refine element ui upload 28 | .upload-container { 29 | .el-upload { 30 | width: 100%; 31 | 32 | .el-upload-dragger { 33 | width: 100%; 34 | height: 200px; 35 | } 36 | } 37 | } 38 | 39 | // dropdown 40 | .el-dropdown-menu { 41 | a { 42 | display: block 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/impl/LockService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service.impl; 2 | 3 | import com.autohome.dbtree.dao.mysql.dbtree.domain.SyncTaskLock; 4 | import com.autohome.dbtree.dao.mysql.dbtree.mapper.SyncTaskLockMapper; 5 | 6 | import javax.annotation.Resource; 7 | 8 | public class LockService { 9 | 10 | @Resource 11 | private SyncTaskLockMapper syncTaskLockMapper; 12 | 13 | public Boolean tryLock() { 14 | SyncTaskLock syncTaskLock = new SyncTaskLock(); 15 | syncTaskLock.setLock_id(1); 16 | try { 17 | syncTaskLockMapper.insert(syncTaskLock); 18 | return true; 19 | } catch (Exception ex) { 20 | return false; 21 | } 22 | } 23 | 24 | public Boolean unLock() { 25 | return syncTaskLockMapper.remove(1) > 0; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dbtree-vue/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/TableDetail.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | import java.util.List; 7 | 8 | @ApiModel 9 | public class TableDetail { 10 | 11 | @ApiModelProperty(value = "表信息") 12 | private TableInfo tableInfo; 13 | 14 | @ApiModelProperty(value = "表字段信息") 15 | private List columnInfoList; 16 | 17 | public TableInfo getTableInfo() { 18 | return tableInfo; 19 | } 20 | 21 | public void setTableInfo(TableInfo tableInfo) { 22 | this.tableInfo = tableInfo; 23 | } 24 | 25 | public List getColumnInfoList() { 26 | return columnInfoList; 27 | } 28 | 29 | public void setColumnInfoList(List columnInfoList) { 30 | this.columnInfoList = columnInfoList; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/dao/mysql/dbtree/mapper/TreeNodeMapper.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.dao.mysql.dbtree.mapper; 2 | 3 | import com.autohome.dbtree.dao.mysql.dbtree.domain.TreeNode; 4 | import org.apache.ibatis.annotations.Param; 5 | 6 | import java.util.List; 7 | 8 | public interface TreeNodeMapper { 9 | 10 | TreeNode selectByPrimaryKey(Long id); 11 | 12 | void add(TreeNode treeNode); 13 | 14 | Integer removeById(Long id); 15 | 16 | List findChildren(Long id); 17 | 18 | Integer updateParent(@Param("idList") List idList, 19 | @Param("newParentId") Long newParentId); 20 | 21 | List findChildrenByDb(String dbName); 22 | 23 | TreeNode findSingleDbNode(String dbName); 24 | 25 | List findAllDbNodes(); 26 | 27 | Integer rename(@Param("id") Long id, 28 | @Param("nodeName") String nodeName); 29 | } -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/DbInfo.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import java.util.List; 4 | 5 | public class DbInfo { 6 | 7 | private String db_server; 8 | 9 | private String db_name; 10 | 11 | private List split_table_rules; 12 | 13 | public String getDb_server() { 14 | return db_server; 15 | } 16 | 17 | public void setDb_server(String db_server) { 18 | this.db_server = db_server; 19 | } 20 | 21 | public String getDb_name() { 22 | return db_name; 23 | } 24 | 25 | public void setDb_name(String db_name) { 26 | this.db_name = db_name; 27 | } 28 | 29 | public List getSplit_table_rules() { 30 | return split_table_rules; 31 | } 32 | 33 | public void setSplit_table_rules(List split_table_rules) { 34 | this.split_table_rules = split_table_rules; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * I think element-ui's default theme color is too light for long-term use. 3 | * So I modified the default color and you can modify it to your liking. 4 | **/ 5 | 6 | /* theme color */ 7 | $--color-primary: #1890ff; 8 | $--color-success: #13ce66; 9 | $--color-warning: #FFBA00; 10 | $--color-danger: #ff4949; 11 | // $--color-info: #1E1E1E; 12 | 13 | $--button-font-weight: 400; 14 | 15 | // $--color-text-regular: #1f2d3d; 16 | 17 | $--border-color-light: #dfe4ed; 18 | $--border-color-lighter: #e6ebf5; 19 | 20 | $--table-border:1px solid#dfe6ec; 21 | 22 | /* icon font path, required */ 23 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 24 | 25 | @import "~element-ui/packages/theme-chalk/src/index"; 26 | 27 | // the :export directive is the magic sauce for webpack 28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 29 | :export { 30 | theme: $--color-primary; 31 | } 32 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/FolderClassifyInfo.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | import java.util.List; 7 | 8 | @ApiModel 9 | public class FolderClassifyInfo { 10 | 11 | @ApiModelProperty(value = "所有表(包括未分类的表,和本目录下的下一层子表)") 12 | private List tables; 13 | 14 | @ApiModelProperty(value = "库里未分类表id列表") 15 | private List notClassifiedIdList; 16 | 17 | public List getTables() { 18 | return tables; 19 | } 20 | 21 | public void setTables(List tables) { 22 | this.tables = tables; 23 | } 24 | 25 | public List getNotClassifiedIdList() { 26 | return notClassifiedIdList; 27 | } 28 | 29 | public void setNotClassifiedIdList(List notClassifiedIdList) { 30 | this.notClassifiedIdList = notClassifiedIdList; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /dbtree-vue/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/MybatisCodeGeneratorConfig.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | 5 | public class MybatisCodeGeneratorConfig { 6 | 7 | private String mybatisBaseFolder; 8 | 9 | private String mysqlConnector; 10 | 11 | private String sqlserverConnector; 12 | 13 | public String getMybatisBaseFolder() { 14 | return mybatisBaseFolder; 15 | } 16 | 17 | public void setMybatisBaseFolder(String mybatisBaseFolder) { 18 | this.mybatisBaseFolder = mybatisBaseFolder; 19 | } 20 | 21 | public String getMysqlConnector() { 22 | return mysqlConnector; 23 | } 24 | 25 | public void setMysqlConnector(String mysqlConnector) { 26 | this.mysqlConnector = mysqlConnector; 27 | } 28 | 29 | public String getSqlserverConnector() { 30 | return sqlserverConnector; 31 | } 32 | 33 | public void setSqlserverConnector(String sqlserverConnector) { 34 | this.sqlserverConnector = sqlserverConnector; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/utils/parseTime.spec.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '@/utils/index.js' 2 | 3 | describe('Utils:parseTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | it('timestamp', () => { 6 | expect(parseTime(d)).toBe('2018-07-13 17:54:01') 7 | }) 8 | it('ten digits timestamp', () => { 9 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') 10 | }) 11 | it('new Date', () => { 12 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') 13 | }) 14 | it('format', () => { 15 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 16 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 17 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 18 | }) 19 | it('get the day of the week', () => { 20 | expect(parseTime(d, '{a}')).toBe('五') // 星期五 21 | }) 22 | it('get the day of the week', () => { 23 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 24 | }) 25 | it('empty argument', () => { 26 | expect(parseTime()).toBeNull() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/dao/mysql/dbtree/domain/SyncTaskLock.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.dao.mysql.dbtree.domain; 2 | 3 | import java.util.Date; 4 | 5 | public class SyncTaskLock { 6 | private Integer id; 7 | 8 | private Integer lock_id; 9 | 10 | private Date created_stime; 11 | 12 | private Date modified_stime; 13 | 14 | public Integer getId() { 15 | return id; 16 | } 17 | 18 | public void setId(Integer id) { 19 | this.id = id; 20 | } 21 | 22 | public Integer getLock_id() { 23 | return lock_id; 24 | } 25 | 26 | public void setLock_id(Integer lock_id) { 27 | this.lock_id = lock_id; 28 | } 29 | 30 | public Date getCreated_stime() { 31 | return created_stime; 32 | } 33 | 34 | public void setCreated_stime(Date created_stime) { 35 | this.created_stime = created_stime; 36 | } 37 | 38 | public Date getModified_stime() { 39 | return modified_stime; 40 | } 41 | 42 | public void setModified_stime(Date modified_stime) { 43 | this.modified_stime = modified_stime; 44 | } 45 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 2.1.6.RELEASE 11 | 12 | 13 | 14 | com.autohome 15 | dbtree 16 | pom 17 | 1.0-SNAPSHOT 18 | 19 | dbtree-vue 20 | dbtree-backend 21 | 22 | 23 | 24 | UTF-8 25 | UTF-8 26 | 1.8 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-present Autohome Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dbtree-vue/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import './styles/element-variables.scss' 7 | import 'element-ui/lib/theme-chalk/index.css' 8 | import locale from 'element-ui/lib/locale/lang/en' // lang i18n 9 | 10 | import '@/styles/index.scss' // global css 11 | 12 | import App from './App' 13 | import store from './store' 14 | import router from './router' 15 | 16 | import '@/icons' // icon 17 | import '@/permission' // permission control 18 | 19 | /** 20 | * If you don't want to use mock-server 21 | * you want to use MockJs for mock api 22 | * you can execute: mockXHR() 23 | * 24 | * Currently MockJs will be used in the production environment, 25 | * please remove it before going online! ! ! 26 | */ 27 | import { mockXHR } from '../mock' 28 | if (process.env.NODE_ENV === 'production') { 29 | mockXHR() 30 | } 31 | 32 | // set ElementUI lang to EN 33 | Vue.use(ElementUI, { locale }) 34 | 35 | Vue.config.productionTip = false 36 | 37 | new Vue({ 38 | el: '#app', 39 | router, 40 | store, 41 | render: h => h(App) 42 | }) 43 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/utils/formatTime.spec.js: -------------------------------------------------------------------------------- 1 | import { formatTime } from '@/utils/index.js' 2 | 3 | describe('Utils:formatTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | const retrofit = 5 * 1000 6 | 7 | it('ten digits timestamp', () => { 8 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') 9 | }) 10 | it('test now', () => { 11 | expect(formatTime(+new Date() - 1)).toBe('刚刚') 12 | }) 13 | it('less two minute', () => { 14 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') 15 | }) 16 | it('less two hour', () => { 17 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') 18 | }) 19 | it('less one day', () => { 20 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') 21 | }) 22 | it('more than one day', () => { 23 | expect(formatTime(d)).toBe('7月13日17时54分') 24 | }) 25 | it('format', () => { 26 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 27 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 28 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/DbServer.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | public class DbServer { 4 | 5 | private String host; 6 | 7 | private String port; 8 | 9 | private String user; 10 | 11 | private String password; 12 | 13 | private String db_type; 14 | 15 | public String getHost() { 16 | return host; 17 | } 18 | 19 | public void setHost(String host) { 20 | this.host = host; 21 | } 22 | 23 | public String getPort() { 24 | return port; 25 | } 26 | 27 | public void setPort(String port) { 28 | this.port = port; 29 | } 30 | 31 | public String getUser() { 32 | return user; 33 | } 34 | 35 | public void setUser(String user) { 36 | this.user = user; 37 | } 38 | 39 | public String getPassword() { 40 | return password; 41 | } 42 | 43 | public void setPassword(String password) { 44 | this.password = password; 45 | } 46 | 47 | public String getDb_type() { 48 | return db_type; 49 | } 50 | 51 | public void setDb_type(String db_type) { 52 | this.db_type = db_type; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | 7 | body { 8 | height: 100%; 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | text-rendering: optimizeLegibility; 12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 13 | } 14 | 15 | label { 16 | font-weight: 700; 17 | } 18 | 19 | html { 20 | height: 100%; 21 | box-sizing: border-box; 22 | } 23 | 24 | #app { 25 | height: 100%; 26 | } 27 | 28 | *, 29 | *:before, 30 | *:after { 31 | box-sizing: inherit; 32 | } 33 | 34 | a:focus, 35 | a:active { 36 | outline: none; 37 | } 38 | 39 | a, 40 | a:focus, 41 | a:hover { 42 | cursor: pointer; 43 | color: inherit; 44 | text-decoration: none; 45 | } 46 | 47 | div:focus { 48 | outline: none; 49 | } 50 | 51 | .clearfix { 52 | &:after { 53 | visibility: hidden; 54 | display: block; 55 | font-size: 0; 56 | content: " "; 57 | clear: both; 58 | height: 0; 59 | } 60 | } 61 | 62 | // main-container global css 63 | .app-container { 64 | padding: 20px; 65 | } 66 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const state = { 4 | sidebar: { 5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 6 | withoutAnimation: false 7 | }, 8 | device: 'desktop' 9 | } 10 | 11 | const mutations = { 12 | TOGGLE_SIDEBAR: state => { 13 | state.sidebar.opened = !state.sidebar.opened 14 | state.sidebar.withoutAnimation = false 15 | if (state.sidebar.opened) { 16 | Cookies.set('sidebarStatus', 1) 17 | } else { 18 | Cookies.set('sidebarStatus', 0) 19 | } 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 0) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | } 30 | 31 | const actions = { 32 | toggleSideBar({ commit }) { 33 | commit('TOGGLE_SIDEBAR') 34 | }, 35 | closeSideBar({ commit }, { withoutAnimation }) { 36 | commit('CLOSE_SIDEBAR', withoutAnimation) 37 | }, 38 | toggleDevice({ commit }, device) { 39 | commit('TOGGLE_DEVICE', device) 40 | } 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | mutations, 47 | actions 48 | } 49 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/util/CommentGeneratorExt.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.util; 2 | 3 | import com.google.common.base.Strings; 4 | import org.mybatis.generator.api.IntrospectedColumn; 5 | import org.mybatis.generator.api.IntrospectedTable; 6 | import org.mybatis.generator.api.dom.java.Field; 7 | import org.mybatis.generator.internal.DefaultCommentGenerator; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | public class CommentGeneratorExt extends DefaultCommentGenerator { 12 | 13 | private final static Logger LOGGER = LoggerFactory.getLogger(CommentGeneratorExt.class); 14 | 15 | @Override 16 | public void addFieldComment(Field field, 17 | IntrospectedTable introspectedTable, 18 | IntrospectedColumn introspectedColumn) { 19 | String remarks = introspectedColumn.getRemarks(); 20 | if (Strings.isNullOrEmpty(remarks)) { 21 | return; 22 | } 23 | try { 24 | field.addJavaDocLine("/**"); 25 | field.addJavaDocLine(" * " + remarks); 26 | field.addJavaDocLine(" */"); 27 | } catch (Exception ex) { 28 | LOGGER.error("error when addFieldComment", ex); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dbtree-backend/src/assembly/assembly-descriptor.xml: -------------------------------------------------------------------------------- 1 | 5 | dbtree-assembly 6 | 7 | zip 8 | 9 | false 10 | 11 | 12 | 13 | src/main/scripts 14 | scripts 15 | 16 | *.sh 17 | 18 | 0755 19 | unix 20 | 21 | 22 | src/main/config 23 | ./ 24 | 25 | dbtree.conf 26 | 27 | unix 28 | 29 | 30 | 31 | target 32 | ./ 33 | 34 | dbtree-*.jar 35 | 36 | 0755 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | druid.datasource.dbtree.driver-class-name=com.mysql.jdbc.Driver 2 | druid.datasource.dbtree.url=jdbc:mysql://127.0.0.1:3306/dbtree 3 | druid.datasource.dbtree.username=username 4 | druid.datasource.dbtree.password=encrypted_password 5 | druid.datasource.dbtree.initialSize=1 6 | druid.datasource.dbtree.minIdle=1 7 | druid.datasource.dbtree.maxActive=10 8 | druid.datasource.dbtree.maxWait=60000 9 | druid.datasource.dbtree.timeBetweenEvictionRunsMillis=60000 10 | druid.datasource.dbtree.minEvictableIdleTimeMillis=300000 11 | druid.datasource.dbtree.validationQuery=SELECT 1 FROM DUAL 12 | druid.datasource.dbtree.testWhileIdle=true 13 | druid.datasource.dbtree.testOnBorrow=false 14 | druid.datasource.dbtree.testOnReturn=false 15 | druid.datasource.dbtree.poolPreparedStatements=true 16 | druid.datasource.dbtree.maxPoolPreparedStatementPerConnectionSize=20 17 | druid.datasource.dbtree.filters=config 18 | druid.datasource.dbtree.connectionProperties=config.decrypt=true;druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 19 | druid.datasource.dbtree.mapperLocations=classpath:mapping/*.xml 20 | 21 | mybatis.base.folder=/data/dbtree/10051/mybatis 22 | mybatis.mysql.connector=/data/dbtree/10051/mybatis/connector/mysql-connector-java-5.1.36.jar 23 | mybatis.sqlserver.connector=/data/dbtree/10051/mybatis/connector/sqljdbc42.jar -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | 3 | druid.datasource.dbtree.driver-class-name=com.mysql.jdbc.Driver 4 | druid.datasource.dbtree.url=jdbc:mysql://127.0.0.1:3306/dbtree 5 | druid.datasource.dbtree.username=username 6 | druid.datasource.dbtree.password=encrypted_password 7 | druid.datasource.dbtree.initialSize=1 8 | druid.datasource.dbtree.minIdle=1 9 | druid.datasource.dbtree.maxActive=10 10 | druid.datasource.dbtree.maxWait=60000 11 | druid.datasource.dbtree.timeBetweenEvictionRunsMillis=60000 12 | druid.datasource.dbtree.minEvictableIdleTimeMillis=300000 13 | druid.datasource.dbtree.validationQuery=SELECT 1 FROM DUAL 14 | druid.datasource.dbtree.testWhileIdle=true 15 | druid.datasource.dbtree.testOnBorrow=false 16 | druid.datasource.dbtree.testOnReturn=false 17 | druid.datasource.dbtree.poolPreparedStatements=true 18 | druid.datasource.dbtree.maxPoolPreparedStatementPerConnectionSize=20 19 | druid.datasource.dbtree.filters=config 20 | druid.datasource.dbtree.connectionProperties=config.decrypt=true;druid.stat.mergeSql=true;druid.stat.slowSqlMillis=2000 21 | druid.datasource.dbtree.mapperLocations=classpath:mapping/*.xml 22 | 23 | mybatis.base.folder=D:\\work\\code\\mybatis 24 | mybatis.mysql.connector=D:\\work\\code\\connector\\mysql-connector-java-5.1.36.jar 25 | mybatis.sqlserver.connector=D:\\work\\code\\connector\\sqljdbc42.jar -------------------------------------------------------------------------------- /dbtree-vue/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/ITreeService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service; 2 | 3 | import com.autohome.dbtree.contract.*; 4 | import com.autohome.dbtree.dao.mysql.dbtree.domain.TreeNode; 5 | 6 | import java.util.List; 7 | 8 | public interface ITreeService { 9 | 10 | boolean addDb(String dbName); 11 | 12 | boolean deleteFolder(Long id); 13 | 14 | boolean updateDb(String dbName); 15 | 16 | boolean deleteDb(String dbName); 17 | 18 | boolean moveNodeTo(List nodeIdList, Long newParentId); 19 | 20 | List tableList(String dbName); 21 | 22 | TableInfo oneTable(String dbName, String tableName); 23 | 24 | List columnList(String dbName, String tableName); 25 | 26 | List findAllDbNodes(); 27 | 28 | List findChildren(Long id); 29 | 30 | List findTableChildren(Long id); 31 | 32 | TreeNode addFolderNode(String folderName, Long parentId); 33 | 34 | Boolean rename(Long id, String nodeName); 35 | 36 | TableDetail tableDetail(Long tableId); 37 | 38 | List folderTables(Long folderId); 39 | 40 | FolderClassifyInfo folderClassifyInfo(Long folderId); 41 | 42 | Boolean updateColumnComment(Long nodeId, String columnName, String newComment); 43 | 44 | Boolean updateTableComment(String dbName, String tableName, String newComment); 45 | } 46 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/icons/element-icons.js: -------------------------------------------------------------------------------- 1 | const elementIcons = [ 2 | 'info', 3 | 'error', 4 | 'success', 5 | 'warning', 6 | 'question', 7 | 'back', 8 | 'arrow-left', 9 | 'arrow-down', 10 | 'arrow-right', 11 | 'arrow-up', 12 | 'caret-left', 13 | 'caret-bottom', 14 | 'caret-top', 15 | 'caret-right', 16 | 'd-arrow-left', 17 | 'd-arrow-right', 18 | 'minus', 19 | 'plus', 20 | 'remove', 21 | 'circle-plus', 22 | 'remove-outline', 23 | 'circle-plus-outline', 24 | 'close', 25 | 'check', 26 | 'circle-close', 27 | 'circle-check', 28 | 'circle-close-outline', 29 | 'circle-check-outline', 30 | 'zoom-out', 31 | 'zoom-in', 32 | 'd-caret', 33 | 'sort', 34 | 'sort-down', 35 | 'sort-up', 36 | 'tickets', 37 | 'document', 38 | 'goods', 39 | 'sold-out', 40 | 'news', 41 | 'message', 42 | 'date', 43 | 'printer', 44 | 'time', 45 | 'bell', 46 | 'mobile-phone', 47 | 'service', 48 | 'view', 49 | 'menu', 50 | 'more', 51 | 'more-outline', 52 | 'star-on', 53 | 'star-off', 54 | 'location', 55 | 'location-outline', 56 | 'phone', 57 | 'phone-outline', 58 | 'picture', 59 | 'picture-outline', 60 | 'delete', 61 | 'search', 62 | 'edit', 63 | 'edit-outline', 64 | 'rank', 65 | 'refresh', 66 | 'share', 67 | 'setting', 68 | 'upload', 69 | 'upload2', 70 | 'download', 71 | 'loading' 72 | ] 73 | 74 | export default elementIcons 75 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dbtree-vue/src/api/code.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const code = { 4 | 5 | /** 6 | * 生成mybatis代码 7 | * @param {*} dbName 库名 8 | * @param {*} domainPackage domain包 9 | * @param {*} mapperPackage mapper包 10 | * @param {*} useActualColumnNames 使用真实列名作为字段名 11 | * @param {*} tables 表 12 | */ 13 | generate(dbName, domainPackage, mapperPackage, useActualColumnNames, tables) { 14 | return request({ 15 | url: '/code/generate', 16 | method: 'get', 17 | params: { 18 | dbName: dbName, 19 | domainPackage: domainPackage, 20 | mapperPackage: mapperPackage, 21 | useActualColumnNames: useActualColumnNames, 22 | tables: tables 23 | } 24 | }); 25 | }, 26 | 27 | /** 28 | * 下载mybatis资源 29 | * @param {*} zipFile 文件名 30 | */ 31 | mybatisDownload(zipFile) { 32 | return request({ 33 | url: '/code/mybatisDownload', 34 | method: 'get', 35 | params: { 36 | zipFile: zipFile 37 | }, 38 | responseType: 'blob' 39 | }); 40 | }, 41 | 42 | /** 43 | * 表结构导出markdown文档 44 | * @param {*} dbName 库名 45 | * @param {*} tables 表 46 | */ 47 | exportMarkdown(dbName, tables) { 48 | return request({ 49 | url: '/code/exportMarkdown', 50 | method: 'get', 51 | params: { 52 | dbName: dbName, 53 | tables: tables 54 | }, 55 | responseType: 'blob' 56 | }); 57 | } 58 | } 59 | 60 | 61 | 62 | export default code 63 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/mapping/SyncTaskLockMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | id, lock_id, created_stime, modified_stime 12 | 13 | 14 | select 15 | 16 | from sync_task_lock 17 | where id = #{id,jdbcType=INTEGER} 18 | 19 | 20 | 21 | SELECT LAST_INSERT_ID() 22 | 23 | insert into sync_task_lock (lock_id) values (#{lock_id,jdbcType=INTEGER}) 24 | 25 | 26 | 27 | DELETE FROM sync_task_lock WHERE lock_id=#{lockId} 28 | 29 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/Swagger2Config.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.ApiInfoBuilder; 6 | import springfox.documentation.builders.PathSelectors; 7 | import springfox.documentation.builders.RequestHandlerSelectors; 8 | import springfox.documentation.service.ApiInfo; 9 | import springfox.documentation.spi.DocumentationType; 10 | import springfox.documentation.spring.web.plugins.Docket; 11 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 12 | 13 | @Configuration 14 | @EnableSwagger2 15 | public class Swagger2Config { 16 | 17 | @Bean 18 | public Docket api() { 19 | return new Docket(DocumentationType.SWAGGER_2).select() 20 | .apis(RequestHandlerSelectors 21 | .basePackage("com.autohome.dbtree.controller")) 22 | .paths(PathSelectors.any()) 23 | .build().apiInfo(apiEndPointsInfo()); 24 | } 25 | private ApiInfo apiEndPointsInfo() { 26 | return new ApiInfoBuilder().title("dbtree-backend API") 27 | .description("dbtree-backend API") 28 | //.contact(new Contact("Ramesh Fadatare", "www.javaguides.net", "ramesh24fadatare@gmail.com")) 29 | .license("Apache 2.0") 30 | .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html") 31 | .version("1.0.0") 32 | .build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /dbtree-vue/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | import java.io.Serializable; 7 | 8 | @ApiModel 9 | public class Protocol implements Serializable { 10 | 11 | private static final long serialVersionUID = 1L; 12 | 13 | @ApiModelProperty(value = "返回码") 14 | private int returncode; 15 | 16 | @ApiModelProperty(value = "说明消息") 17 | private String message; 18 | 19 | @ApiModelProperty(value = "返回结果") 20 | private T result; 21 | 22 | public int getReturncode() { 23 | return returncode; 24 | } 25 | 26 | public void setReturncode(int returncode) { 27 | this.returncode = returncode; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | 34 | public void setMessage(String message) { 35 | this.message = message; 36 | } 37 | 38 | public T getResult() { 39 | return result; 40 | } 41 | 42 | public void setResult(T result) { 43 | this.result = result; 44 | } 45 | 46 | public Protocol() { 47 | } 48 | 49 | public Protocol(int returncode, String message, T result) { 50 | this.returncode = returncode; 51 | this.message = message; 52 | this.result = result; 53 | } 54 | 55 | public Protocol(int returncode, String message) { 56 | this(returncode, message, null); 57 | } 58 | 59 | public Protocol(T result) { 60 | this(0, "ok", result); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/BeanConfig.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import com.autohome.dbtree.service.impl.ConnManager; 4 | import com.autohome.dbtree.service.impl.LockService; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class BeanConfig { 12 | 13 | @Value("${mybatis.base.folder}") 14 | private String mybatisBaseFolder; 15 | 16 | @Value("${mybatis.mysql.connector}") 17 | private String mysqlConnector; 18 | 19 | @Value("${mybatis.sqlserver.connector}") 20 | private String sqlserverConnector; 21 | 22 | @Bean 23 | public ObjectMapper objectMapper() { 24 | return new ObjectMapper(); 25 | } 26 | 27 | @Bean(destroyMethod = "close") 28 | public ConnManager connManager() { 29 | return new ConnManager(); 30 | } 31 | 32 | @Bean(destroyMethod = "unLock") 33 | public LockService lockService() { 34 | return new LockService(); 35 | } 36 | 37 | @Bean 38 | public MybatisCodeGeneratorConfig mybatisCodeGeneratorConfig() { 39 | MybatisCodeGeneratorConfig mybatisCodeGeneratorConfig = new MybatisCodeGeneratorConfig(); 40 | mybatisCodeGeneratorConfig.setMybatisBaseFolder(mybatisBaseFolder); 41 | mybatisCodeGeneratorConfig.setMysqlConnector(mysqlConnector); 42 | mybatisCodeGeneratorConfig.setSqlserverConnector(sqlserverConnector); 43 | return mybatisCodeGeneratorConfig; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 57 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 78 | 79 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/dbtree/server/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 78 | 79 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/dao/mysql/dbtree/domain/TreeNode.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.dao.mysql.dbtree.domain; 2 | 3 | import java.util.Date; 4 | 5 | public class TreeNode { 6 | private Long id; 7 | 8 | private String db_name; 9 | 10 | private Integer folder; 11 | 12 | private String node_name; 13 | 14 | private Long parent_id; 15 | 16 | private Date created_stime; 17 | 18 | private Date modified_stime; 19 | 20 | public Long getId() { 21 | return id; 22 | } 23 | 24 | public void setId(Long id) { 25 | this.id = id; 26 | } 27 | 28 | public String getDb_name() { 29 | return db_name; 30 | } 31 | 32 | public void setDb_name(String db_name) { 33 | this.db_name = db_name == null ? null : db_name.trim(); 34 | } 35 | 36 | public Integer getFolder() { 37 | return folder; 38 | } 39 | 40 | public void setFolder(Integer folder) { 41 | this.folder = folder; 42 | } 43 | 44 | public String getNode_name() { 45 | return node_name; 46 | } 47 | 48 | public void setNode_name(String node_name) { 49 | this.node_name = node_name == null ? null : node_name.trim(); 50 | } 51 | 52 | public Long getParent_id() { 53 | return parent_id; 54 | } 55 | 56 | public void setParent_id(Long parent_id) { 57 | this.parent_id = parent_id; 58 | } 59 | 60 | public Date getCreated_stime() { 61 | return created_stime; 62 | } 63 | 64 | public void setCreated_stime(Date created_stime) { 65 | this.created_stime = created_stime; 66 | } 67 | 68 | public Date getModified_stime() { 69 | return modified_stime; 70 | } 71 | 72 | public void setModified_stime(Date modified_stime) { 73 | this.modified_stime = modified_stime; 74 | } 75 | } -------------------------------------------------------------------------------- /dbtree-vue/mock/user.js: -------------------------------------------------------------------------------- 1 | 2 | const tokens = { 3 | admin: { 4 | token: 'admin-token' 5 | }, 6 | editor: { 7 | token: 'editor-token' 8 | } 9 | } 10 | 11 | const users = { 12 | 'admin-token': { 13 | roles: ['admin'], 14 | introduction: 'I am a super administrator', 15 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 16 | name: 'Super Admin' 17 | }, 18 | 'editor-token': { 19 | roles: ['editor'], 20 | introduction: 'I am an editor', 21 | avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', 22 | name: 'Normal Editor' 23 | } 24 | } 25 | 26 | export default [ 27 | // user login 28 | { 29 | url: '/user/login', 30 | type: 'post', 31 | response: config => { 32 | const { username } = config.body 33 | const token = tokens[username] 34 | 35 | // mock error 36 | if (!token) { 37 | return { 38 | code: 60204, 39 | message: 'Account and password are incorrect.' 40 | } 41 | } 42 | 43 | return { 44 | code: 20000, 45 | data: token 46 | } 47 | } 48 | }, 49 | 50 | // get user info 51 | { 52 | url: '/user/info\.*', 53 | type: 'get', 54 | response: config => { 55 | const { token } = config.query 56 | const info = users[token] 57 | 58 | // mock error 59 | if (!info) { 60 | return { 61 | code: 50008, 62 | message: 'Login failed, unable to get user details.' 63 | } 64 | } 65 | 66 | return { 67 | code: 20000, 68 | data: info 69 | } 70 | } 71 | }, 72 | 73 | // user logout 74 | { 75 | url: '/user/logout', 76 | type: 'post', 77 | response: _ => { 78 | return { 79 | code: 20000, 80 | data: 'success' 81 | } 82 | } 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/DbTreeConfig.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import java.io.IOException; 4 | import java.io.StringWriter; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.ArrayList; 7 | import java.util.Map; 8 | 9 | import javax.annotation.Resource; 10 | 11 | import com.autohome.dbtree.contract.DbInfo; 12 | import com.autohome.dbtree.contract.DbServer; 13 | import com.fasterxml.jackson.core.type.TypeReference; 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | import org.apache.commons.io.IOUtils; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.core.io.ClassPathResource; 19 | 20 | @Configuration 21 | public class DbTreeConfig { 22 | 23 | @Resource 24 | private ObjectMapper objectMapper; 25 | 26 | @Bean 27 | public Map dbInfoMap() throws IOException { 28 | String json = readJsonFile("dbconfig/db-config.json"); 29 | Map dbInfoMap = objectMapper.readValue(json, new TypeReference>() {}); 30 | return dbInfoMap; 31 | } 32 | 33 | @Bean 34 | public Map dbServerMap() throws IOException { 35 | String json = readJsonFile("dbconfig/db-server.json"); 36 | Map dbServerMap = objectMapper.readValue(json, new TypeReference>() {}); 37 | return dbServerMap; 38 | } 39 | 40 | private String readJsonFile(String path) throws IOException { 41 | ClassPathResource classPathResource = new ClassPathResource(path); 42 | try (StringWriter stringWriter = new StringWriter()) { 43 | IOUtils.copy(classPathResource.getInputStream(), stringWriter, StandardCharsets.UTF_8); 44 | return stringWriter.toString(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dbtree-vue/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { param2Obj } from '../src/utils' 3 | 4 | import user from './user' 5 | import table from './table' 6 | 7 | const mocks = [ 8 | ...user, 9 | ...table 10 | ] 11 | 12 | // for front mock 13 | // please use it cautiously, it will redefine XMLHttpRequest, 14 | // which will cause many of your third-party libraries to be invalidated(like progress event). 15 | export function mockXHR() { 16 | // mock patch 17 | // https://github.com/nuysoft/Mock/issues/300 18 | Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send 19 | Mock.XHR.prototype.send = function() { 20 | if (this.custom.xhr) { 21 | this.custom.xhr.withCredentials = this.withCredentials || false 22 | 23 | if (this.responseType) { 24 | this.custom.xhr.responseType = this.responseType 25 | } 26 | } 27 | this.proxy_send(...arguments) 28 | } 29 | 30 | function XHR2ExpressReqWrap(respond) { 31 | return function(options) { 32 | let result = null 33 | if (respond instanceof Function) { 34 | const { body, type, url } = options 35 | // https://expressjs.com/en/4x/api.html#req 36 | result = respond({ 37 | method: type, 38 | body: JSON.parse(body), 39 | query: param2Obj(url) 40 | }) 41 | } else { 42 | result = respond 43 | } 44 | return Mock.mock(result) 45 | } 46 | } 47 | 48 | for (const i of mocks) { 49 | Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response)) 50 | } 51 | } 52 | 53 | // for mock server 54 | const responseFake = (url, type, respond) => { 55 | return { 56 | url: new RegExp(`/mock${url}`), 57 | type: type || 'get', 58 | response(req, res) { 59 | res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond)) 60 | } 61 | } 62 | } 63 | 64 | export default mocks.map(route => { 65 | return responseFake(route.url, route.type, route.response) 66 | }) 67 | -------------------------------------------------------------------------------- /dbtree-vue/src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import { Message } from 'element-ui' 4 | import NProgress from 'nprogress' // progress bar 5 | import 'nprogress/nprogress.css' // progress bar style 6 | import { getToken } from '@/utils/auth' // get token from cookie 7 | import getPageTitle from '@/utils/get-page-title' 8 | 9 | NProgress.configure({ showSpinner: false }) // NProgress Configuration 10 | 11 | const whiteList = ['/login'] // no redirect whitelist 12 | 13 | router.beforeEach(async(to, from, next) => { 14 | // start progress bar 15 | NProgress.start() 16 | 17 | // set page title 18 | document.title = getPageTitle(to.meta.title) 19 | 20 | // determine whether the user has logged in 21 | const hasToken = getToken() 22 | 23 | if (hasToken) { 24 | if (to.path === '/login') { 25 | // if is logged in, redirect to the home page 26 | next({ path: '/' }) 27 | NProgress.done() 28 | } else { 29 | const hasGetUserInfo = store.getters.name 30 | if (hasGetUserInfo) { 31 | next() 32 | } else { 33 | try { 34 | // get user info 35 | await store.dispatch('user/getInfo') 36 | 37 | next() 38 | } catch (error) { 39 | // remove token and go to login page to re-login 40 | await store.dispatch('user/resetToken') 41 | Message.error(error || 'Has Error') 42 | next(`/login?redirect=${to.path}`) 43 | NProgress.done() 44 | } 45 | } 46 | } 47 | } else { 48 | /* has no token*/ 49 | 50 | if (whiteList.indexOf(to.path) !== -1) { 51 | // in the free login whitelist, go directly 52 | next() 53 | } else { 54 | // other pages that do not have permission to access are redirected to the login page. 55 | next(`/login?redirect=${to.path}`) 56 | NProgress.done() 57 | } 58 | } 59 | }) 60 | 61 | router.afterEach(() => { 62 | // finish progress bar 63 | NProgress.done() 64 | }) 65 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/impl/TableExportService.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service.impl; 2 | 3 | import com.autohome.dbtree.contract.ColumnInfo; 4 | import com.autohome.dbtree.contract.TableInfo; 5 | import com.autohome.dbtree.service.ITableExportService; 6 | import com.autohome.dbtree.service.ITreeService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.annotation.Resource; 10 | import java.util.List; 11 | 12 | @Service 13 | public class TableExportService implements ITableExportService { 14 | 15 | @Resource 16 | private ITreeService treeService; 17 | 18 | @Override 19 | public String exportMarkdown(String dbName, List tableList) { 20 | StringBuilder markdownBuilder = new StringBuilder(); 21 | for (String table : tableList) { 22 | TableInfo tableInfo = treeService.oneTable(dbName, table); 23 | List columnInfoList = treeService.columnList(dbName, table); 24 | markdownBuilder.append("\n\n").append("## ").append(table).append(": ").append(tableInfo.getDescription()).append("\n"); 25 | markdownBuilder.append("序号|字段名|类型|自增|可空|主键|默认值|注释").append("\n") 26 | .append("---|---|---|---|---|---|---|---"); 27 | for (int i = 0; i < columnInfoList.size(); i++) { 28 | ColumnInfo columnInfo = columnInfoList.get(i); 29 | markdownBuilder.append("\n").append(i).append("|") 30 | .append(columnInfo.getColumnName()).append("|") 31 | .append(columnInfo.getColumnType()).append("|") 32 | .append(columnInfo.getAutoIncrement()).append("|") 33 | .append(columnInfo.getNullable()).append("|") 34 | .append(columnInfo.getPrimaryKey()).append("|") 35 | .append(columnInfo.getColumnDefault() == null ? "" : columnInfo.getColumnDefault()).append("|") 36 | .append(columnInfo.getColumnComment()); 37 | } 38 | } 39 | 40 | return markdownBuilder.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/Logo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ title }} 7 | 8 | 9 | 10 | {{ title }} 11 | 12 | 13 | 14 | 15 | 16 | 33 | 34 | 83 | -------------------------------------------------------------------------------- /dbtree-vue/mock/mock-server.js: -------------------------------------------------------------------------------- 1 | const chokidar = require('chokidar') 2 | const bodyParser = require('body-parser') 3 | const chalk = require('chalk') 4 | const path = require('path') 5 | 6 | const mockDir = path.join(process.cwd(), 'mock') 7 | 8 | function registerRoutes(app) { 9 | let mockLastIndex 10 | const { default: mocks } = require('./index.js') 11 | for (const mock of mocks) { 12 | app[mock.type](mock.url, mock.response) 13 | mockLastIndex = app._router.stack.length 14 | } 15 | const mockRoutesLength = Object.keys(mocks).length 16 | return { 17 | mockRoutesLength: mockRoutesLength, 18 | mockStartIndex: mockLastIndex - mockRoutesLength 19 | } 20 | } 21 | 22 | function unregisterRoutes() { 23 | Object.keys(require.cache).forEach(i => { 24 | if (i.includes(mockDir)) { 25 | delete require.cache[require.resolve(i)] 26 | } 27 | }) 28 | } 29 | 30 | module.exports = app => { 31 | // es6 polyfill 32 | require('@babel/register') 33 | 34 | // parse app.body 35 | // https://expressjs.com/en/4x/api.html#req.body 36 | app.use(bodyParser.json()) 37 | app.use(bodyParser.urlencoded({ 38 | extended: true 39 | })) 40 | 41 | const mockRoutes = registerRoutes(app) 42 | var mockRoutesLength = mockRoutes.mockRoutesLength 43 | var mockStartIndex = mockRoutes.mockStartIndex 44 | 45 | // watch files, hot reload mock server 46 | chokidar.watch(mockDir, { 47 | ignored: /mock-server/, 48 | ignoreInitial: true 49 | }).on('all', (event, path) => { 50 | if (event === 'change' || event === 'add') { 51 | try { 52 | // remove mock routes stack 53 | app._router.stack.splice(mockStartIndex, mockRoutesLength) 54 | 55 | // clear routes cache 56 | unregisterRoutes() 57 | 58 | const mockRoutes = registerRoutes(app) 59 | mockRoutesLength = mockRoutes.mockRoutesLength 60 | mockStartIndex = mockRoutes.mockStartIndex 61 | 62 | console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`)) 63 | } catch (error) { 64 | console.log(chalk.redBright(error)) 65 | } 66 | } 67 | }) 68 | } 69 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/ScheduleTask.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import com.autohome.dbtree.contract.DbInfo; 4 | import com.autohome.dbtree.dao.mysql.dbtree.domain.TreeNode; 5 | import com.autohome.dbtree.service.ITreeService; 6 | import com.autohome.dbtree.service.impl.LockService; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.scheduling.annotation.Scheduled; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Component 17 | public class ScheduleTask { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTask.class); 20 | 21 | @Resource 22 | private LockService lockService; 23 | 24 | @Resource 25 | private Map dbInfoMap; 26 | 27 | @Resource 28 | private ITreeService treeService; 29 | 30 | @Scheduled(fixedDelay = 1000 * 60 * 5, initialDelay = 1000 * 10) 31 | public void syncTableTask() { 32 | Boolean haveLock = lockService.tryLock(); 33 | if(!haveLock) { 34 | return; 35 | } 36 | try { 37 | List databaseNodes = treeService.findAllDbNodes(); 38 | for (Map.Entry dbInfoEntry : dbInfoMap.entrySet()) { 39 | try { 40 | String dbName = dbInfoEntry.getKey(); 41 | boolean exist = databaseNodes.stream().anyMatch(x -> x.getNode_name().equalsIgnoreCase(dbName)); 42 | if (exist) { 43 | treeService.updateDb(dbName); 44 | } else { 45 | treeService.addDb(dbName); 46 | } 47 | } catch (Exception ex) { 48 | LOGGER.error("error when sync table for db: " + dbInfoEntry.getKey(), ex); 49 | } 50 | } 51 | } catch (Exception ex) { 52 | LOGGER.error("error when syncTableTask", ex); 53 | } finally { 54 | lockService.unLock(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /dbtree-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "4.2.1", 4 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", 5 | "author": "Pan ", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "vue-cli-service serve", 9 | "build:prod": "vue-cli-service build", 10 | "build:stage": "vue-cli-service build --mode staging", 11 | "preview": "node build/index.js --preview", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test:unit": "jest --clearCache && vue-cli-service test:unit", 14 | "test:ci": "npm run lint && npm run test:unit", 15 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml", 16 | "inspect": "vue-cli-service inspect" 17 | }, 18 | "dependencies": { 19 | "axios": "0.18.1", 20 | "clipboard": "^2.0.4", 21 | "element-ui": "^2.12.0", 22 | "js-cookie": "2.2.0", 23 | "normalize.css": "7.0.0", 24 | "nprogress": "0.2.0", 25 | "path-to-regexp": "2.4.0", 26 | "vue": "2.6.10", 27 | "vue-router": "3.0.6", 28 | "vuex": "3.1.0" 29 | }, 30 | "devDependencies": { 31 | "@babel/core": "7.0.0", 32 | "@babel/register": "7.0.0", 33 | "@vue/cli-plugin-babel": "3.6.0", 34 | "@vue/cli-plugin-eslint": "^3.9.1", 35 | "@vue/cli-plugin-unit-jest": "3.6.3", 36 | "@vue/cli-service": "3.6.0", 37 | "@vue/test-utils": "1.0.0-beta.29", 38 | "autoprefixer": "^9.5.1", 39 | "babel-core": "7.0.0-bridge.0", 40 | "babel-eslint": "10.0.1", 41 | "babel-jest": "23.6.0", 42 | "chalk": "2.4.2", 43 | "connect": "3.6.6", 44 | "eslint": "5.15.3", 45 | "eslint-plugin-vue": "5.2.2", 46 | "html-webpack-plugin": "3.2.0", 47 | "mockjs": "1.0.1-beta3", 48 | "node-sass": "^4.9.0", 49 | "runjs": "^4.3.2", 50 | "sass-loader": "^7.1.0", 51 | "script-ext-html-webpack-plugin": "2.1.3", 52 | "script-loader": "0.7.2", 53 | "serve-static": "^1.13.2", 54 | "svg-sprite-loader": "4.1.3", 55 | "svgo": "1.2.2", 56 | "vue-template-compiler": "2.6.10" 57 | }, 58 | "engines": { 59 | "node": ">=8.9", 60 | "npm": ">= 3.0.0" 61 | }, 62 | "browserslist": [ 63 | "> 1%", 64 | "last 2 versions" 65 | ] 66 | } 67 | -------------------------------------------------------------------------------- /dbtree-vue/src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/user' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | import { resetRouter } from '@/router' 4 | 5 | const state = { 6 | token: getToken(), 7 | name: '', 8 | avatar: '' 9 | } 10 | 11 | const mutations = { 12 | SET_TOKEN: (state, token) => { 13 | state.token = token 14 | }, 15 | SET_NAME: (state, name) => { 16 | state.name = name 17 | }, 18 | SET_AVATAR: (state, avatar) => { 19 | state.avatar = avatar 20 | } 21 | } 22 | 23 | const actions = { 24 | // user login 25 | login({ commit }, userInfo) { 26 | const { username, password } = userInfo 27 | return new Promise((resolve, reject) => { 28 | login({ username: username.trim(), password: password }).then(response => { 29 | const { data } = response 30 | commit('SET_TOKEN', data.token) 31 | setToken(data.token) 32 | resolve() 33 | }).catch(error => { 34 | reject(error) 35 | }) 36 | }) 37 | }, 38 | 39 | // get user info 40 | getInfo({ commit, state }) { 41 | return new Promise((resolve, reject) => { 42 | getInfo(state.token).then(response => { 43 | const { data } = response 44 | 45 | if (!data) { 46 | reject('Verification failed, please Login again.') 47 | } 48 | 49 | const { name, avatar } = data 50 | 51 | commit('SET_NAME', name) 52 | commit('SET_AVATAR', avatar) 53 | resolve(data) 54 | }).catch(error => { 55 | reject(error) 56 | }) 57 | }) 58 | }, 59 | 60 | // user logout 61 | logout({ commit, state }) { 62 | return new Promise((resolve, reject) => { 63 | logout(state.token).then(() => { 64 | commit('SET_TOKEN', '') 65 | removeToken() 66 | resetRouter() 67 | resolve() 68 | }).catch(error => { 69 | reject(error) 70 | }) 71 | }) 72 | }, 73 | 74 | // remove token 75 | resetToken({ commit }) { 76 | return new Promise(resolve => { 77 | commit('SET_TOKEN', '') 78 | removeToken() 79 | resolve() 80 | }) 81 | } 82 | } 83 | 84 | export default { 85 | namespaced: true, 86 | state, 87 | mutations, 88 | actions 89 | } 90 | 91 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 52 | 53 | 94 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/TreeNodeVo.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import com.autohome.dbtree.dao.mysql.dbtree.domain.TreeNode; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | 7 | @ApiModel 8 | public class TreeNodeVo { 9 | 10 | @ApiModelProperty(value = "所属数据库") 11 | private String database; 12 | 13 | @ApiModelProperty(value = "节点名") 14 | private String node_name; 15 | 16 | @ApiModelProperty(value = "是否目录节点") 17 | private Boolean leaf; 18 | 19 | @ApiModelProperty(value = "节点ID") 20 | private Long id; 21 | 22 | @ApiModelProperty(value = "父亲节点ID") 23 | private Long parent_id; 24 | 25 | @ApiModelProperty(value = "是否在编辑") 26 | private Boolean edit; 27 | 28 | public String getDatabase() { 29 | return database; 30 | } 31 | 32 | public void setDatabase(String database) { 33 | this.database = database; 34 | } 35 | 36 | public String getNode_name() { 37 | return node_name; 38 | } 39 | 40 | public void setNode_name(String node_name) { 41 | this.node_name = node_name; 42 | } 43 | 44 | public Long getId() { 45 | return id; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public Long getParent_id() { 53 | return parent_id; 54 | } 55 | 56 | public void setParent_id(Long parent_id) { 57 | this.parent_id = parent_id; 58 | } 59 | 60 | public Boolean getLeaf() { 61 | return leaf; 62 | } 63 | 64 | public void setLeaf(Boolean leaf) { 65 | this.leaf = leaf; 66 | } 67 | 68 | public Boolean getEdit() { 69 | return edit; 70 | } 71 | 72 | public void setEdit(Boolean edit) { 73 | this.edit = edit; 74 | } 75 | 76 | public static TreeNodeVo fromTreeNode(TreeNode treeNode) { 77 | TreeNodeVo treeNodeVo = new TreeNodeVo(); 78 | treeNodeVo.setId(treeNode.getId()); 79 | treeNodeVo.setNode_name(treeNode.getNode_name()); 80 | treeNodeVo.setParent_id(treeNode.getParent_id()); 81 | treeNodeVo.setLeaf(treeNode.getFolder() == 0); 82 | treeNodeVo.setEdit(false); 83 | treeNodeVo.setDatabase(treeNode.getDb_name()); 84 | return treeNodeVo; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /dbtree-vue/src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ item.meta.title }} 6 | {{ item.meta.title }} 7 | 8 | 9 | 10 | 11 | 12 | 65 | 66 | 79 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/table/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | {{ scope.$index }} 14 | 15 | 16 | 17 | 18 | {{ scope.row.title }} 19 | 20 | 21 | 22 | 23 | {{ scope.row.author }} 24 | 25 | 26 | 27 | 28 | {{ scope.row.pageviews }} 29 | 30 | 31 | 32 | 33 | {{ scope.row.status }} 34 | 35 | 36 | 37 | 38 | 39 | {{ scope.row.display_time }} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 80 | -------------------------------------------------------------------------------- /dbtree-vue/src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/icons/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | {{ generateIconCode(item) }} 13 | 14 | 15 | 16 | {{ item }} 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {{ generateElementIconCode(item) }} 26 | 27 | 28 | 29 | {{ item }} 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 64 | 65 | 92 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/form/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | - 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Create 44 | Cancel 45 | 46 | 47 | 48 | 49 | 50 | 79 | 80 | 85 | 86 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/config/DbTreeMybatisConfig.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.config; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import com.github.pagehelper.PageInterceptor; 5 | import org.apache.ibatis.plugin.Interceptor; 6 | import org.apache.ibatis.session.SqlSessionFactory; 7 | import org.mybatis.spring.SqlSessionFactoryBean; 8 | import org.mybatis.spring.annotation.MapperScan; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.context.properties.ConfigurationProperties; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 14 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 15 | import org.springframework.transaction.PlatformTransactionManager; 16 | import org.springframework.transaction.annotation.EnableTransactionManagement; 17 | 18 | import java.io.IOException; 19 | import java.util.Properties; 20 | 21 | @Configuration 22 | @EnableTransactionManagement 23 | @MapperScan(value = "com.autohome.dbtree.dao.mysql.dbtree.mapper", sqlSessionFactoryRef = "dbTreeSqlSessionFactory") 24 | public class DbTreeMybatisConfig { 25 | 26 | @Value("${druid.datasource.dbtree.mapperLocations}") 27 | private String mapperLocations; 28 | 29 | @Bean(name = "dbTreeDataSource", destroyMethod = "close") 30 | @ConfigurationProperties(prefix = "druid.datasource.dbtree") 31 | public DruidDataSource dbTreeDataSource() { 32 | return new DruidDataSource(); 33 | } 34 | 35 | @Bean(name = "dbTreeSqlSessionFactory") 36 | public SqlSessionFactory dbTreeSqlSessionFactory() throws Exception { 37 | PageInterceptor pageInterceptor = new PageInterceptor(); 38 | pageInterceptor.setProperties(new Properties()); 39 | SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 40 | sqlSessionFactoryBean.setDataSource(dbTreeDataSource()); 41 | sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageInterceptor}); 42 | 43 | try { 44 | PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 45 | sqlSessionFactoryBean.setMapperLocations(resolver.getResources(this.mapperLocations)); 46 | } catch (IOException ex) { 47 | throw new RuntimeException(ex); 48 | } 49 | 50 | try { 51 | return sqlSessionFactoryBean.getObject(); 52 | } catch (Exception ex) { 53 | throw new RuntimeException(ex); 54 | } 55 | } 56 | 57 | @Bean 58 | public PlatformTransactionManager dbTreeTransactionManager() throws Exception { 59 | return new DataSourceTransactionManager(dbTreeDataSource()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { MessageBox, Message } from 'element-ui' 3 | import store from '@/store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // create an axios instance 7 | const service = axios.create({ 8 | baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url 9 | // withCredentials: true, // send cookies when cross-domain requests 10 | timeout: 10000 // request timeout 11 | }) 12 | 13 | // request interceptor 14 | // service.interceptors.request.use( 15 | // config => { 16 | // // do something before request is sent 17 | 18 | // if (store.getters.token) { 19 | // // let each request carry token 20 | // // ['X-Token'] is a custom headers key 21 | // // please modify it according to the actual situation 22 | // config.headers['X-Token'] = getToken() 23 | // } 24 | // return config 25 | // }, 26 | // error => { 27 | // // do something with request error 28 | // console.log(error) // for debug 29 | // return Promise.reject(error) 30 | // } 31 | // ) 32 | 33 | // response interceptor 34 | // service.interceptors.response.use( 35 | // /** 36 | // * If you want to get http information such as headers or status 37 | // * Please return response => response 38 | // */ 39 | 40 | // /** 41 | // * Determine the request status by custom code 42 | // * Here is just an example 43 | // * You can also judge the status by HTTP Status Code 44 | // */ 45 | // response => { 46 | // const res = response.data 47 | 48 | // // if the custom code is not 20000, it is judged as an error. 49 | // if (res.code !== 20000) { 50 | // Message({ 51 | // message: res.message || 'Error', 52 | // type: 'error', 53 | // duration: 5 * 1000 54 | // }) 55 | 56 | // // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired; 57 | // if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 58 | // // to re-login 59 | // MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', { 60 | // confirmButtonText: 'Re-Login', 61 | // cancelButtonText: 'Cancel', 62 | // type: 'warning' 63 | // }).then(() => { 64 | // store.dispatch('user/resetToken').then(() => { 65 | // location.reload() 66 | // }) 67 | // }) 68 | // } 69 | // return Promise.reject(new Error(res.message || 'Error')) 70 | // } else { 71 | // return res 72 | // } 73 | // }, 74 | // error => { 75 | // console.log('err' + error) // for debug 76 | // Message({ 77 | // message: error.message, 78 | // type: 'error', 79 | // duration: 5 * 1000 80 | // }) 81 | // return Promise.reject(error) 82 | // } 83 | // ) 84 | 85 | export default service 86 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/resources/mapping/TreeNodeMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | id, db_name, folder, node_name, parent_id, created_stime, modified_stime 15 | 16 | 17 | select 18 | 19 | from tree_node 20 | where id = #{id,jdbcType=BIGINT} 21 | 22 | 23 | 25 | INSERT INTO tree_node(db_name, folder, node_name, parent_id) 26 | VALUES(#{db_name}, #{folder}, #{node_name}, #{parent_id}) 27 | 28 | 29 | 30 | DELETE FROM tree_node WHERE id=#{id} 31 | 32 | 33 | 34 | SELECT FROM tree_node 35 | WHERE parent_id=#{id} ORDER BY folder DESC, node_name ASC 36 | 37 | 38 | 39 | UPDATE tree_node SET parent_id = #{newParentId} WHERE id IN 40 | 42 | #{item} 43 | 44 | 45 | 46 | 47 | SELECT FROM tree_node 48 | WHERE db_name=#{dbName} 49 | 50 | 51 | 52 | SELECT FROM tree_node 53 | WHERE db_name=#{dbName} AND parent_id=0 54 | 55 | 56 | 57 | SELECT FROM tree_node 58 | WHERE parent_id=0 59 | 60 | 61 | 62 | UPDATE tree_node SET node_name=#{nodeName} WHERE id=#{id} 63 | 64 | -------------------------------------------------------------------------------- /dbtree-vue/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * Parse the time to string 7 | * @param {(Object|string|number)} time 8 | * @param {string} cFormat 9 | * @returns {string} 10 | */ 11 | export function parseTime(time, cFormat) { 12 | if (arguments.length === 0) { 13 | return null 14 | } 15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 16 | let date 17 | if (typeof time === 'object') { 18 | date = time 19 | } else { 20 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { 21 | time = parseInt(time) 22 | } 23 | if ((typeof time === 'number') && (time.toString().length === 10)) { 24 | time = time * 1000 25 | } 26 | date = new Date(time) 27 | } 28 | const formatObj = { 29 | y: date.getFullYear(), 30 | m: date.getMonth() + 1, 31 | d: date.getDate(), 32 | h: date.getHours(), 33 | i: date.getMinutes(), 34 | s: date.getSeconds(), 35 | a: date.getDay() 36 | } 37 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 38 | let value = formatObj[key] 39 | // Note: getDay() returns 0 on Sunday 40 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] } 41 | if (result.length > 0 && value < 10) { 42 | value = '0' + value 43 | } 44 | return value || 0 45 | }) 46 | return time_str 47 | } 48 | 49 | /** 50 | * @param {number} time 51 | * @param {string} option 52 | * @returns {string} 53 | */ 54 | export function formatTime(time, option) { 55 | if (('' + time).length === 10) { 56 | time = parseInt(time) * 1000 57 | } else { 58 | time = +time 59 | } 60 | const d = new Date(time) 61 | const now = Date.now() 62 | 63 | const diff = (now - d) / 1000 64 | 65 | if (diff < 30) { 66 | return '刚刚' 67 | } else if (diff < 3600) { 68 | // less 1 hour 69 | return Math.ceil(diff / 60) + '分钟前' 70 | } else if (diff < 3600 * 24) { 71 | return Math.ceil(diff / 3600) + '小时前' 72 | } else if (diff < 3600 * 24 * 2) { 73 | return '1天前' 74 | } 75 | if (option) { 76 | return parseTime(time, option) 77 | } else { 78 | return ( 79 | d.getMonth() + 80 | 1 + 81 | '月' + 82 | d.getDate() + 83 | '日' + 84 | d.getHours() + 85 | '时' + 86 | d.getMinutes() + 87 | '分' 88 | ) 89 | } 90 | } 91 | 92 | /** 93 | * @param {string} url 94 | * @returns {Object} 95 | */ 96 | export function param2Obj(url) { 97 | const search = url.split('?')[1] 98 | if (!search) { 99 | return {} 100 | } 101 | return JSON.parse( 102 | '{"' + 103 | decodeURIComponent(search) 104 | .replace(/"/g, '\\"') 105 | .replace(/&/g, '","') 106 | .replace(/=/g, '":"') 107 | .replace(/\+/g, ' ') + 108 | '"}' 109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | 96 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/service/impl/ConnManager.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service.impl; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | import javax.annotation.Resource; 9 | 10 | import com.alibaba.druid.pool.DruidDataSource; 11 | import com.autohome.dbtree.contract.DbInfo; 12 | import com.autohome.dbtree.contract.DbServer; 13 | 14 | public class ConnManager { 15 | 16 | private ConcurrentHashMap dataSourceMap = new ConcurrentHashMap<>(); 17 | 18 | @Resource 19 | private Map dbInfoMap; 20 | 21 | @Resource 22 | private Map dbServerMap; 23 | 24 | public Connection fetchConnection(String dbName) throws SQLException { 25 | if (dataSourceMap.containsKey(dbName)) { 26 | return dataSourceMap.get(dbName).getConnection(); 27 | } 28 | if (!dbInfoMap.containsKey(dbName)) { 29 | throw new RuntimeException("unknown db: " + dbName); 30 | } 31 | 32 | initDataSource(dbName); 33 | 34 | return dataSourceMap.get(dbName).getConnection(); 35 | } 36 | 37 | public void close() { 38 | for (Map.Entry entry : dataSourceMap.entrySet()) { 39 | entry.getValue().close(); 40 | } 41 | } 42 | 43 | private synchronized void initDataSource(String dbName) { 44 | DbInfo dbInfo = dbInfoMap.get(dbName); 45 | DbServer dbServer = dbServerMap.get(dbInfo.getDb_server()); 46 | DruidDataSource druidDataSource = new DruidDataSource(); 47 | String dataSourceUrl = null; 48 | if (dbServer.getDb_type().equalsIgnoreCase("mysql")) { 49 | druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); 50 | dataSourceUrl = String.format("jdbc:mysql://%s:%s/%s?useSSL=false&verifyServerCertificate=false", dbServer.getHost(), dbServer.getPort(), dbInfo.getDb_name()); 51 | } else if(dbServer.getDb_type().equalsIgnoreCase("sqlserver")) { 52 | druidDataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 53 | dataSourceUrl = String.format("jdbc:sqlserver://%s:%s;database=%s", dbServer.getHost(), dbServer.getPort(), dbName); 54 | } else if(dbServer.getDb_type().equalsIgnoreCase("oracle")) { 55 | druidDataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver"); 56 | } else { 57 | throw new IllegalArgumentException("unknown db_type: " + dbServer.getDb_type()); 58 | } 59 | 60 | druidDataSource.setUrl(dataSourceUrl); 61 | druidDataSource.setUsername(dbServer.getUser()); 62 | druidDataSource.setPassword(dbServer.getPassword()); 63 | druidDataSource.setInitialSize(1); 64 | druidDataSource.setMinIdle(0); 65 | druidDataSource.setMaxActive(2); 66 | druidDataSource.setMaxWait(600000); 67 | druidDataSource.setValidationQuery("SELECT 1"); 68 | 69 | dataSourceMap.put(dbName, druidDataSource); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /dbtree-vue/tests/unit/components/Breadcrumb.spec.js: -------------------------------------------------------------------------------- 1 | import { mount, createLocalVue } from '@vue/test-utils' 2 | import VueRouter from 'vue-router' 3 | import ElementUI from 'element-ui' 4 | import Breadcrumb from '@/components/Breadcrumb/index.vue' 5 | 6 | const localVue = createLocalVue() 7 | localVue.use(VueRouter) 8 | localVue.use(ElementUI) 9 | 10 | const routes = [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | children: [{ 15 | path: 'dashboard', 16 | name: 'dashboard' 17 | }] 18 | }, 19 | { 20 | path: '/menu', 21 | name: 'menu', 22 | children: [{ 23 | path: 'menu1', 24 | name: 'menu1', 25 | meta: { title: 'menu1' }, 26 | children: [{ 27 | path: 'menu1-1', 28 | name: 'menu1-1', 29 | meta: { title: 'menu1-1' } 30 | }, 31 | { 32 | path: 'menu1-2', 33 | name: 'menu1-2', 34 | redirect: 'noredirect', 35 | meta: { title: 'menu1-2' }, 36 | children: [{ 37 | path: 'menu1-2-1', 38 | name: 'menu1-2-1', 39 | meta: { title: 'menu1-2-1' } 40 | }, 41 | { 42 | path: 'menu1-2-2', 43 | name: 'menu1-2-2' 44 | }] 45 | }] 46 | }] 47 | }] 48 | 49 | const router = new VueRouter({ 50 | routes 51 | }) 52 | 53 | describe('Breadcrumb.vue', () => { 54 | const wrapper = mount(Breadcrumb, { 55 | localVue, 56 | router 57 | }) 58 | it('dashboard', () => { 59 | router.push('/dashboard') 60 | const len = wrapper.findAll('.el-breadcrumb__inner').length 61 | expect(len).toBe(1) 62 | }) 63 | it('normal route', () => { 64 | router.push('/menu/menu1') 65 | const len = wrapper.findAll('.el-breadcrumb__inner').length 66 | expect(len).toBe(2) 67 | }) 68 | it('nested route', () => { 69 | router.push('/menu/menu1/menu1-2/menu1-2-1') 70 | const len = wrapper.findAll('.el-breadcrumb__inner').length 71 | expect(len).toBe(4) 72 | }) 73 | it('no meta.title', () => { 74 | router.push('/menu/menu1/menu1-2/menu1-2-2') 75 | const len = wrapper.findAll('.el-breadcrumb__inner').length 76 | expect(len).toBe(3) 77 | }) 78 | // it('click link', () => { 79 | // router.push('/menu/menu1/menu1-2/menu1-2-2') 80 | // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 81 | // const second = breadcrumbArray.at(1) 82 | // console.log(breadcrumbArray) 83 | // const href = second.find('a').attributes().href 84 | // expect(href).toBe('#/menu/menu1') 85 | // }) 86 | // it('noRedirect', () => { 87 | // router.push('/menu/menu1/menu1-2/menu1-2-1') 88 | // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 89 | // const redirectBreadcrumb = breadcrumbArray.at(2) 90 | // expect(redirectBreadcrumb.contains('a')).toBe(false) 91 | // }) 92 | it('last breadcrumb', () => { 93 | router.push('/menu/menu1/menu1-2/menu1-2-1') 94 | const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner') 95 | const redirectBreadcrumb = breadcrumbArray.at(3) 96 | expect(redirectBreadcrumb.contains('a')).toBe(false) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /dbtree-vue/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | dbtree 7 | com.autohome 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | dbtree-vue 13 | 14 | 15 | 16 | 17 | org.codehaus.mojo 18 | exec-maven-plugin 19 | 1.6.0 20 | 21 | src 22 | 23 | 24 | 25 | exec-npm-install 26 | prepare-package 27 | 28 | exec 29 | 30 | 31 | npm 32 | 33 | install 34 | 35 | ${basedir} 36 | 37 | 38 | 39 | exec-npm-run-build 40 | prepare-package 41 | 42 | exec 43 | 44 | 45 | npm 46 | 47 | run 48 | build:prod 49 | 50 | ${basedir} 51 | 52 | 53 | 54 | 55 | 56 | 57 | org.apache.maven.plugins 58 | maven-resources-plugin 59 | 60 | 61 | copy Vue.js frontend content 62 | package 63 | 64 | copy-resources 65 | 66 | 67 | ${project.parent.basedir}/dbtree-backend/src/main/resources/public 68 | true 69 | 70 | 71 | ${project.parent.basedir}/dbtree-vue/target/dist 72 | 73 | static/ 74 | index.html 75 | favicon.ico 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /dbtree-backend/src/test/java/com/autohome/dbtree/service/impl/TableExportServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.service.impl; 2 | 3 | import com.autohome.dbtree.contract.ColumnInfo; 4 | import com.autohome.dbtree.contract.TableInfo; 5 | import com.autohome.dbtree.service.ITreeService; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.Mockito; 11 | import org.mockito.junit.MockitoJUnitRunner; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.junit.Assert.*; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class TableExportServiceTest { 21 | 22 | @Mock 23 | private ITreeService treeService; 24 | 25 | @InjectMocks 26 | private TableExportService tableExportService; 27 | 28 | @Test 29 | public void exportMarkdownTest() { 30 | List columnInfoList1 = new ArrayList<>(); 31 | ColumnInfo columnInfo11 = new ColumnInfo(); 32 | columnInfo11.setColumnName("id"); 33 | columnInfo11.setColumnType("int(11)"); 34 | columnInfo11.setAutoIncrement(1); 35 | columnInfo11.setNullable("NO"); 36 | columnInfo11.setPrimaryKey(1); 37 | columnInfo11.setColumnDefault(""); 38 | columnInfo11.setColumnComment("自增id"); 39 | columnInfoList1.add(columnInfo11); 40 | 41 | ColumnInfo columnInfo12 = new ColumnInfo(); 42 | columnInfo12.setColumnName("name"); 43 | columnInfo12.setColumnType("varchar(100)"); 44 | columnInfo12.setAutoIncrement(0); 45 | columnInfo12.setNullable("NO"); 46 | columnInfo12.setPrimaryKey(0); 47 | columnInfo12.setColumnDefault(""); 48 | columnInfo12.setColumnComment("名称"); 49 | columnInfoList1.add(columnInfo12); 50 | 51 | Mockito.doReturn(columnInfoList1).when(treeService).columnList(Mockito.anyString(), Mockito.anyString()); 52 | TableInfo tableInfo1 = new TableInfo(); 53 | tableInfo1.setTableName("table"); 54 | tableInfo1.setDescription("注释1"); 55 | Mockito.doReturn(tableInfo1).when(treeService).oneTable("db", "table"); 56 | 57 | TableInfo tableInfo2 = new TableInfo(); 58 | tableInfo2.setTableName("table2"); 59 | tableInfo2.setDescription("注释2"); 60 | Mockito.doReturn(tableInfo2).when(treeService).oneTable("db", "table2"); 61 | 62 | String result = tableExportService.exportMarkdown("db", Arrays.asList("table", "table2")); 63 | 64 | assertEquals("\n" + 65 | "\n" + 66 | "## table: 注释1\n" + 67 | "序号|字段名|类型|自增|可空|主键|默认值|注释\n" + 68 | "---|---|---|---|---|---|---|---\n" + 69 | "0|id|int(11)|1|NO|1||自增id\n" + 70 | "1|name|varchar(100)|0|NO|0||名称\n" + 71 | "\n" + 72 | "## table2: 注释2\n" + 73 | "序号|字段名|类型|自增|可空|主键|默认值|注释\n" + 74 | "---|---|---|---|---|---|---|---\n" + 75 | "0|id|int(11)|1|NO|1||自增id\n" + 76 | "1|name|varchar(100)|0|NO|0||名称", result); 77 | } 78 | } -------------------------------------------------------------------------------- /dbtree-vue/README.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | 3 | English | [简体中文](./README-zh.md) 4 | 5 | > A minimal vue admin template with Element UI & axios & iconfont & permission control & lint 6 | 7 | **Live demo:** http://panjiachen.github.io/vue-admin-template 8 | 9 | 10 | **The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`** 11 | 12 | ## Build Setup 13 | 14 | 15 | ```bash 16 | # clone the project 17 | git clone https://github.com/PanJiaChen/vue-admin-template.git 18 | 19 | # enter the project directory 20 | cd vue-admin-template 21 | 22 | # install dependency 23 | npm install 24 | 25 | # develop 26 | npm run dev 27 | ``` 28 | 29 | This will automatically open http://localhost:9528 30 | 31 | ## Build 32 | 33 | ```bash 34 | # build for test environment 35 | npm run build:stage 36 | 37 | # build for production environment 38 | npm run build:prod 39 | ``` 40 | 41 | ## Advanced 42 | 43 | ```bash 44 | # preview the release environment effect 45 | npm run preview 46 | 47 | # preview the release environment effect + static resource analysis 48 | npm run preview -- --report 49 | 50 | # code format check 51 | npm run lint 52 | 53 | # code format check and auto fix 54 | npm run lint -- --fix 55 | ``` 56 | 57 | Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information 58 | 59 | ## Demo 60 | 61 |  62 | 63 | ## Extra 64 | 65 | If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 66 | 67 | For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour)) 68 | 69 | ## Related Project 70 | 71 | [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 72 | 73 | [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 74 | 75 | [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 76 | 77 | ## Browsers support 78 | 79 | Modern browsers and Internet Explorer 10+. 80 | 81 | | [](http://godban.github.io/browsers-support-badges/)IE / Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari | 82 | | --------- | --------- | --------- | --------- | 83 | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions 84 | 85 | ## License 86 | 87 | [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. 88 | 89 | Copyright (c) 2017-present PanJiaChen 90 | -------------------------------------------------------------------------------- /dbtree-vue/README-zh.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | 3 | > 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 4 | 5 | [线上地址](http://panjiachen.github.io/vue-admin-template) 6 | 7 | [国内访问](https://panjiachen.gitee.io/vue-admin-template) 8 | 9 | 目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。 10 | 11 | ## Extra 12 | 13 | 如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 14 | 15 | ## 相关项目 16 | 17 | [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 18 | 19 | [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 20 | 21 | [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 22 | 23 | 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: 24 | 25 | - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) 26 | - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) 27 | - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) 28 | - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) 29 | - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) 30 | 31 | ## Build Setup 32 | 33 | ```bash 34 | # 克隆项目 35 | git clone https://github.com/PanJiaChen/vue-admin-template.git 36 | 37 | # 进入项目目录 38 | cd vue-admin-template 39 | 40 | # 安装依赖 41 | npm install 42 | 43 | # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 44 | npm install --registry=https://registry.npm.taobao.org 45 | 46 | # 启动服务 47 | npm run dev 48 | ``` 49 | 50 | 浏览器访问 [http://localhost:9528](http://localhost:9528) 51 | 52 | ## 发布 53 | 54 | ```bash 55 | # 构建测试环境 56 | npm run build:stage 57 | 58 | # 构建生产环境 59 | npm run build:prod 60 | ``` 61 | 62 | ## 其它 63 | 64 | ```bash 65 | # 预览发布环境效果 66 | npm run preview 67 | 68 | # 预览发布环境效果 + 静态资源分析 69 | npm run preview -- --report 70 | 71 | # 代码格式检查 72 | npm run lint 73 | 74 | # 代码格式检查并自动修复 75 | npm run lint -- --fix 76 | ``` 77 | 78 | 更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/) 79 | 80 | ## Demo 81 | 82 |  83 | 84 | ## Browsers support 85 | 86 | Modern browsers and Internet Explorer 10+. 87 | 88 | | [](http://godban.github.io/browsers-support-badges/)IE / Edge | [](http://godban.github.io/browsers-support-badges/)Firefox | [](http://godban.github.io/browsers-support-badges/)Chrome | [](http://godban.github.io/browsers-support-badges/)Safari | 89 | | --------- | --------- | --------- | --------- | 90 | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions 91 | 92 | ## License 93 | 94 | [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. 95 | 96 | Copyright (c) 2017-present PanJiaChen 97 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/contract/ColumnInfo.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.contract; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | 6 | @ApiModel 7 | public class ColumnInfo { 8 | 9 | @ApiModelProperty(value = "列名") 10 | private String columnName; 11 | 12 | @ApiModelProperty(value = "列数据类型,如: varchar") 13 | private String dataType; 14 | 15 | private String maxLength; 16 | 17 | @ApiModelProperty(value = "列注释") 18 | private String columnComment; 19 | 20 | @ApiModelProperty(value = "列默认值") 21 | private String columnDefault; 22 | 23 | @ApiModelProperty(value = "列是否可空") 24 | private String nullable; 25 | 26 | @ApiModelProperty(value = "是否主键") 27 | private Integer primaryKey; 28 | 29 | @ApiModelProperty(value = "额外说明") 30 | private String extra; 31 | 32 | @ApiModelProperty(value = "列类型。如: varchar(200)") 33 | private String columnType; 34 | 35 | @ApiModelProperty(value = "是否自增") 36 | private Integer autoIncrement; 37 | 38 | @ApiModelProperty(value = "字段精度") 39 | private Integer precision; 40 | 41 | @ApiModelProperty(value = "小数位数") 42 | private Integer scale; 43 | 44 | public String getColumnName() { 45 | return columnName; 46 | } 47 | 48 | public void setColumnName(String columnName) { 49 | this.columnName = columnName; 50 | } 51 | 52 | public String getDataType() { 53 | return dataType; 54 | } 55 | 56 | public void setDataType(String dataType) { 57 | this.dataType = dataType; 58 | } 59 | 60 | public String getMaxLength() { 61 | return maxLength; 62 | } 63 | 64 | public void setMaxLength(String maxLength) { 65 | this.maxLength = maxLength; 66 | } 67 | 68 | public String getColumnComment() { 69 | return columnComment; 70 | } 71 | 72 | public void setColumnComment(String columnComment) { 73 | this.columnComment = columnComment; 74 | } 75 | 76 | public String getColumnDefault() { 77 | return columnDefault; 78 | } 79 | 80 | public void setColumnDefault(String columnDefault) { 81 | this.columnDefault = columnDefault; 82 | } 83 | 84 | public String getNullable() { 85 | return nullable; 86 | } 87 | 88 | public void setNullable(String nullable) { 89 | this.nullable = nullable; 90 | } 91 | 92 | public Integer getPrimaryKey() { 93 | return primaryKey; 94 | } 95 | 96 | public void setPrimaryKey(Integer primaryKey) { 97 | this.primaryKey = primaryKey; 98 | } 99 | 100 | public String getExtra() { 101 | return extra; 102 | } 103 | 104 | public void setExtra(String extra) { 105 | this.extra = extra; 106 | } 107 | 108 | public String getColumnType() { 109 | return columnType; 110 | } 111 | 112 | public void setColumnType(String columnType) { 113 | this.columnType = columnType; 114 | } 115 | 116 | public Integer getAutoIncrement() { 117 | return autoIncrement; 118 | } 119 | 120 | public void setAutoIncrement(Integer autoIncrement) { 121 | this.autoIncrement = autoIncrement; 122 | } 123 | 124 | public Integer getPrecision() { 125 | return precision; 126 | } 127 | 128 | public void setPrecision(Integer precision) { 129 | this.precision = precision; 130 | } 131 | 132 | public Integer getScale() { 133 | return scale; 134 | } 135 | 136 | public void setScale(Integer scale) { 137 | this.scale = scale; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /dbtree-vue/src/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Home 17 | 18 | 19 | 20 | Github 21 | 22 | 23 | Docs 24 | 25 | 26 | Log Out 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 61 | 62 | 140 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/controller/CodeController.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.controller; 2 | 3 | import com.autohome.dbtree.config.MybatisCodeGeneratorConfig; 4 | import com.autohome.dbtree.contract.Protocol; 5 | import com.autohome.dbtree.service.IMybatisCodeGenerateService; 6 | import com.autohome.dbtree.service.ITableExportService; 7 | import com.google.common.base.Splitter; 8 | import com.google.common.base.Strings; 9 | import org.springframework.core.io.ByteArrayResource; 10 | import org.springframework.http.HttpHeaders; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | import javax.annotation.Resource; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.charset.StandardCharsets; 23 | import java.nio.file.Files; 24 | import java.nio.file.Path; 25 | import java.nio.file.Paths; 26 | import java.util.List; 27 | 28 | @Controller 29 | @RequestMapping("/code") 30 | public class CodeController { 31 | 32 | @Resource 33 | private MybatisCodeGeneratorConfig mybatisCodeGeneratorConfig; 34 | 35 | @Resource 36 | private IMybatisCodeGenerateService myBatisCodeGenerateService; 37 | 38 | @Resource 39 | private ITableExportService tableExportService; 40 | 41 | @RequestMapping(value = "/mybatisDownload", method = RequestMethod.GET) 42 | public ResponseEntity mybatisDownload(@RequestParam(value = "zipFile", required = true) String zipFile) throws IOException { 43 | File file = new File(mybatisCodeGeneratorConfig.getMybatisBaseFolder() + File.separator + zipFile); 44 | Path path = Paths.get(file.getAbsolutePath()); 45 | ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path)); 46 | HttpHeaders headers = new HttpHeaders(); 47 | headers.add("Content-Disposition", "attachment; filename=" + zipFile); 48 | return ResponseEntity.ok() 49 | .headers(headers) 50 | .contentLength(file.length()) 51 | .contentType(MediaType.parseMediaType("application/octet-stream")) 52 | .body(resource); 53 | } 54 | 55 | @RequestMapping(value = "/exportMarkdown", method = RequestMethod.GET) 56 | public ResponseEntity exportMarkdown(@RequestParam String dbName, @RequestParam String tables) { 57 | String content = tableExportService.exportMarkdown(dbName, Splitter.on(',').splitToList(tables)); 58 | ByteArrayResource resource = new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)); 59 | HttpHeaders headers = new HttpHeaders(); 60 | headers.add("Content-Disposition", "attachment; filename=" + dbName + ".md"); 61 | return ResponseEntity.ok() 62 | .headers(headers) 63 | .contentLength(resource.contentLength()) 64 | .contentType(MediaType.parseMediaType("application/octet-stream")) 65 | .body(resource); 66 | } 67 | 68 | @ResponseBody 69 | @RequestMapping(value = "/generate", method = RequestMethod.GET) 70 | public Protocol generate(String dbName, String domainPackage, String mapperPackage, Boolean useActualColumnNames, String tables) { 71 | List tableList = Splitter.on(",").splitToList(tables); 72 | String fileName = myBatisCodeGenerateService.execute(domainPackage, mapperPackage, dbName, tableList, useActualColumnNames); 73 | if(Strings.isNullOrEmpty(fileName)) { 74 | return new Protocol<>(501, "代码生成失败"); 75 | } 76 | return new Protocol<>(fileName); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README-EN.md: -------------------------------------------------------------------------------- 1 | ## DBTree Introduction 2 | 3 | English | [简体中文](./README.md) 4 | 5 | DBTree is a light web tool for browsing database table definitions. Compared to phpMyAdmin, DBTree only 6 | focus on browsing table information and maintaining comment. You can use it to classify your tables as folders and 7 | update comment handily. 8 | 9 | ## Features 10 | 11 | * organize tables as folders 12 | * browse important schema information 13 | * show only one table delegate if the table is split into hundreds or thousands. 14 | * update table comment, column comment online handily. 15 | * support mysql, sql server 16 | * export table schema as markdown 17 | 18 | ## Demo 19 | 20 | 21 | 22 | ## Why this tool 23 | 24 | In daily development, database communication is one of the most important and frequent part, our team found that no matter database 25 | client like MSS Management Studio, phpMyAdmin or database design software like Power Designer, it is not convenient to browse or share 26 | table definition and comment. Especially when there are thousands of sub-tables, it is really tough. So we design this tool and we 27 | love this tool, hope it would help you too. 28 | 29 | comment friendly and classify your table as folders is good for all. 30 | 31 | ## config 32 | 33 | * database server config 34 | 35 | dbtree-backend/src/main/resources/dbconfig/db-server.json 36 | ```json 37 | { 38 | "mysql-127.0.0.1": { 39 | "db_type": "mysql", 40 | "host": "127.0.0.1", 41 | "port": 3306, 42 | "user": "root", 43 | "password": "123456" 44 | }, 45 | "sqlserver-127.0.0.2": { 46 | "db_type": "sqlserver", 47 | "host": "127.0.0.2", 48 | "port": 1433, 49 | "user": "root", 50 | "password":"123456" 51 | } 52 | } 53 | ``` 54 | the password is plain. 55 | 56 | * database config 57 | 58 | dbtree-backend/src/main/resources/dbconfig/db-config.json 59 | ```json 60 | { 61 | "db_1": { 62 | "db_name": "db_1", 63 | "db_server": "mysql-127.0.0.1", 64 | "split_table_rules": [ 65 | { 66 | "delegate_table": "rule", 67 | "table_pattern": "rule_%" 68 | } 69 | ] 70 | }, 71 | "db_2": { 72 | "db_name": "db_2", 73 | "db_server": "sqlserver-127.0.0.2" 74 | } 75 | } 76 | ``` 77 | db-config.json is a json map content, the key must be as same as database name(db_name). You can use split_table_rules to specify the 78 | table split pattern, the example means using rule to represent all rule_% tables. 79 | 80 | * mybatis.base.folder 81 | 82 | the store folder for mybatis generated code file 83 | 84 | * mybatis.mysql.connector 85 | 86 | the location of mysql jdbc jar. you can copy dbtree/connector/mysql-connector-java-5.1.36.jar to your own folder. 87 | 88 | * mybatis.sqlserver.connector 89 | 90 | the location of sqlserver jdbc jar. you can copy dbtree/connector/sqljdbc42.jar to your own folder 91 | 92 | ## Debug 93 | 94 | Requirements 95 | * jdk 1.8+ 96 | * node.js 97 | 98 | open and run the springboot project,visit:http://localhost:8080 99 | 100 | for better debug experience, you should use VS Code to open the dbtree-vue folder. execute the following command: 101 | 102 | ```bash 103 | # install dependency 104 | npm install 105 | 106 | # develop 107 | npm run dev 108 | ``` 109 | 110 | This will automatically open http://localhost:9528 111 | 112 | ## Deploy 113 | 114 | This project use springboot2.x + vue-element-template + mybatis。dbtree-vue resources will be package into dbtree-backend/src/main/resources/public. 115 | You need only deploy dbtree-backend/target/dbtree.jar. 116 | 117 | mysql schema folder:dbtree-backend/doc/schema 118 | 119 | JDK version 1.8 120 | 121 | The tool only deploy in develop environment, you don't need to deploy it in production, it is not safe. 122 | 123 | ## Thanks To 124 | 125 | - [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 126 | 127 | 128 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/java/com/autohome/dbtree/util/JdbcUtil.java: -------------------------------------------------------------------------------- 1 | package com.autohome.dbtree.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.sql.Connection; 7 | import java.sql.PreparedStatement; 8 | import java.sql.ResultSet; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.function.Consumer; 12 | import java.util.function.Function; 13 | 14 | public class JdbcUtil { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(JdbcUtil.class); 17 | 18 | public static T executeQuery(Connection connection, String dbName, String sql, Function resultSetFunction, Consumer preparedStatementFunction) { 19 | PreparedStatement preparedStatement = null; 20 | ResultSet resultSet = null; 21 | try { 22 | preparedStatement = connection.prepareStatement(sql); 23 | if (preparedStatementFunction != null) { 24 | preparedStatementFunction.accept(preparedStatement); 25 | } 26 | resultSet = preparedStatement.executeQuery(); 27 | return resultSetFunction.apply(resultSet); 28 | } catch (Exception ex) { 29 | throw new RuntimeException(ex); 30 | } finally { 31 | try { 32 | if (resultSet != null) { 33 | resultSet.close(); 34 | } 35 | } catch (Exception ex) { 36 | LOGGER.error("error when close resultSet", ex); 37 | } 38 | 39 | try { 40 | if (preparedStatement != null) { 41 | preparedStatement.close(); 42 | } 43 | } catch (Exception ex) { 44 | LOGGER.error("error when close statement", ex); 45 | } 46 | 47 | try { 48 | if (connection != null) { 49 | connection.close(); 50 | } 51 | } catch (Exception ex) { 52 | LOGGER.error("error when close connection", ex); 53 | } 54 | } 55 | } 56 | 57 | public static boolean execute(Connection connection, String dbName, String sql, Consumer preparedStatementFunction) { 58 | PreparedStatement preparedStatement = null; 59 | try { 60 | preparedStatement = connection.prepareStatement(sql); 61 | if (preparedStatementFunction != null) { 62 | preparedStatementFunction.accept(preparedStatement); 63 | } 64 | preparedStatement.execute(); 65 | return true; 66 | } catch (Exception ex) { 67 | throw new RuntimeException(ex); 68 | } finally { 69 | try { 70 | if (preparedStatement != null) { 71 | preparedStatement.close(); 72 | } 73 | } catch (Exception ex) { 74 | LOGGER.error("error when close statement", ex); 75 | } 76 | 77 | try { 78 | if (connection != null) { 79 | connection.close(); 80 | } 81 | } catch (Exception ex) { 82 | LOGGER.error("error when close connection", ex); 83 | } 84 | } 85 | } 86 | 87 | 88 | public static T executeQuery(Connection connection, String dbName, String sql, Function resultSetFunction) { 89 | return executeQuery(connection, dbName, sql, resultSetFunction, null); 90 | } 91 | 92 | public static void appendInClause(StringBuilder sqlBuilder, int size) { 93 | sqlBuilder.append(" ("); 94 | List placeHolderList = new ArrayList<>(size); 95 | for (int i = 0; i < size; i++) { 96 | placeHolderList.add("?"); 97 | } 98 | sqlBuilder.append(String.join(",", placeHolderList)); 99 | sqlBuilder.append(")"); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /dbtree-vue/src/api/dbtree.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const dbtree = { 4 | /** 5 | * 查询目录下的表 6 | * @param {*} folderId 目录ID 7 | */ 8 | tableList ( folderId ) { 9 | return request({ 10 | url: '/tree/tableList', 11 | method: 'get', 12 | params: { folderId: folderId } 13 | }) 14 | }, 15 | 16 | /** 17 | * 查询目录下所有节点 18 | * @param {*} folderId 目录ID 19 | */ 20 | children (folderId) { 21 | return request({ 22 | url: '/tree/children', 23 | method: 'get', 24 | params: { folderId: folderId } 25 | }) 26 | }, 27 | 28 | /** 29 | * 创建新目录 30 | * @param {*} parentId 父亲节点ID 31 | * @param {*} folderName 目录名 32 | */ 33 | addFolder (parentId, folderName) { 34 | return request({ 35 | url: '/tree/addFolder', 36 | method: 'post', 37 | params: { 38 | parentId: parentId, 39 | folderName: folderName 40 | } 41 | }) 42 | }, 43 | 44 | /** 45 | * 重新命名目录 46 | * @param {*} folderId 目录ID 47 | * @param {*} newName 新名字 48 | */ 49 | renameFolder (folderId, newName) { 50 | return request({ 51 | url: '/tree/renameFolder', 52 | method: 'post', 53 | params: { folderId: folderId, newName: newName } 54 | }) 55 | }, 56 | 57 | 58 | 59 | /** 60 | * 数据库列表 61 | */ 62 | databases() { 63 | return request({ 64 | url: '/tree/databases', 65 | method: 'get' 66 | }) 67 | }, 68 | 69 | /** 70 | * 删除目录 71 | * @param {*} folderId 待删除目录ID 72 | */ 73 | deleteFolder(folderId) { 74 | return request({ 75 | url: '/tree/deleteFolder', 76 | method: 'post', 77 | params: { 78 | folderId: folderId 79 | } 80 | }) 81 | }, 82 | 83 | /** 84 | * 表详细信息 85 | * @param {*} tableId 表节点ID 86 | */ 87 | tableDetail(tableId) { 88 | return request({ 89 | url: '/tree/tableDetail', 90 | method: 'get', 91 | params: { 92 | tableId: tableId 93 | } 94 | }) 95 | }, 96 | 97 | /** 98 | * 移动节点到某目录下 99 | * @param {*} nodeId 当前节点ID 100 | * @param {*} parentId 目录目录ID 101 | */ 102 | moveTo(nodeIdList, parentId) { 103 | return request({ 104 | url: '/tree/moveTo', 105 | method: 'post', 106 | params: { 107 | nodeIdList: nodeIdList.join(','), 108 | parentId: parentId 109 | } 110 | }); 111 | }, 112 | 113 | /** 114 | * 获取目录综合分类信息 115 | * @param {*} folderId 目录ID 116 | */ 117 | folderClassifyInfo(folderId) { 118 | return request({ 119 | url: '/tree/folderClassifyInfo', 120 | method: 'get', 121 | params: { 122 | folderId: folderId 123 | } 124 | }); 125 | }, 126 | 127 | /** 128 | * 更新列注释 129 | * @param {*} nodeId 表节点ID 130 | * @param {*} columnName 列名 131 | * @param {*} newComment 新注释 132 | */ 133 | updateColumnComment(nodeId, columnName, newComment) { 134 | return request({ 135 | url: '/tree/updateColumnComment', 136 | method: 'post', 137 | params: { 138 | nodeId: nodeId, 139 | columnName: columnName, 140 | newComment: newComment 141 | } 142 | }); 143 | }, 144 | 145 | /** 146 | * 修改表注释 147 | * @param {*} dbName 数据库 148 | * @param {*} tableName 表名 149 | * @param {*} newComment 新注释 150 | */ 151 | updateTableComment(dbName, tableName, newComment) { 152 | return request({ 153 | url: '/tree/updateTableComment', 154 | method: 'post', 155 | params: { 156 | dbName: dbName, 157 | tableName: tableName, 158 | newComment: newComment 159 | } 160 | }); 161 | } 162 | } 163 | 164 | export default dbtree -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DBTree简介 2 | 3 | 简体中文 | [English](./README-EN.md) 4 | 5 | DBTree是一个WEB版的轻量数据库表结构查看及管理工具,相比phpMyAdmin, DBTree只专注于方便开发查看表结构信息和 6 | 注释维护。通过树形展示库表结构,可以对表进行自定义归类,在线更新注释。你还可以使用它来部分替代mybatis generator的代码生成 7 | 功能。 8 | 9 | ## Features 10 | 11 | * 一键生成下载mybatis资源 12 | * 对表进行目录分类 13 | * 展示表结构信息 14 | * 分表只展示其中一个表结构 15 | * 修改表注释,字段注释 16 | * 支持mysql, sqlserver 17 | * 表结构导出markdown文档 18 | * 网页方式管理,方便共享 19 | 20 | ## 功能截图 21 | 22 | * 树形展示表信息,可编辑注释 23 | 24 | 25 | 26 | * 选择目录,勾选表,生成mybatis资源 27 | 28 | 29 | 30 | * 添加分类目录,对表进行归档 31 | 32 | 33 | 34 | 35 | 36 | ## 功能演示 37 | 38 | 39 | 40 | ## 为何写这个小工具 41 | 42 | 在不断的迭代过程中,数据库是开发之间沟通非常频繁且重要的一个环节,我们发现无论是mss management studio,phpMyAdmin这类数据库客户端还是 43 | 类似Power Designer的数据库设计软件对表信息共享,注释维护这两个需求来说用起来都非常不便利,尤其是有上千个分表的时候,客户端用起来还是非常难受的。 44 | 所以写了这个网页小工具,希望对有同样需求的人有所帮助。 45 | 46 | 还有一个很酷的功能,就是你可以选择表,然后一键生成下载mybatis资源文件的功能,非常方便,不用配置mybatis generator配置文件。 47 | 48 | ## 配置修改 49 | 50 | * 服务器配置 51 | 52 | dbtree-backend/src/main/resources/dbconfig/db-server.json 53 | ```json 54 | { 55 | "mysql-127.0.0.1": { 56 | "db_type": "mysql", 57 | "host": "127.0.0.1", 58 | "port": 3306, 59 | "user": "root", 60 | "password": "123456" 61 | }, 62 | "sqlserver-127.0.0.2": { 63 | "db_type": "sqlserver", 64 | "host": "127.0.0.2", 65 | "port": 1433, 66 | "user": "root", 67 | "password":"123456" 68 | } 69 | } 70 | ``` 71 | 密码是明文配置 72 | 73 | * 数据库配置 74 | 75 | dbtree-backend/src/main/resources/dbconfig/db-config.json 76 | ```json 77 | { 78 | "db_1": { 79 | "db_name": "db_1", 80 | "db_server": "mysql-127.0.0.1", 81 | "split_table_rules": [ 82 | { 83 | "delegate_table": "rule", 84 | "table_pattern": "rule_%" 85 | } 86 | ] 87 | }, 88 | "db_2": { 89 | "db_name": "db_2", 90 | "db_server": "sqlserver-127.0.0.2" 91 | } 92 | } 93 | ``` 94 | db-config.json配置文件内容是map结构的json数据,其中key必须和库名(db_name)一致。split_table_rules是指定分表策略,例子的意思是用 rule 代替所有rule_%的表。 95 | 96 | * mybatis.base.folder 97 | 98 | mybatis生成文件存放目录。 99 | 100 | * mybatis.mysql.connector 101 | 102 | mysql jdbc jar包位置, 用于配置mybatis generator core的mysql classpathEntry。你可以直接使用dbtree/connector/mysql-connector-java-5.1.36.jar 103 | 拷贝到你的服务器上 104 | 105 | * mybatis.sqlserver.connector 106 | 107 | sqlserver jdbc jar包位置, 用于配置mybatis generator core的SQL Server classpathEntry。你可以直接使用 dbtree/connector/sqljdbc42.jar拷贝 108 | 到你的服务器上 109 | 110 | ## 表结构语句 111 | 112 | mysql表创建语句见:dbtree-backend/doc/schema目录 113 | 114 | ## 数据库连接配置 115 | 116 | 自行修改配置文件中的数据库服务器地址,和用户名, 密码,密码使用druid默认加密。参考: [druid数据库密码加密](https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter) 117 | 118 | ## 开发调试 119 | 120 | 环境要求 121 | * jdk 1.8+ 122 | * node.js 123 | 124 | 打开项目运行,访问:http://localhost:8080 125 | 126 | 为了更好的开发体验,你应该单独使用VS Code打开dbtree-vue目录,进行调试。执行如下命令: 127 | 128 | ```bash 129 | # install dependency 130 | npm install 131 | 132 | # 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 133 | npm install --registry=https://registry.npm.taobao.org 134 | 135 | # develop 136 | npm run dev 137 | ``` 138 | 139 | 会自动打开: http://localhost:9528 140 | 141 | ## 部署 142 | 143 | JDK使用1.8 144 | 145 | 项目使用 springboot2.x + vue-element-template + mybatis。dbtree-vue资源会打包进dbtree-backend/src/main/resources/public下。直接部署 146 | dbtree-backend/target/dbtree-{version}.jar就可以了。 147 | 148 | 打包后会自动生成dbtree-backend/target/dbtree-{version}.zip文件,将包上传到服务器上,解压后,已经写好启动脚本,端口改为自己的即可。 149 | 150 | ```bash 151 | # start 152 | scripts/startup.sh 153 | 154 | # stop 155 | scripts/shutdown.sh 156 | 157 | ``` 158 | 159 | 这个工具主要给开发人员使用,部署到测试就可以了,没必要部署到线上,也不安全。 160 | 161 | ## 后续规划 162 | 163 | * 增加服务器配置页面,数据库配置页面,动态增加数据库,不用修改配置重新部署 164 | * 表列表在表名增加链接,点击可以到表详细页 165 | * 表详细页面,增加表注释修改功能 166 | * 数据库展开,获取下面两级数据,解决只获取一级数据,搜索功能显得鸡肋的问题 167 | 168 | ## 主要技术 169 | 170 | * Element tree组件 171 | * mybatis generator core 172 | * springboot2.x 173 | * vue 2.x 174 | 175 | ## 致谢 176 | 177 | - [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template) 178 | - [Element tree](https://element.eleme.io/#/zh-CN/component/tree) 179 | 180 | 181 | -------------------------------------------------------------------------------- /dbtree-backend/src/main/scripts/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SERVICE_NAME=dbtree 3 | ## Adjust server port if necessary 4 | SERVER_PORT=8080 5 | PROFILE=dev 6 | ## Adjust log dir if necessary 7 | LOG_DIR=/data/dbtree/$SERVER_PORT/logs 8 | 9 | 10 | ## Adjust memory settings if necessary 11 | export JAVA_OPTS="-Xms1024m -Xmx1024m -Xss256k -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m -XX:NewRatio=4 -XX:SurvivorRatio=8" 12 | 13 | ## Only uncomment the following when you are using server jvm 14 | #export JAVA_OPTS="$JAVA_OPTS -server -XX:-ReduceInitialCardMarks" 15 | 16 | export JAVA_OPTS="$JAVA_OPTS -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+UseConcMarkSweepGC -XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+ScavengeBeforeFullGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSPermGenSweepingEnabled -XX:CMSInitiatingPermOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrent -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationConcurrentTime -XX:+PrintHeapAtGC -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Duser.timezone=Asia/Shanghai -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom" 17 | export JAVA_OPTS="$JAVA_OPTS -Dserver.port=$SERVER_PORT -Dspring.profiles.active=$PROFILE -Dlogs.dir=$LOG_DIR -Dlogging.file=$LOG_DIR/$SERVICE_NAME.log -Xloggc:$LOG_DIR/heap_trace.txt -XX:HeapDumpPath=$LOG_DIR/HeapDumpOnOutOfMemoryError/" 18 | 19 | PATH_TO_JAR=$SERVICE_NAME".jar" 20 | SERVER_URL="http://localhost:$SERVER_PORT" 21 | 22 | function checkPidAlive { 23 | for i in `ls -t ${SERVICE_NAME}_data${SERVICE_NAME}${SERVER_PORT}/$SERVICE_NAME*.pid 2>/dev/null` 24 | do 25 | read pid < $i 26 | 27 | result=$(ps -p "$pid") 28 | if [ "$?" -eq 0 ]; then 29 | return 0 30 | else 31 | printf "\npid - $pid just quit unexpectedly, please check logs under $LOG_DIR and /tmp for more information!\n" 32 | exit 1; 33 | fi 34 | done 35 | 36 | printf "\nNo pid file found, startup may failed. Please check logs under $LOG_DIR and /tmp for more information!\n" 37 | exit 1; 38 | } 39 | 40 | if [ "$(uname)" == "Darwin" ]; then 41 | windows="0" 42 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 43 | windows="0" 44 | elif [ "$(expr substr $(uname -s) 1 5)" == "MINGW" ]; then 45 | windows="1" 46 | else 47 | windows="0" 48 | fi 49 | 50 | # for Windows 51 | if [ "$windows" == "1" ] && [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then 52 | tmp_java_home=`cygpath -sw "$JAVA_HOME"` 53 | export JAVA_HOME=`cygpath -u $tmp_java_home` 54 | echo "Windows new JAVA_HOME is: $JAVA_HOME" 55 | fi 56 | 57 | cd `dirname $0`/.. 58 | 59 | for i in `ls $SERVICE_NAME-*.jar 2>/dev/null` 60 | do 61 | if [[ ! $i == *"-sources.jar" ]] 62 | then 63 | PATH_TO_JAR=$i 64 | break 65 | fi 66 | done 67 | 68 | if [[ ! -f PATH_TO_JAR && -d current ]]; then 69 | cd current 70 | for i in `ls $SERVICE_NAME-*.jar 2>/dev/null` 71 | do 72 | if [[ ! $i == *"-sources.jar" ]] 73 | then 74 | PATH_TO_JAR=$i 75 | break 76 | fi 77 | done 78 | fi 79 | 80 | if [[ -f $SERVICE_NAME".jar" ]]; then 81 | rm -rf $SERVICE_NAME".jar" 82 | fi 83 | 84 | printf "$(date) ==== Starting ==== \n" 85 | 86 | ln $PATH_TO_JAR $SERVICE_NAME".jar" 87 | chmod a+x $SERVICE_NAME".jar" 88 | ./$SERVICE_NAME".jar" start 89 | 90 | rc=$?; 91 | 92 | if [[ $rc != 0 ]]; 93 | then 94 | echo "$(date) Failed to start $SERVICE_NAME.jar, return code: $rc" 95 | exit $rc; 96 | fi 97 | 98 | declare -i counter=0 99 | declare -i max_counter=48 # 48*5=240s 100 | declare -i total_time=0 101 | 102 | printf "Waiting for server startup" 103 | until [[ (( counter -ge max_counter )) || "$(curl -X GET --silent --connect-timeout 1 --max-time 2 --head $SERVER_URL | grep "200")" != "" ]]; 104 | do 105 | printf "." 106 | counter+=1 107 | sleep 5 108 | 109 | checkPidAlive 110 | done 111 | 112 | total_time=counter*5 113 | 114 | if [[ (( counter -ge max_counter )) ]]; 115 | then 116 | printf "\n$(date) Server failed to start in $total_time seconds!\n" 117 | exit 1; 118 | fi 119 | 120 | printf "\n$(date) Server started in $total_time seconds!\n" 121 | 122 | exit 0; 123 | -------------------------------------------------------------------------------- /dbtree-vue/src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | 3 | .main-container { 4 | min-height: 100%; 5 | transition: margin-left .28s; 6 | margin-left: $sideBarWidth; 7 | position: relative; 8 | } 9 | 10 | .sidebar-container { 11 | transition: width 0.28s; 12 | width: $sideBarWidth !important; 13 | background-color: $menuBg; 14 | height: 100%; 15 | position: fixed; 16 | font-size: 0px; 17 | top: 0; 18 | bottom: 0; 19 | left: 0; 20 | z-index: 1001; 21 | overflow: hidden; 22 | 23 | // reset element-ui css 24 | .horizontal-collapse-transition { 25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 26 | } 27 | 28 | .scrollbar-wrapper { 29 | overflow-x: hidden !important; 30 | } 31 | 32 | .el-scrollbar__bar.is-vertical { 33 | right: 0px; 34 | } 35 | 36 | .el-scrollbar { 37 | height: 100%; 38 | } 39 | 40 | &.has-logo { 41 | .el-scrollbar { 42 | height: calc(100% - 50px); 43 | } 44 | } 45 | 46 | .is-horizontal { 47 | display: none; 48 | } 49 | 50 | a { 51 | display: inline-block; 52 | width: 100%; 53 | overflow: hidden; 54 | } 55 | 56 | .svg-icon { 57 | margin-right: 16px; 58 | } 59 | 60 | .el-menu { 61 | border: none; 62 | height: 100%; 63 | width: 100% !important; 64 | } 65 | 66 | // menu hover 67 | .submenu-title-noDropdown, 68 | .el-submenu__title { 69 | &:hover { 70 | background-color: $menuHover !important; 71 | } 72 | } 73 | 74 | .is-active>.el-submenu__title { 75 | color: $subMenuActiveText !important; 76 | } 77 | 78 | & .nest-menu .el-submenu>.el-submenu__title, 79 | & .el-submenu .el-menu-item { 80 | min-width: $sideBarWidth !important; 81 | background-color: $subMenuBg !important; 82 | 83 | &:hover { 84 | background-color: $subMenuHover !important; 85 | } 86 | } 87 | } 88 | 89 | .hideSidebar { 90 | .sidebar-container { 91 | width: 54px !important; 92 | } 93 | 94 | .main-container { 95 | margin-left: 54px; 96 | } 97 | 98 | .submenu-title-noDropdown { 99 | padding: 0 !important; 100 | position: relative; 101 | 102 | .el-tooltip { 103 | padding: 0 !important; 104 | 105 | .svg-icon { 106 | margin-left: 20px; 107 | } 108 | } 109 | } 110 | 111 | .el-submenu { 112 | overflow: hidden; 113 | 114 | &>.el-submenu__title { 115 | padding: 0 !important; 116 | 117 | .svg-icon { 118 | margin-left: 20px; 119 | } 120 | 121 | .el-submenu__icon-arrow { 122 | display: none; 123 | } 124 | } 125 | } 126 | 127 | .el-menu--collapse { 128 | .el-submenu { 129 | &>.el-submenu__title { 130 | &>span { 131 | height: 0; 132 | width: 0; 133 | overflow: hidden; 134 | visibility: hidden; 135 | display: inline-block; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | 142 | .el-menu--collapse .el-menu .el-submenu { 143 | min-width: $sideBarWidth !important; 144 | } 145 | 146 | // mobile responsive 147 | .mobile { 148 | .main-container { 149 | margin-left: 0px; 150 | } 151 | 152 | .sidebar-container { 153 | transition: transform .28s; 154 | width: $sideBarWidth !important; 155 | } 156 | 157 | &.hideSidebar { 158 | .sidebar-container { 159 | pointer-events: none; 160 | transition-duration: 0.3s; 161 | transform: translate3d(-$sideBarWidth, 0, 0); 162 | } 163 | } 164 | } 165 | 166 | .withoutAnimation { 167 | 168 | .main-container, 169 | .sidebar-container { 170 | transition: none; 171 | } 172 | } 173 | } 174 | 175 | // when menu collapsed 176 | .el-menu--vertical { 177 | &>.el-menu { 178 | .svg-icon { 179 | margin-right: 16px; 180 | } 181 | } 182 | 183 | .nest-menu .el-submenu>.el-submenu__title, 184 | .el-menu-item { 185 | &:hover { 186 | // you can use $subMenuHover 187 | background-color: $menuHover !important; 188 | } 189 | } 190 | 191 | // the scroll bar appears when the subMenu is too long 192 | >.el-menu--popup { 193 | max-height: 100vh; 194 | overflow-y: auto; 195 | 196 | &::-webkit-scrollbar-track-piece { 197 | background: #d3dce6; 198 | } 199 | 200 | &::-webkit-scrollbar { 201 | width: 6px; 202 | } 203 | 204 | &::-webkit-scrollbar-thumb { 205 | background: #99a9bf; 206 | border-radius: 20px; 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /dbtree-vue/vue.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const defaultSettings = require('./src/settings.js') 4 | 5 | function resolve(dir) { 6 | return path.join(__dirname, dir) 7 | } 8 | 9 | const name = defaultSettings.title || 'vue Admin Template' // page title 10 | 11 | // If your port is set to 80, 12 | // use administrator privileges to execute the command line. 13 | // For example, Mac: sudo npm run 14 | // You can change the port by the following methods: 15 | // port = 9528 npm run dev OR npm run dev --port = 9528 16 | const port = process.env.port || process.env.npm_config_port || 9528 // dev port 17 | 18 | // All configuration item explanations can be find in https://cli.vuejs.org/config/ 19 | module.exports = { 20 | /** 21 | * You will need to set publicPath if you plan to deploy your site under a sub path, 22 | * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/, 23 | * then publicPath should be set to "/bar/". 24 | * In most cases please use '/' !!! 25 | * Detail: https://cli.vuejs.org/config/#publicpath 26 | */ 27 | publicPath: '/', 28 | outputDir: 'target/dist', 29 | assetsDir: 'static', 30 | lintOnSave: process.env.NODE_ENV === 'development', 31 | productionSourceMap: false, 32 | devServer: { 33 | port: port, 34 | open: true, 35 | overlay: { 36 | warnings: false, 37 | errors: true 38 | }, 39 | proxy: { 40 | [process.env.VUE_APP_BASE_API +'/tree']: { 41 | //target: `http://127.0.0.1:${port}/mock`, 42 | target: `http://localhost:8080`, 43 | changeOrigin: true, 44 | pathRewrite: { 45 | ['^' + process.env.VUE_APP_BASE_API]: '' 46 | } 47 | }, 48 | [process.env.VUE_APP_BASE_API +'/code']: { 49 | //target: `http://127.0.0.1:${port}/mock`, 50 | target: `http://localhost:8080`, 51 | changeOrigin: true, 52 | pathRewrite: { 53 | ['^' + process.env.VUE_APP_BASE_API]: '' 54 | } 55 | }, 56 | // change xxx-api/login => mock/login 57 | // detail: https://cli.vuejs.org/config/#devserver-proxy 58 | [process.env.VUE_APP_BASE_API]: { 59 | target: `http://127.0.0.1:${port}/mock`, 60 | //target: `http://localhost:8080`, 61 | changeOrigin: true, 62 | pathRewrite: { 63 | ['^' + process.env.VUE_APP_BASE_API]: '' 64 | } 65 | } 66 | } 67 | ,after: require('./mock/mock-server.js') 68 | }, 69 | configureWebpack: { 70 | // provide the app's title in webpack's name field, so that 71 | // it can be accessed in index.html to inject the correct title. 72 | name: name, 73 | resolve: { 74 | alias: { 75 | '@': resolve('src') 76 | } 77 | } 78 | }, 79 | chainWebpack(config) { 80 | config.plugins.delete('preload') // TODO: need test 81 | config.plugins.delete('prefetch') // TODO: need test 82 | 83 | // set svg-sprite-loader 84 | config.module 85 | .rule('svg') 86 | .exclude.add(resolve('src/icons')) 87 | .end() 88 | config.module 89 | .rule('icons') 90 | .test(/\.svg$/) 91 | .include.add(resolve('src/icons')) 92 | .end() 93 | .use('svg-sprite-loader') 94 | .loader('svg-sprite-loader') 95 | .options({ 96 | symbolId: 'icon-[name]' 97 | }) 98 | .end() 99 | 100 | // set preserveWhitespace 101 | config.module 102 | .rule('vue') 103 | .use('vue-loader') 104 | .loader('vue-loader') 105 | .tap(options => { 106 | options.compilerOptions.preserveWhitespace = true 107 | return options 108 | }) 109 | .end() 110 | 111 | config 112 | // https://webpack.js.org/configuration/devtool/#development 113 | .when(process.env.NODE_ENV === 'development', 114 | config => config.devtool('cheap-source-map') 115 | ) 116 | 117 | config 118 | .when(process.env.NODE_ENV !== 'development', 119 | config => { 120 | config 121 | .plugin('ScriptExtHtmlWebpackPlugin') 122 | .after('html') 123 | .use('script-ext-html-webpack-plugin', [{ 124 | // `runtime` must same as runtimeChunk name. default is `runtime` 125 | inline: /runtime\..*\.js$/ 126 | }]) 127 | .end() 128 | config 129 | .optimization.splitChunks({ 130 | chunks: 'all', 131 | cacheGroups: { 132 | libs: { 133 | name: 'chunk-libs', 134 | test: /[\\/]node_modules[\\/]/, 135 | priority: 10, 136 | chunks: 'initial' // only package third parties that are initially dependent 137 | }, 138 | elementUI: { 139 | name: 'chunk-elementUI', // split elementUI into a single package 140 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app 141 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm 142 | }, 143 | commons: { 144 | name: 'chunk-commons', 145 | test: resolve('src/components'), // can customize your rules 146 | minChunks: 3, // minimum common number 147 | priority: 5, 148 | reuseExistingChunk: true 149 | } 150 | } 151 | }) 152 | config.optimization.runtimeChunk('single') 153 | } 154 | ) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /dbtree-vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true, 11 | }, 12 | extends: ['plugin:vue/recommended', 'eslint:recommended'], 13 | 14 | // add your custom rules here 15 | //it is base on https://github.com/vuejs/eslint-config-vue 16 | rules: { 17 | "vue/max-attributes-per-line": [2, { 18 | "singleline": 10, 19 | "multiline": { 20 | "max": 1, 21 | "allowFirstLine": false 22 | } 23 | }], 24 | "vue/singleline-html-element-content-newline": "off", 25 | "vue/multiline-html-element-content-newline":"off", 26 | "vue/name-property-casing": ["error", "PascalCase"], 27 | "vue/no-v-html": "off", 28 | 'accessor-pairs': 2, 29 | 'arrow-spacing': [2, { 30 | 'before': true, 31 | 'after': true 32 | }], 33 | 'block-spacing': [2, 'always'], 34 | 'brace-style': [2, '1tbs', { 35 | 'allowSingleLine': true 36 | }], 37 | 'camelcase': [0, { 38 | 'properties': 'always' 39 | }], 40 | 'comma-dangle': [2, 'never'], 41 | 'comma-spacing': [2, { 42 | 'before': false, 43 | 'after': true 44 | }], 45 | 'comma-style': [2, 'last'], 46 | 'constructor-super': 2, 47 | 'curly': [2, 'multi-line'], 48 | 'dot-location': [2, 'property'], 49 | 'eol-last': 2, 50 | 'eqeqeq': ["error", "always", {"null": "ignore"}], 51 | 'generator-star-spacing': [2, { 52 | 'before': true, 53 | 'after': true 54 | }], 55 | 'handle-callback-err': [2, '^(err|error)$'], 56 | 'indent': [2, 2, { 57 | 'SwitchCase': 1 58 | }], 59 | 'jsx-quotes': [2, 'prefer-single'], 60 | 'key-spacing': [2, { 61 | 'beforeColon': false, 62 | 'afterColon': true 63 | }], 64 | 'keyword-spacing': [2, { 65 | 'before': true, 66 | 'after': true 67 | }], 68 | 'new-cap': [2, { 69 | 'newIsCap': true, 70 | 'capIsNew': false 71 | }], 72 | 'new-parens': 2, 73 | 'no-array-constructor': 2, 74 | 'no-caller': 2, 75 | 'no-console': 'off', 76 | 'no-class-assign': 2, 77 | 'no-cond-assign': 2, 78 | 'no-const-assign': 2, 79 | 'no-control-regex': 0, 80 | 'no-delete-var': 2, 81 | 'no-dupe-args': 2, 82 | 'no-dupe-class-members': 2, 83 | 'no-dupe-keys': 2, 84 | 'no-duplicate-case': 2, 85 | 'no-empty-character-class': 2, 86 | 'no-empty-pattern': 2, 87 | 'no-eval': 2, 88 | 'no-ex-assign': 2, 89 | 'no-extend-native': 2, 90 | 'no-extra-bind': 2, 91 | 'no-extra-boolean-cast': 2, 92 | 'no-extra-parens': [2, 'functions'], 93 | 'no-fallthrough': 2, 94 | 'no-floating-decimal': 2, 95 | 'no-func-assign': 2, 96 | 'no-implied-eval': 2, 97 | 'no-inner-declarations': [2, 'functions'], 98 | 'no-invalid-regexp': 2, 99 | 'no-irregular-whitespace': 2, 100 | 'no-iterator': 2, 101 | 'no-label-var': 2, 102 | 'no-labels': [2, { 103 | 'allowLoop': false, 104 | 'allowSwitch': false 105 | }], 106 | 'no-lone-blocks': 2, 107 | 'no-mixed-spaces-and-tabs': 2, 108 | 'no-multi-spaces': 2, 109 | 'no-multi-str': 2, 110 | 'no-multiple-empty-lines': [2, { 111 | 'max': 1 112 | }], 113 | 'no-native-reassign': 2, 114 | 'no-negated-in-lhs': 2, 115 | 'no-new-object': 2, 116 | 'no-new-require': 2, 117 | 'no-new-symbol': 2, 118 | 'no-new-wrappers': 2, 119 | 'no-obj-calls': 2, 120 | 'no-octal': 2, 121 | 'no-octal-escape': 2, 122 | 'no-path-concat': 2, 123 | 'no-proto': 2, 124 | 'no-redeclare': 2, 125 | 'no-regex-spaces': 2, 126 | 'no-return-assign': [2, 'except-parens'], 127 | 'no-self-assign': 2, 128 | 'no-self-compare': 2, 129 | 'no-sequences': 2, 130 | 'no-shadow-restricted-names': 2, 131 | 'no-spaced-func': 2, 132 | 'no-sparse-arrays': 2, 133 | 'no-this-before-super': 2, 134 | 'no-throw-literal': 2, 135 | 'no-trailing-spaces': 2, 136 | 'no-undef': 2, 137 | 'no-undef-init': 2, 138 | 'no-unexpected-multiline': 2, 139 | 'no-unmodified-loop-condition': 2, 140 | 'no-unneeded-ternary': [2, { 141 | 'defaultAssignment': false 142 | }], 143 | 'no-unreachable': 2, 144 | 'no-unsafe-finally': 2, 145 | 'no-unused-vars': [2, { 146 | 'vars': 'all', 147 | 'args': 'none' 148 | }], 149 | 'no-useless-call': 2, 150 | 'no-useless-computed-key': 2, 151 | 'no-useless-constructor': 2, 152 | 'no-useless-escape': 0, 153 | 'no-whitespace-before-property': 2, 154 | 'no-with': 2, 155 | 'one-var': [2, { 156 | 'initialized': 'never' 157 | }], 158 | 'operator-linebreak': [2, 'after', { 159 | 'overrides': { 160 | '?': 'before', 161 | ':': 'before' 162 | } 163 | }], 164 | 'padded-blocks': [2, 'never'], 165 | 'quotes': [2, 'single', { 166 | 'avoidEscape': true, 167 | 'allowTemplateLiterals': true 168 | }], 169 | 'semi': [2, 'never'], 170 | 'semi-spacing': [2, { 171 | 'before': false, 172 | 'after': true 173 | }], 174 | 'space-before-blocks': [2, 'always'], 175 | 'space-before-function-paren': [2, 'never'], 176 | 'space-in-parens': [2, 'never'], 177 | 'space-infix-ops': 2, 178 | 'space-unary-ops': [2, { 179 | 'words': true, 180 | 'nonwords': false 181 | }], 182 | 'spaced-comment': [2, 'always', { 183 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] 184 | }], 185 | 'template-curly-spacing': [2, 'never'], 186 | 'use-isnan': 2, 187 | 'valid-typeof': 2, 188 | 'wrap-iife': [2, 'any'], 189 | 'yield-star-spacing': [2, 'both'], 190 | 'yoda': [2, 'never'], 191 | 'prefer-const': 2, 192 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 193 | 'object-curly-spacing': [2, 'always', { 194 | objectsInObjects: false 195 | }], 196 | 'array-bracket-spacing': [2, 'never'] 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /dbtree-vue/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | OOPS! 12 | All rights reserved 13 | wallstreetcn 14 | 15 | {{ message }} 16 | Please check that the URL you entered is correct, or click the button below to return to the homepage. 17 | Back to home 18 | 19 | 20 | 21 | 22 | 23 | 34 | 35 | 229 | --------------------------------------------------------------------------------