├── .gitignore ├── README.en.md ├── README.md ├── docker-compose.yml ├── manager ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── vite.svg ├── src │ ├── App.vue │ ├── api.js │ ├── assets │ │ └── vue.svg │ ├── components │ │ ├── BentoList.vue │ │ ├── SourceList.vue │ │ ├── bento │ │ │ ├── CmpBentoItem.vue │ │ │ ├── JsonBentoItem.vue │ │ │ └── SqlBentoItem.vue │ │ └── datasource │ │ │ └── SourceItem.vue │ ├── main.js │ └── style.css └── vite.config.js ├── server ├── dockerfile ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── red │ │ │ └── view │ │ │ ├── ViewApplication.java │ │ │ ├── config │ │ │ ├── InitGenerator.java │ │ │ ├── JacksonConfig.java │ │ │ └── WebConfig.java │ │ │ ├── controller │ │ │ ├── BentoController.java │ │ │ ├── DataSourceController.java │ │ │ ├── TableController.java │ │ │ └── ViewController.java │ │ │ ├── entity │ │ │ ├── AjaxResult.java │ │ │ ├── BentoEntity.java │ │ │ ├── BentoTreeEntity.java │ │ │ ├── DataSourceEntity.java │ │ │ └── bentos │ │ │ │ ├── BentoFileEntity.java │ │ │ │ ├── BentoGroupEntity.java │ │ │ │ ├── BentoJsonEntity.java │ │ │ │ ├── BentoSqlEntity.java │ │ │ │ └── BentoTreeEntity.java │ │ │ ├── mapper │ │ │ ├── BentoFileMapper.java │ │ │ ├── BentoGroupMapper.java │ │ │ ├── BentoJsonMapper.java │ │ │ ├── BentoSqlMapper.java │ │ │ └── DataSourceMapper.java │ │ │ ├── service │ │ │ ├── IBentoService.java │ │ │ ├── IDataSourceService.java │ │ │ ├── IGoviewConvert.java │ │ │ ├── IViewJDBC.java │ │ │ ├── IViewService.java │ │ │ ├── bentos │ │ │ │ ├── IBentoFileService.java │ │ │ │ ├── IBentoGroupService.java │ │ │ │ ├── IBentoJsonService.java │ │ │ │ └── IBentoSqlService.java │ │ │ └── impl │ │ │ │ ├── BentoFileServiceImpl.java │ │ │ │ ├── BentoGroupServiceImpl.java │ │ │ │ ├── BentoJsonServiceImpl.java │ │ │ │ ├── BentoSqlServiceImpl.java │ │ │ │ ├── DataSourceServiceImpl.java │ │ │ │ ├── ViewJDBC.java │ │ │ │ └── ViewServiceImpl.java │ │ │ └── util │ │ │ ├── DateUtil.java │ │ │ ├── JSONUtil.java │ │ │ ├── ObjectHelper.java │ │ │ └── UUIDUtil.java │ └── resources │ │ ├── application-mysql.yml │ │ ├── application-sqlite.yml │ │ ├── application.yml │ │ ├── db │ │ ├── mysql.sql │ │ ├── redview-sqlite.db │ │ └── sqlite.sql │ │ └── public │ │ ├── index.html │ │ └── redview.png │ └── test │ └── java │ └── com │ └── red │ └── view │ └── ViewApplicationTests.java └── ui ├── .env ├── .gitignore ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── public └── favicon.ico ├── src ├── App.vue ├── api.ts ├── assets │ ├── base.css │ ├── logo.svg │ └── main.css ├── components │ ├── BentoList.vue │ ├── SourceList.vue │ ├── bento │ │ ├── CmpBentoItem.vue │ │ ├── JsonBentoItem.vue │ │ └── SqlBentoItem.vue │ └── ds │ │ └── SourceItem.vue ├── main.ts └── types │ └── IDataSource.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | ReadMe.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # Red-View 2 | ### A Multi-DataSource Data Query Service 3 | ## Introduction 4 | I develop backend services for various dashboard systems. 5 | My frontend colleagues use GoView to quickly build these dashboards. 6 | I noticed that dashboard systems often require extensive statistical analysis and multi-data source query interfaces. 7 | Therefore, 8 | I designed a configurable backend service to meet the needs of GoView. 9 | 10 | ## Features 11 | * Supports multiple databases, including PostgreSQL, MySQL, Oracle, SQL Server, ClickHouse, and JSON. 12 | * Provides a configuration interface for setting up views and their data sources in a graphical manner. 13 | * Lightweight – focuses solely on queries without unnecessary components, suitable for embedding into microservices architectures, currently supports Spring Cloud Alibaba. 14 | 15 | ## TODO 16 | * Composite Views: Build new views using multiple existing views as data sources to satisfy complex multi-source join queries. 17 | * Improve Fuzzy Search: Currently uses named parameters (i.e., :paramName) for pre-compiling SQL statements, but this approach is not very friendly for fuzzy search scenarios. 18 | * Integrate Files and Documents: Provide markdown previews for documents. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Red-View 2 | 3 | ### 一个支持多数据源的数据查询服务 4 | 5 | 6 | 7 | ### 介绍 8 | 我为很多驾驶舱系统开发后台服务。 9 | 我的前端伙伴为了快速构建驾驶舱系统,采用了[GoView](https://www.mtruning.club/#/)。 10 | 我发现,驾驶舱系统需要大量的统计分析类的多数据源的查询接口, 11 | 所以我尝试设计一个可配置的后台服务,为了满足GoView的需要。 12 | 13 | ### 特性 14 | * 支持多种数据库,目前支持postgresql、mysql、oracle、sqlserver、clickhouse、json 15 | * 提供配置页面,可以以图形化的方式配置视图及其数据源 16 | * 轻量化,只有查询,没有集成多余的部件,适合嵌入到微服务体系中,目前支持SpringCloudAlibaba 17 | 18 | ### TODO 19 | * 组合视图,以多个视图为数据源构建新的视图,能满足多数据源联合查询的复杂需求 20 | * 优化模糊查询,目前采用命名参数(即 :paramName)的方式实现预编译SQL语句,但其对模糊查询等场景不够友好 21 | * 接入文件和文档,文档提供markdown预览 22 | * 集成AI生成SQL 23 | 24 | ### 灯泡 25 | ``` 26 | 鉴权功能,根本不用集成。 27 | 最近跑了一遍国产API网关Apisix的Demo,看上去挺带劲的。 28 | 对比他的老对手Kong,apisix的文档有中文可真是太棒了。 29 | 在redview前装个apisix,由apisix来完成鉴权,是最棒的选择。 30 | ``` 31 | ``` 32 | 用了一段时间发现,编写复杂的统计SQL,需要对SQL非常熟悉。 33 | 虽然不需要写代码,但是构思复杂SQL耗时也很长,开发效率实际上并没有提高。 34 | 当然这也不能说redview没用,开发后台本来就是SQL和代码结合,判断和类型转换等麻烦事一样会遇到。 35 | redview将混沌的不确定的部分都交给SQL,也是一种进步。 36 | 我发现通义千问在数据库SQL语句生成方面很在行。 37 | 如果,可以用AI来生成SQL,自动测试SQL,自动改正SQL。那确实是大大地提高了开发效率。 38 | 一个集成了AI的万用数据视图也是挺酷的,想整。 39 | ``` 40 | 41 | 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | view: 4 | build: . 5 | ports: 6 | - '20000:20000' # 冒号左边可以改成自己服务器未被占用的端口 7 | restart: on-failure 8 | 9 | db1: 10 | image: mysql:5.7 11 | command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci 12 | ports: 13 | - '3306:3306' 14 | environment: 15 | MYSQL_ROOT_PASSWORD: 123456 # 数据库用户root的密码 16 | volumes: 17 | - /D/red-view/mysql/data:/var/lib/mysql # 冒号左边可以改路径,现在是表示把数据存放在在当前文件夹下的 mariadb 文件夹中 18 | restart: on-failure 19 | db2: 20 | image: postgis/postgis:latest 21 | ports: 22 | - '5432:5432' 23 | volumes: 24 | - /D/red-view/postgresql/data:/var/lib/postgresql/data 25 | environment: 26 | POSTGRES_DB: gis_db 27 | POSTGRES_USER: postgres 28 | POSTGRES_PASSWORD: 9C@5EDm%ZGvE 29 | db3: 30 | image: clickhouse/clickhouse-server 31 | ports: 32 | - '8123:8123' 33 | volumes: 34 | - /D/red-view/clickhouse/data:/var/lib/clickhouse/ 35 | db4: 36 | image: oraclelinux:9 37 | ports: 38 | - '1521:1521' 39 | volumes: 40 | - /D/red-view/oracle_data:/opt/oracle/oradata 41 | db5: 42 | image: mcr.microsoft.com/mssql/server 43 | ports: 44 | - '1433:1433' 45 | volumes: 46 | - /D/red-view/sqlserver:/var/opt/mssql 47 | environment: 48 | ACCEPT_EULA: Y 49 | MSSQL_SA_PASSWORD: 9C@5EDm%ZGvE 50 | MSSQL_PID: Express 51 | 52 | 53 | -------------------------------------------------------------------------------- /manager/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /manager/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /manager/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manager", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "manager", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "axios": "^1.7.9", 12 | "element-plus": "^2.9.1", 13 | "vue": "^3.5.13" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^5.2.1", 17 | "vite": "^6.0.5" 18 | } 19 | }, 20 | "node_modules/@babel/helper-string-parser": { 21 | "version": "7.25.9", 22 | "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 23 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 24 | "engines": { 25 | "node": ">=6.9.0" 26 | } 27 | }, 28 | "node_modules/@babel/helper-validator-identifier": { 29 | "version": "7.25.9", 30 | "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 31 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 32 | "engines": { 33 | "node": ">=6.9.0" 34 | } 35 | }, 36 | "node_modules/@babel/parser": { 37 | "version": "7.26.3", 38 | "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.26.3.tgz", 39 | "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", 40 | "dependencies": { 41 | "@babel/types": "^7.26.3" 42 | }, 43 | "bin": { 44 | "parser": "bin/babel-parser.js" 45 | }, 46 | "engines": { 47 | "node": ">=6.0.0" 48 | } 49 | }, 50 | "node_modules/@babel/types": { 51 | "version": "7.26.3", 52 | "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.26.3.tgz", 53 | "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", 54 | "dependencies": { 55 | "@babel/helper-string-parser": "^7.25.9", 56 | "@babel/helper-validator-identifier": "^7.25.9" 57 | }, 58 | "engines": { 59 | "node": ">=6.9.0" 60 | } 61 | }, 62 | "node_modules/@ctrl/tinycolor": { 63 | "version": "3.6.1", 64 | "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", 65 | "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", 66 | "engines": { 67 | "node": ">=10" 68 | } 69 | }, 70 | "node_modules/@element-plus/icons-vue": { 71 | "version": "2.3.1", 72 | "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", 73 | "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", 74 | "peerDependencies": { 75 | "vue": "^3.2.0" 76 | } 77 | }, 78 | "node_modules/@esbuild/aix-ppc64": { 79 | "version": "0.24.0", 80 | "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", 81 | "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", 82 | "cpu": [ 83 | "ppc64" 84 | ], 85 | "dev": true, 86 | "optional": true, 87 | "os": [ 88 | "aix" 89 | ], 90 | "engines": { 91 | "node": ">=18" 92 | } 93 | }, 94 | "node_modules/@esbuild/android-arm": { 95 | "version": "0.24.0", 96 | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz", 97 | "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", 98 | "cpu": [ 99 | "arm" 100 | ], 101 | "dev": true, 102 | "optional": true, 103 | "os": [ 104 | "android" 105 | ], 106 | "engines": { 107 | "node": ">=18" 108 | } 109 | }, 110 | "node_modules/@esbuild/android-arm64": { 111 | "version": "0.24.0", 112 | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", 113 | "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", 114 | "cpu": [ 115 | "arm64" 116 | ], 117 | "dev": true, 118 | "optional": true, 119 | "os": [ 120 | "android" 121 | ], 122 | "engines": { 123 | "node": ">=18" 124 | } 125 | }, 126 | "node_modules/@esbuild/android-x64": { 127 | "version": "0.24.0", 128 | "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz", 129 | "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", 130 | "cpu": [ 131 | "x64" 132 | ], 133 | "dev": true, 134 | "optional": true, 135 | "os": [ 136 | "android" 137 | ], 138 | "engines": { 139 | "node": ">=18" 140 | } 141 | }, 142 | "node_modules/@esbuild/darwin-arm64": { 143 | "version": "0.24.0", 144 | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", 145 | "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", 146 | "cpu": [ 147 | "arm64" 148 | ], 149 | "dev": true, 150 | "optional": true, 151 | "os": [ 152 | "darwin" 153 | ], 154 | "engines": { 155 | "node": ">=18" 156 | } 157 | }, 158 | "node_modules/@esbuild/darwin-x64": { 159 | "version": "0.24.0", 160 | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", 161 | "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", 162 | "cpu": [ 163 | "x64" 164 | ], 165 | "dev": true, 166 | "optional": true, 167 | "os": [ 168 | "darwin" 169 | ], 170 | "engines": { 171 | "node": ">=18" 172 | } 173 | }, 174 | "node_modules/@esbuild/freebsd-arm64": { 175 | "version": "0.24.0", 176 | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", 177 | "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", 178 | "cpu": [ 179 | "arm64" 180 | ], 181 | "dev": true, 182 | "optional": true, 183 | "os": [ 184 | "freebsd" 185 | ], 186 | "engines": { 187 | "node": ">=18" 188 | } 189 | }, 190 | "node_modules/@esbuild/freebsd-x64": { 191 | "version": "0.24.0", 192 | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", 193 | "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", 194 | "cpu": [ 195 | "x64" 196 | ], 197 | "dev": true, 198 | "optional": true, 199 | "os": [ 200 | "freebsd" 201 | ], 202 | "engines": { 203 | "node": ">=18" 204 | } 205 | }, 206 | "node_modules/@esbuild/linux-arm": { 207 | "version": "0.24.0", 208 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", 209 | "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", 210 | "cpu": [ 211 | "arm" 212 | ], 213 | "dev": true, 214 | "optional": true, 215 | "os": [ 216 | "linux" 217 | ], 218 | "engines": { 219 | "node": ">=18" 220 | } 221 | }, 222 | "node_modules/@esbuild/linux-arm64": { 223 | "version": "0.24.0", 224 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", 225 | "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", 226 | "cpu": [ 227 | "arm64" 228 | ], 229 | "dev": true, 230 | "optional": true, 231 | "os": [ 232 | "linux" 233 | ], 234 | "engines": { 235 | "node": ">=18" 236 | } 237 | }, 238 | "node_modules/@esbuild/linux-ia32": { 239 | "version": "0.24.0", 240 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", 241 | "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", 242 | "cpu": [ 243 | "ia32" 244 | ], 245 | "dev": true, 246 | "optional": true, 247 | "os": [ 248 | "linux" 249 | ], 250 | "engines": { 251 | "node": ">=18" 252 | } 253 | }, 254 | "node_modules/@esbuild/linux-loong64": { 255 | "version": "0.24.0", 256 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", 257 | "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", 258 | "cpu": [ 259 | "loong64" 260 | ], 261 | "dev": true, 262 | "optional": true, 263 | "os": [ 264 | "linux" 265 | ], 266 | "engines": { 267 | "node": ">=18" 268 | } 269 | }, 270 | "node_modules/@esbuild/linux-mips64el": { 271 | "version": "0.24.0", 272 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", 273 | "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", 274 | "cpu": [ 275 | "mips64el" 276 | ], 277 | "dev": true, 278 | "optional": true, 279 | "os": [ 280 | "linux" 281 | ], 282 | "engines": { 283 | "node": ">=18" 284 | } 285 | }, 286 | "node_modules/@esbuild/linux-ppc64": { 287 | "version": "0.24.0", 288 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", 289 | "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", 290 | "cpu": [ 291 | "ppc64" 292 | ], 293 | "dev": true, 294 | "optional": true, 295 | "os": [ 296 | "linux" 297 | ], 298 | "engines": { 299 | "node": ">=18" 300 | } 301 | }, 302 | "node_modules/@esbuild/linux-riscv64": { 303 | "version": "0.24.0", 304 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", 305 | "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", 306 | "cpu": [ 307 | "riscv64" 308 | ], 309 | "dev": true, 310 | "optional": true, 311 | "os": [ 312 | "linux" 313 | ], 314 | "engines": { 315 | "node": ">=18" 316 | } 317 | }, 318 | "node_modules/@esbuild/linux-s390x": { 319 | "version": "0.24.0", 320 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", 321 | "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", 322 | "cpu": [ 323 | "s390x" 324 | ], 325 | "dev": true, 326 | "optional": true, 327 | "os": [ 328 | "linux" 329 | ], 330 | "engines": { 331 | "node": ">=18" 332 | } 333 | }, 334 | "node_modules/@esbuild/linux-x64": { 335 | "version": "0.24.0", 336 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", 337 | "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", 338 | "cpu": [ 339 | "x64" 340 | ], 341 | "dev": true, 342 | "optional": true, 343 | "os": [ 344 | "linux" 345 | ], 346 | "engines": { 347 | "node": ">=18" 348 | } 349 | }, 350 | "node_modules/@esbuild/netbsd-x64": { 351 | "version": "0.24.0", 352 | "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", 353 | "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", 354 | "cpu": [ 355 | "x64" 356 | ], 357 | "dev": true, 358 | "optional": true, 359 | "os": [ 360 | "netbsd" 361 | ], 362 | "engines": { 363 | "node": ">=18" 364 | } 365 | }, 366 | "node_modules/@esbuild/openbsd-arm64": { 367 | "version": "0.24.0", 368 | "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", 369 | "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", 370 | "cpu": [ 371 | "arm64" 372 | ], 373 | "dev": true, 374 | "optional": true, 375 | "os": [ 376 | "openbsd" 377 | ], 378 | "engines": { 379 | "node": ">=18" 380 | } 381 | }, 382 | "node_modules/@esbuild/openbsd-x64": { 383 | "version": "0.24.0", 384 | "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", 385 | "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", 386 | "cpu": [ 387 | "x64" 388 | ], 389 | "dev": true, 390 | "optional": true, 391 | "os": [ 392 | "openbsd" 393 | ], 394 | "engines": { 395 | "node": ">=18" 396 | } 397 | }, 398 | "node_modules/@esbuild/sunos-x64": { 399 | "version": "0.24.0", 400 | "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", 401 | "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", 402 | "cpu": [ 403 | "x64" 404 | ], 405 | "dev": true, 406 | "optional": true, 407 | "os": [ 408 | "sunos" 409 | ], 410 | "engines": { 411 | "node": ">=18" 412 | } 413 | }, 414 | "node_modules/@esbuild/win32-arm64": { 415 | "version": "0.24.0", 416 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", 417 | "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", 418 | "cpu": [ 419 | "arm64" 420 | ], 421 | "dev": true, 422 | "optional": true, 423 | "os": [ 424 | "win32" 425 | ], 426 | "engines": { 427 | "node": ">=18" 428 | } 429 | }, 430 | "node_modules/@esbuild/win32-ia32": { 431 | "version": "0.24.0", 432 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", 433 | "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", 434 | "cpu": [ 435 | "ia32" 436 | ], 437 | "dev": true, 438 | "optional": true, 439 | "os": [ 440 | "win32" 441 | ], 442 | "engines": { 443 | "node": ">=18" 444 | } 445 | }, 446 | "node_modules/@esbuild/win32-x64": { 447 | "version": "0.24.0", 448 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", 449 | "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", 450 | "cpu": [ 451 | "x64" 452 | ], 453 | "dev": true, 454 | "optional": true, 455 | "os": [ 456 | "win32" 457 | ], 458 | "engines": { 459 | "node": ">=18" 460 | } 461 | }, 462 | "node_modules/@floating-ui/core": { 463 | "version": "1.6.8", 464 | "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.6.8.tgz", 465 | "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", 466 | "dependencies": { 467 | "@floating-ui/utils": "^0.2.8" 468 | } 469 | }, 470 | "node_modules/@floating-ui/dom": { 471 | "version": "1.6.12", 472 | "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.6.12.tgz", 473 | "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", 474 | "dependencies": { 475 | "@floating-ui/core": "^1.6.0", 476 | "@floating-ui/utils": "^0.2.8" 477 | } 478 | }, 479 | "node_modules/@floating-ui/utils": { 480 | "version": "0.2.8", 481 | "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.8.tgz", 482 | "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" 483 | }, 484 | "node_modules/@jridgewell/sourcemap-codec": { 485 | "version": "1.5.0", 486 | "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 487 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 488 | }, 489 | "node_modules/@popperjs/core": { 490 | "name": "@sxzz/popperjs-es", 491 | "version": "2.11.7", 492 | "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", 493 | "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", 494 | "funding": { 495 | "type": "opencollective", 496 | "url": "https://opencollective.com/popperjs" 497 | } 498 | }, 499 | "node_modules/@rollup/rollup-android-arm-eabi": { 500 | "version": "4.29.1", 501 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz", 502 | "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==", 503 | "cpu": [ 504 | "arm" 505 | ], 506 | "dev": true, 507 | "optional": true, 508 | "os": [ 509 | "android" 510 | ] 511 | }, 512 | "node_modules/@rollup/rollup-android-arm64": { 513 | "version": "4.29.1", 514 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz", 515 | "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==", 516 | "cpu": [ 517 | "arm64" 518 | ], 519 | "dev": true, 520 | "optional": true, 521 | "os": [ 522 | "android" 523 | ] 524 | }, 525 | "node_modules/@rollup/rollup-darwin-arm64": { 526 | "version": "4.29.1", 527 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz", 528 | "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==", 529 | "cpu": [ 530 | "arm64" 531 | ], 532 | "dev": true, 533 | "optional": true, 534 | "os": [ 535 | "darwin" 536 | ] 537 | }, 538 | "node_modules/@rollup/rollup-darwin-x64": { 539 | "version": "4.29.1", 540 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz", 541 | "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==", 542 | "cpu": [ 543 | "x64" 544 | ], 545 | "dev": true, 546 | "optional": true, 547 | "os": [ 548 | "darwin" 549 | ] 550 | }, 551 | "node_modules/@rollup/rollup-freebsd-arm64": { 552 | "version": "4.29.1", 553 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz", 554 | "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==", 555 | "cpu": [ 556 | "arm64" 557 | ], 558 | "dev": true, 559 | "optional": true, 560 | "os": [ 561 | "freebsd" 562 | ] 563 | }, 564 | "node_modules/@rollup/rollup-freebsd-x64": { 565 | "version": "4.29.1", 566 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz", 567 | "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==", 568 | "cpu": [ 569 | "x64" 570 | ], 571 | "dev": true, 572 | "optional": true, 573 | "os": [ 574 | "freebsd" 575 | ] 576 | }, 577 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 578 | "version": "4.29.1", 579 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz", 580 | "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==", 581 | "cpu": [ 582 | "arm" 583 | ], 584 | "dev": true, 585 | "optional": true, 586 | "os": [ 587 | "linux" 588 | ] 589 | }, 590 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 591 | "version": "4.29.1", 592 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz", 593 | "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==", 594 | "cpu": [ 595 | "arm" 596 | ], 597 | "dev": true, 598 | "optional": true, 599 | "os": [ 600 | "linux" 601 | ] 602 | }, 603 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 604 | "version": "4.29.1", 605 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz", 606 | "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==", 607 | "cpu": [ 608 | "arm64" 609 | ], 610 | "dev": true, 611 | "optional": true, 612 | "os": [ 613 | "linux" 614 | ] 615 | }, 616 | "node_modules/@rollup/rollup-linux-arm64-musl": { 617 | "version": "4.29.1", 618 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz", 619 | "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==", 620 | "cpu": [ 621 | "arm64" 622 | ], 623 | "dev": true, 624 | "optional": true, 625 | "os": [ 626 | "linux" 627 | ] 628 | }, 629 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 630 | "version": "4.29.1", 631 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz", 632 | "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==", 633 | "cpu": [ 634 | "loong64" 635 | ], 636 | "dev": true, 637 | "optional": true, 638 | "os": [ 639 | "linux" 640 | ] 641 | }, 642 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 643 | "version": "4.29.1", 644 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz", 645 | "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==", 646 | "cpu": [ 647 | "ppc64" 648 | ], 649 | "dev": true, 650 | "optional": true, 651 | "os": [ 652 | "linux" 653 | ] 654 | }, 655 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 656 | "version": "4.29.1", 657 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz", 658 | "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==", 659 | "cpu": [ 660 | "riscv64" 661 | ], 662 | "dev": true, 663 | "optional": true, 664 | "os": [ 665 | "linux" 666 | ] 667 | }, 668 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 669 | "version": "4.29.1", 670 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz", 671 | "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==", 672 | "cpu": [ 673 | "s390x" 674 | ], 675 | "dev": true, 676 | "optional": true, 677 | "os": [ 678 | "linux" 679 | ] 680 | }, 681 | "node_modules/@rollup/rollup-linux-x64-gnu": { 682 | "version": "4.29.1", 683 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz", 684 | "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==", 685 | "cpu": [ 686 | "x64" 687 | ], 688 | "dev": true, 689 | "optional": true, 690 | "os": [ 691 | "linux" 692 | ] 693 | }, 694 | "node_modules/@rollup/rollup-linux-x64-musl": { 695 | "version": "4.29.1", 696 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz", 697 | "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==", 698 | "cpu": [ 699 | "x64" 700 | ], 701 | "dev": true, 702 | "optional": true, 703 | "os": [ 704 | "linux" 705 | ] 706 | }, 707 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 708 | "version": "4.29.1", 709 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz", 710 | "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==", 711 | "cpu": [ 712 | "arm64" 713 | ], 714 | "dev": true, 715 | "optional": true, 716 | "os": [ 717 | "win32" 718 | ] 719 | }, 720 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 721 | "version": "4.29.1", 722 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz", 723 | "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==", 724 | "cpu": [ 725 | "ia32" 726 | ], 727 | "dev": true, 728 | "optional": true, 729 | "os": [ 730 | "win32" 731 | ] 732 | }, 733 | "node_modules/@rollup/rollup-win32-x64-msvc": { 734 | "version": "4.29.1", 735 | "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz", 736 | "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==", 737 | "cpu": [ 738 | "x64" 739 | ], 740 | "dev": true, 741 | "optional": true, 742 | "os": [ 743 | "win32" 744 | ] 745 | }, 746 | "node_modules/@types/estree": { 747 | "version": "1.0.6", 748 | "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.6.tgz", 749 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 750 | "dev": true 751 | }, 752 | "node_modules/@types/lodash": { 753 | "version": "4.17.13", 754 | "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz", 755 | "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==" 756 | }, 757 | "node_modules/@types/lodash-es": { 758 | "version": "4.17.12", 759 | "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", 760 | "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", 761 | "dependencies": { 762 | "@types/lodash": "*" 763 | } 764 | }, 765 | "node_modules/@types/web-bluetooth": { 766 | "version": "0.0.16", 767 | "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", 768 | "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" 769 | }, 770 | "node_modules/@vitejs/plugin-vue": { 771 | "version": "5.2.1", 772 | "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", 773 | "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", 774 | "dev": true, 775 | "engines": { 776 | "node": "^18.0.0 || >=20.0.0" 777 | }, 778 | "peerDependencies": { 779 | "vite": "^5.0.0 || ^6.0.0", 780 | "vue": "^3.2.25" 781 | } 782 | }, 783 | "node_modules/@vue/compiler-core": { 784 | "version": "3.5.13", 785 | "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", 786 | "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", 787 | "dependencies": { 788 | "@babel/parser": "^7.25.3", 789 | "@vue/shared": "3.5.13", 790 | "entities": "^4.5.0", 791 | "estree-walker": "^2.0.2", 792 | "source-map-js": "^1.2.0" 793 | } 794 | }, 795 | "node_modules/@vue/compiler-dom": { 796 | "version": "3.5.13", 797 | "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", 798 | "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", 799 | "dependencies": { 800 | "@vue/compiler-core": "3.5.13", 801 | "@vue/shared": "3.5.13" 802 | } 803 | }, 804 | "node_modules/@vue/compiler-sfc": { 805 | "version": "3.5.13", 806 | "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", 807 | "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", 808 | "dependencies": { 809 | "@babel/parser": "^7.25.3", 810 | "@vue/compiler-core": "3.5.13", 811 | "@vue/compiler-dom": "3.5.13", 812 | "@vue/compiler-ssr": "3.5.13", 813 | "@vue/shared": "3.5.13", 814 | "estree-walker": "^2.0.2", 815 | "magic-string": "^0.30.11", 816 | "postcss": "^8.4.48", 817 | "source-map-js": "^1.2.0" 818 | } 819 | }, 820 | "node_modules/@vue/compiler-ssr": { 821 | "version": "3.5.13", 822 | "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", 823 | "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", 824 | "dependencies": { 825 | "@vue/compiler-dom": "3.5.13", 826 | "@vue/shared": "3.5.13" 827 | } 828 | }, 829 | "node_modules/@vue/reactivity": { 830 | "version": "3.5.13", 831 | "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", 832 | "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", 833 | "dependencies": { 834 | "@vue/shared": "3.5.13" 835 | } 836 | }, 837 | "node_modules/@vue/runtime-core": { 838 | "version": "3.5.13", 839 | "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", 840 | "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", 841 | "dependencies": { 842 | "@vue/reactivity": "3.5.13", 843 | "@vue/shared": "3.5.13" 844 | } 845 | }, 846 | "node_modules/@vue/runtime-dom": { 847 | "version": "3.5.13", 848 | "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", 849 | "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", 850 | "dependencies": { 851 | "@vue/reactivity": "3.5.13", 852 | "@vue/runtime-core": "3.5.13", 853 | "@vue/shared": "3.5.13", 854 | "csstype": "^3.1.3" 855 | } 856 | }, 857 | "node_modules/@vue/server-renderer": { 858 | "version": "3.5.13", 859 | "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", 860 | "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", 861 | "dependencies": { 862 | "@vue/compiler-ssr": "3.5.13", 863 | "@vue/shared": "3.5.13" 864 | }, 865 | "peerDependencies": { 866 | "vue": "3.5.13" 867 | } 868 | }, 869 | "node_modules/@vue/shared": { 870 | "version": "3.5.13", 871 | "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", 872 | "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" 873 | }, 874 | "node_modules/@vueuse/core": { 875 | "version": "9.13.0", 876 | "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", 877 | "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", 878 | "dependencies": { 879 | "@types/web-bluetooth": "^0.0.16", 880 | "@vueuse/metadata": "9.13.0", 881 | "@vueuse/shared": "9.13.0", 882 | "vue-demi": "*" 883 | }, 884 | "funding": { 885 | "url": "https://github.com/sponsors/antfu" 886 | } 887 | }, 888 | "node_modules/@vueuse/core/node_modules/vue-demi": { 889 | "version": "0.14.10", 890 | "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", 891 | "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", 892 | "hasInstallScript": true, 893 | "bin": { 894 | "vue-demi-fix": "bin/vue-demi-fix.js", 895 | "vue-demi-switch": "bin/vue-demi-switch.js" 896 | }, 897 | "engines": { 898 | "node": ">=12" 899 | }, 900 | "funding": { 901 | "url": "https://github.com/sponsors/antfu" 902 | }, 903 | "peerDependencies": { 904 | "@vue/composition-api": "^1.0.0-rc.1", 905 | "vue": "^3.0.0-0 || ^2.6.0" 906 | }, 907 | "peerDependenciesMeta": { 908 | "@vue/composition-api": { 909 | "optional": true 910 | } 911 | } 912 | }, 913 | "node_modules/@vueuse/metadata": { 914 | "version": "9.13.0", 915 | "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", 916 | "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", 917 | "funding": { 918 | "url": "https://github.com/sponsors/antfu" 919 | } 920 | }, 921 | "node_modules/@vueuse/shared": { 922 | "version": "9.13.0", 923 | "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", 924 | "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", 925 | "dependencies": { 926 | "vue-demi": "*" 927 | }, 928 | "funding": { 929 | "url": "https://github.com/sponsors/antfu" 930 | } 931 | }, 932 | "node_modules/@vueuse/shared/node_modules/vue-demi": { 933 | "version": "0.14.10", 934 | "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", 935 | "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", 936 | "hasInstallScript": true, 937 | "bin": { 938 | "vue-demi-fix": "bin/vue-demi-fix.js", 939 | "vue-demi-switch": "bin/vue-demi-switch.js" 940 | }, 941 | "engines": { 942 | "node": ">=12" 943 | }, 944 | "funding": { 945 | "url": "https://github.com/sponsors/antfu" 946 | }, 947 | "peerDependencies": { 948 | "@vue/composition-api": "^1.0.0-rc.1", 949 | "vue": "^3.0.0-0 || ^2.6.0" 950 | }, 951 | "peerDependenciesMeta": { 952 | "@vue/composition-api": { 953 | "optional": true 954 | } 955 | } 956 | }, 957 | "node_modules/async-validator": { 958 | "version": "4.2.5", 959 | "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", 960 | "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" 961 | }, 962 | "node_modules/asynckit": { 963 | "version": "0.4.0", 964 | "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", 965 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 966 | }, 967 | "node_modules/axios": { 968 | "version": "1.7.9", 969 | "resolved": "https://registry.npmmirror.com/axios/-/axios-1.7.9.tgz", 970 | "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", 971 | "dependencies": { 972 | "follow-redirects": "^1.15.6", 973 | "form-data": "^4.0.0", 974 | "proxy-from-env": "^1.1.0" 975 | } 976 | }, 977 | "node_modules/combined-stream": { 978 | "version": "1.0.8", 979 | "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", 980 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 981 | "dependencies": { 982 | "delayed-stream": "~1.0.0" 983 | }, 984 | "engines": { 985 | "node": ">= 0.8" 986 | } 987 | }, 988 | "node_modules/csstype": { 989 | "version": "3.1.3", 990 | "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", 991 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 992 | }, 993 | "node_modules/dayjs": { 994 | "version": "1.11.13", 995 | "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", 996 | "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" 997 | }, 998 | "node_modules/delayed-stream": { 999 | "version": "1.0.0", 1000 | "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", 1001 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 1002 | "engines": { 1003 | "node": ">=0.4.0" 1004 | } 1005 | }, 1006 | "node_modules/element-plus": { 1007 | "version": "2.9.1", 1008 | "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.1.tgz", 1009 | "integrity": "sha512-9Agqf/jt4Ugk7EZ6C5LME71sgkvauPCsnvJN12Xid2XVobjufxMGpRE4L7pS4luJMOmFAH3J0NgYEGZT5r+NDg==", 1010 | "dependencies": { 1011 | "@ctrl/tinycolor": "^3.4.1", 1012 | "@element-plus/icons-vue": "^2.3.1", 1013 | "@floating-ui/dom": "^1.0.1", 1014 | "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", 1015 | "@types/lodash": "^4.14.182", 1016 | "@types/lodash-es": "^4.17.6", 1017 | "@vueuse/core": "^9.1.0", 1018 | "async-validator": "^4.2.5", 1019 | "dayjs": "^1.11.13", 1020 | "escape-html": "^1.0.3", 1021 | "lodash": "^4.17.21", 1022 | "lodash-es": "^4.17.21", 1023 | "lodash-unified": "^1.0.2", 1024 | "memoize-one": "^6.0.0", 1025 | "normalize-wheel-es": "^1.2.0" 1026 | }, 1027 | "peerDependencies": { 1028 | "vue": "^3.2.0" 1029 | } 1030 | }, 1031 | "node_modules/entities": { 1032 | "version": "4.5.0", 1033 | "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", 1034 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1035 | "engines": { 1036 | "node": ">=0.12" 1037 | }, 1038 | "funding": { 1039 | "url": "https://github.com/fb55/entities?sponsor=1" 1040 | } 1041 | }, 1042 | "node_modules/esbuild": { 1043 | "version": "0.24.0", 1044 | "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.24.0.tgz", 1045 | "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", 1046 | "dev": true, 1047 | "hasInstallScript": true, 1048 | "bin": { 1049 | "esbuild": "bin/esbuild" 1050 | }, 1051 | "engines": { 1052 | "node": ">=18" 1053 | }, 1054 | "optionalDependencies": { 1055 | "@esbuild/aix-ppc64": "0.24.0", 1056 | "@esbuild/android-arm": "0.24.0", 1057 | "@esbuild/android-arm64": "0.24.0", 1058 | "@esbuild/android-x64": "0.24.0", 1059 | "@esbuild/darwin-arm64": "0.24.0", 1060 | "@esbuild/darwin-x64": "0.24.0", 1061 | "@esbuild/freebsd-arm64": "0.24.0", 1062 | "@esbuild/freebsd-x64": "0.24.0", 1063 | "@esbuild/linux-arm": "0.24.0", 1064 | "@esbuild/linux-arm64": "0.24.0", 1065 | "@esbuild/linux-ia32": "0.24.0", 1066 | "@esbuild/linux-loong64": "0.24.0", 1067 | "@esbuild/linux-mips64el": "0.24.0", 1068 | "@esbuild/linux-ppc64": "0.24.0", 1069 | "@esbuild/linux-riscv64": "0.24.0", 1070 | "@esbuild/linux-s390x": "0.24.0", 1071 | "@esbuild/linux-x64": "0.24.0", 1072 | "@esbuild/netbsd-x64": "0.24.0", 1073 | "@esbuild/openbsd-arm64": "0.24.0", 1074 | "@esbuild/openbsd-x64": "0.24.0", 1075 | "@esbuild/sunos-x64": "0.24.0", 1076 | "@esbuild/win32-arm64": "0.24.0", 1077 | "@esbuild/win32-ia32": "0.24.0", 1078 | "@esbuild/win32-x64": "0.24.0" 1079 | } 1080 | }, 1081 | "node_modules/escape-html": { 1082 | "version": "1.0.3", 1083 | "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", 1084 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 1085 | }, 1086 | "node_modules/estree-walker": { 1087 | "version": "2.0.2", 1088 | "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", 1089 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 1090 | }, 1091 | "node_modules/follow-redirects": { 1092 | "version": "1.15.9", 1093 | "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz", 1094 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", 1095 | "funding": [ 1096 | { 1097 | "type": "individual", 1098 | "url": "https://github.com/sponsors/RubenVerborgh" 1099 | } 1100 | ], 1101 | "engines": { 1102 | "node": ">=4.0" 1103 | }, 1104 | "peerDependenciesMeta": { 1105 | "debug": { 1106 | "optional": true 1107 | } 1108 | } 1109 | }, 1110 | "node_modules/form-data": { 1111 | "version": "4.0.1", 1112 | "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.1.tgz", 1113 | "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", 1114 | "dependencies": { 1115 | "asynckit": "^0.4.0", 1116 | "combined-stream": "^1.0.8", 1117 | "mime-types": "^2.1.12" 1118 | }, 1119 | "engines": { 1120 | "node": ">= 6" 1121 | } 1122 | }, 1123 | "node_modules/fsevents": { 1124 | "version": "2.3.3", 1125 | "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", 1126 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1127 | "dev": true, 1128 | "hasInstallScript": true, 1129 | "optional": true, 1130 | "os": [ 1131 | "darwin" 1132 | ], 1133 | "engines": { 1134 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1135 | } 1136 | }, 1137 | "node_modules/lodash": { 1138 | "version": "4.17.21", 1139 | "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", 1140 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 1141 | }, 1142 | "node_modules/lodash-es": { 1143 | "version": "4.17.21", 1144 | "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", 1145 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 1146 | }, 1147 | "node_modules/lodash-unified": { 1148 | "version": "1.0.3", 1149 | "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", 1150 | "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", 1151 | "peerDependencies": { 1152 | "@types/lodash-es": "*", 1153 | "lodash": "*", 1154 | "lodash-es": "*" 1155 | } 1156 | }, 1157 | "node_modules/magic-string": { 1158 | "version": "0.30.17", 1159 | "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", 1160 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1161 | "dependencies": { 1162 | "@jridgewell/sourcemap-codec": "^1.5.0" 1163 | } 1164 | }, 1165 | "node_modules/memoize-one": { 1166 | "version": "6.0.0", 1167 | "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", 1168 | "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" 1169 | }, 1170 | "node_modules/mime-db": { 1171 | "version": "1.52.0", 1172 | "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", 1173 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 1174 | "engines": { 1175 | "node": ">= 0.6" 1176 | } 1177 | }, 1178 | "node_modules/mime-types": { 1179 | "version": "2.1.35", 1180 | "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", 1181 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1182 | "dependencies": { 1183 | "mime-db": "1.52.0" 1184 | }, 1185 | "engines": { 1186 | "node": ">= 0.6" 1187 | } 1188 | }, 1189 | "node_modules/nanoid": { 1190 | "version": "3.3.8", 1191 | "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.8.tgz", 1192 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 1193 | "funding": [ 1194 | { 1195 | "type": "github", 1196 | "url": "https://github.com/sponsors/ai" 1197 | } 1198 | ], 1199 | "bin": { 1200 | "nanoid": "bin/nanoid.cjs" 1201 | }, 1202 | "engines": { 1203 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1204 | } 1205 | }, 1206 | "node_modules/normalize-wheel-es": { 1207 | "version": "1.2.0", 1208 | "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", 1209 | "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" 1210 | }, 1211 | "node_modules/picocolors": { 1212 | "version": "1.1.1", 1213 | "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", 1214 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 1215 | }, 1216 | "node_modules/postcss": { 1217 | "version": "8.4.49", 1218 | "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", 1219 | "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", 1220 | "funding": [ 1221 | { 1222 | "type": "opencollective", 1223 | "url": "https://opencollective.com/postcss/" 1224 | }, 1225 | { 1226 | "type": "tidelift", 1227 | "url": "https://tidelift.com/funding/github/npm/postcss" 1228 | }, 1229 | { 1230 | "type": "github", 1231 | "url": "https://github.com/sponsors/ai" 1232 | } 1233 | ], 1234 | "dependencies": { 1235 | "nanoid": "^3.3.7", 1236 | "picocolors": "^1.1.1", 1237 | "source-map-js": "^1.2.1" 1238 | }, 1239 | "engines": { 1240 | "node": "^10 || ^12 || >=14" 1241 | } 1242 | }, 1243 | "node_modules/proxy-from-env": { 1244 | "version": "1.1.0", 1245 | "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 1246 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 1247 | }, 1248 | "node_modules/rollup": { 1249 | "version": "4.29.1", 1250 | "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.29.1.tgz", 1251 | "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==", 1252 | "dev": true, 1253 | "dependencies": { 1254 | "@types/estree": "1.0.6" 1255 | }, 1256 | "bin": { 1257 | "rollup": "dist/bin/rollup" 1258 | }, 1259 | "engines": { 1260 | "node": ">=18.0.0", 1261 | "npm": ">=8.0.0" 1262 | }, 1263 | "optionalDependencies": { 1264 | "@rollup/rollup-android-arm-eabi": "4.29.1", 1265 | "@rollup/rollup-android-arm64": "4.29.1", 1266 | "@rollup/rollup-darwin-arm64": "4.29.1", 1267 | "@rollup/rollup-darwin-x64": "4.29.1", 1268 | "@rollup/rollup-freebsd-arm64": "4.29.1", 1269 | "@rollup/rollup-freebsd-x64": "4.29.1", 1270 | "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", 1271 | "@rollup/rollup-linux-arm-musleabihf": "4.29.1", 1272 | "@rollup/rollup-linux-arm64-gnu": "4.29.1", 1273 | "@rollup/rollup-linux-arm64-musl": "4.29.1", 1274 | "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", 1275 | "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", 1276 | "@rollup/rollup-linux-riscv64-gnu": "4.29.1", 1277 | "@rollup/rollup-linux-s390x-gnu": "4.29.1", 1278 | "@rollup/rollup-linux-x64-gnu": "4.29.1", 1279 | "@rollup/rollup-linux-x64-musl": "4.29.1", 1280 | "@rollup/rollup-win32-arm64-msvc": "4.29.1", 1281 | "@rollup/rollup-win32-ia32-msvc": "4.29.1", 1282 | "@rollup/rollup-win32-x64-msvc": "4.29.1", 1283 | "fsevents": "~2.3.2" 1284 | } 1285 | }, 1286 | "node_modules/source-map-js": { 1287 | "version": "1.2.1", 1288 | "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", 1289 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1290 | "engines": { 1291 | "node": ">=0.10.0" 1292 | } 1293 | }, 1294 | "node_modules/vite": { 1295 | "version": "6.0.5", 1296 | "resolved": "https://registry.npmmirror.com/vite/-/vite-6.0.5.tgz", 1297 | "integrity": "sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==", 1298 | "dev": true, 1299 | "dependencies": { 1300 | "esbuild": "0.24.0", 1301 | "postcss": "^8.4.49", 1302 | "rollup": "^4.23.0" 1303 | }, 1304 | "bin": { 1305 | "vite": "bin/vite.js" 1306 | }, 1307 | "engines": { 1308 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1309 | }, 1310 | "funding": { 1311 | "url": "https://github.com/vitejs/vite?sponsor=1" 1312 | }, 1313 | "optionalDependencies": { 1314 | "fsevents": "~2.3.3" 1315 | }, 1316 | "peerDependencies": { 1317 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1318 | "jiti": ">=1.21.0", 1319 | "less": "*", 1320 | "lightningcss": "^1.21.0", 1321 | "sass": "*", 1322 | "sass-embedded": "*", 1323 | "stylus": "*", 1324 | "sugarss": "*", 1325 | "terser": "^5.16.0", 1326 | "tsx": "^4.8.1", 1327 | "yaml": "^2.4.2" 1328 | }, 1329 | "peerDependenciesMeta": { 1330 | "@types/node": { 1331 | "optional": true 1332 | }, 1333 | "jiti": { 1334 | "optional": true 1335 | }, 1336 | "less": { 1337 | "optional": true 1338 | }, 1339 | "lightningcss": { 1340 | "optional": true 1341 | }, 1342 | "sass": { 1343 | "optional": true 1344 | }, 1345 | "sass-embedded": { 1346 | "optional": true 1347 | }, 1348 | "stylus": { 1349 | "optional": true 1350 | }, 1351 | "sugarss": { 1352 | "optional": true 1353 | }, 1354 | "terser": { 1355 | "optional": true 1356 | }, 1357 | "tsx": { 1358 | "optional": true 1359 | }, 1360 | "yaml": { 1361 | "optional": true 1362 | } 1363 | } 1364 | }, 1365 | "node_modules/vue": { 1366 | "version": "3.5.13", 1367 | "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", 1368 | "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", 1369 | "dependencies": { 1370 | "@vue/compiler-dom": "3.5.13", 1371 | "@vue/compiler-sfc": "3.5.13", 1372 | "@vue/runtime-dom": "3.5.13", 1373 | "@vue/server-renderer": "3.5.13", 1374 | "@vue/shared": "3.5.13" 1375 | }, 1376 | "peerDependencies": { 1377 | "typescript": "*" 1378 | }, 1379 | "peerDependenciesMeta": { 1380 | "typescript": { 1381 | "optional": true 1382 | } 1383 | } 1384 | } 1385 | } 1386 | } 1387 | -------------------------------------------------------------------------------- /manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manager", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "axios": "^1.7.9", 13 | "element-plus": "^2.9.1", 14 | "vue": "^3.5.13" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^5.2.1", 18 | "vite": "^6.0.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /manager/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujinhong/redview/00c30bd99a3ca3670de990d7f49052d8c22f9e1e/manager/public/favicon.ico -------------------------------------------------------------------------------- /manager/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manager/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /manager/src/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const service = axios.create({ 4 | // axios中请求配置有baseURL选项,表示请求URL公共部分 5 | baseURL: "dev-api", 6 | // 超时 7 | timeout: 10000 8 | }) 9 | 10 | /** 基本请求 */ 11 | export const searchAction = (url, params) => { 12 | return service.get(url, { params }) 13 | } 14 | 15 | export const jsonAction = (url, params) => { 16 | return service.post(url, params) 17 | } 18 | 19 | export const formAction = (url, params) => { 20 | return service.post(url, params, { 21 | headers: { 22 | 'Content-Type': 'multipart/form-data' 23 | } 24 | }) 25 | } 26 | export const updateAction = (url, params) => { 27 | return service.put(url, params) 28 | } 29 | export const deleteAction = (url, params) => { 30 | return service.delete(url, params) 31 | } 32 | 33 | 34 | /** 35 | * 业务请求 36 | */ 37 | 38 | export const getBentos = (params) => { 39 | return searchAction('/bento/list', params) 40 | } 41 | 42 | export const delBento = (id) => { 43 | return deleteAction('/bento/del', { id }) 44 | } 45 | export const getBento = (id) => { 46 | return searchAction('/bento/get', { id }) 47 | } 48 | 49 | // 数据源 50 | export const getDatasources = (params) => { 51 | return searchAction('/ds/list', params) 52 | } 53 | export const insertDatasource = (data) => { 54 | console.log(data) 55 | return jsonAction('/ds', data) 56 | } 57 | export const updateDatasource = (data) => { 58 | return updateAction('/ds', data) 59 | } 60 | export const deleteDatasource = (id) => { 61 | return deleteAction('/ds/' + id) 62 | } 63 | export const tryDatasource = (id) => { 64 | return jsonAction('/ds/try', { id }) 65 | } -------------------------------------------------------------------------------- /manager/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manager/src/components/BentoList.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 103 | 104 | -------------------------------------------------------------------------------- /manager/src/components/SourceList.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 157 | 158 | -------------------------------------------------------------------------------- /manager/src/components/bento/CmpBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /manager/src/components/bento/JsonBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /manager/src/components/bento/SqlBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /manager/src/components/datasource/SourceItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /manager/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | import ElementPlus from 'element-plus' 6 | import 'element-plus/dist/index.css' 7 | 8 | const app = createApp(App) 9 | app.use(ElementPlus) 10 | 11 | app.mount('#app') 12 | -------------------------------------------------------------------------------- /manager/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | flex-direction: column; 29 | place-items: center; 30 | min-width: 320px; 31 | height: 100vh; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | #app { 64 | width: calc( 100% - 4rem); 65 | height: calc( 100% - 4rem); 66 | padding: 2rem; 67 | } 68 | 69 | @media (prefers-color-scheme: light) { 70 | :root { 71 | color: #213547; 72 | background-color: #ffffff; 73 | } 74 | a:hover { 75 | color: #747bff; 76 | } 77 | button { 78 | background-color: #f9f9f9; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /manager/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | base: '/manager/', 8 | build: { 9 | outDir: 'dist', 10 | }, 11 | proxy: { 12 | '/dev-api': { 13 | target: 'https://localhost:20000', 14 | changeOrigin: true, 15 | rewrite: (p) => p.replace(/^\/dev-api/, '') 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /server/dockerfile: -------------------------------------------------------------------------------- 1 | # 基础镜像 2 | FROM openjdk:8-jre 3 | # 挂载目录 4 | VOLUME /home/red 5 | # 创建目录 6 | RUN mkdir -p /home/red 7 | # 指定路径 8 | WORKDIR /home/red 9 | # 复制jar文件到路径 10 | COPY ./target/view.jar /home/red/view.jar 11 | # 启动网关服务 12 | ENTRYPOINT ["java","-jar","view.jar"] -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.red 6 | view 7 | 0.0.1 8 | view 9 | Multi DataSource Data Server 10 | 11 | 1.8 12 | UTF-8 13 | UTF-8 14 | 2.6.13 15 | 2021.0.5.0 16 | true 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | 25 | com.alibaba.cloud 26 | spring-cloud-starter-alibaba-nacos-discovery 27 | 28 | 29 | com.alibaba.cloud 30 | spring-cloud-starter-alibaba-nacos-config 31 | 32 | 33 | 34 | com.alibaba 35 | druid-spring-boot-starter 36 | 1.2.16 37 | 38 | 39 | com.baomidou 40 | dynamic-datasource-spring-boot-starter 41 | 3.5.2 42 | 43 | 44 | com.baomidou 45 | mybatis-plus-generator 46 | 3.5.2 47 | 48 | 49 | com.baomidou 50 | mybatis-plus-boot-starter 51 | 3.5.2 52 | 53 | 54 | 55 | org.xerial 56 | sqlite-jdbc 57 | 3.32.3.3 58 | 59 | 60 | com.microsoft.sqlserver 61 | mssql-jdbc 62 | runtime 63 | 64 | 65 | com.mysql 66 | mysql-connector-j 67 | runtime 68 | 69 | 70 | com.oracle.database.jdbc 71 | ojdbc8 72 | runtime 73 | 74 | 75 | org.postgresql 76 | postgresql 77 | runtime 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-starter-test 82 | test 83 | 84 | 85 | org.projectlombok 86 | lombok 87 | provided 88 | 89 | 90 | com.alibaba 91 | fastjson 92 | 1.2.83 93 | 94 | 95 | 96 | 97 | 98 | 99 | org.springframework.boot 100 | spring-boot-dependencies 101 | ${spring-boot.version} 102 | pom 103 | import 104 | 105 | 106 | com.alibaba.cloud 107 | spring-cloud-alibaba-dependencies 108 | ${spring-cloud-alibaba.version} 109 | pom 110 | import 111 | 112 | 113 | 114 | 115 | 116 | ${project.artifactId} 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-compiler-plugin 121 | 3.8.1 122 | 123 | 1.8 124 | 1.8 125 | UTF-8 126 | 127 | 128 | 129 | org.springframework.boot 130 | spring-boot-maven-plugin 131 | ${spring-boot.version} 132 | 133 | 134 | 135 | repackage 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/ViewApplication.java: -------------------------------------------------------------------------------- 1 | package com.red.view; 2 | 3 | import org.mybatis.spring.annotation.MapperScan; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | @MapperScan("com.red.view.mapper") 9 | public class ViewApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ViewApplication.class, args); 13 | System.out.println("数据视图启动成功。"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/config/InitGenerator.java: -------------------------------------------------------------------------------- 1 | package com.red.view.config; 2 | 3 | /** 4 | * @author pjh 5 | * @created 2024/7/22 6 | */ 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.ApplicationArguments; 9 | import org.springframework.boot.ApplicationRunner; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.io.File; 13 | 14 | @Component 15 | public class InitGenerator implements ApplicationRunner { 16 | 17 | 18 | @Value("${spring.file.local.path}") 19 | private String outputPath; 20 | 21 | 22 | @Override 23 | public void run(ApplicationArguments args) throws Exception { 24 | 25 | //创建生成目录 26 | File fileOutPath = new File(outputPath); 27 | if (!fileOutPath.exists()) { 28 | fileOutPath.mkdirs(); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/config/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package com.red.view.config; 2 | 3 | /** 4 | * @author pjh 5 | * @created 2024/7/30 6 | */ 7 | import com.fasterxml.jackson.annotation.JsonInclude; 8 | import com.fasterxml.jackson.core.JsonGenerator; 9 | import com.fasterxml.jackson.databind.*; 10 | import com.fasterxml.jackson.databind.module.SimpleModule; 11 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 12 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; 13 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 14 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; 15 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; 16 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 17 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; 18 | import org.springframework.context.annotation.Bean; 19 | import org.springframework.context.annotation.Configuration; 20 | import org.springframework.context.annotation.Primary; 21 | 22 | import java.io.IOException; 23 | import java.text.SimpleDateFormat; 24 | import java.time.LocalDate; 25 | import java.time.LocalDateTime; 26 | import java.time.LocalTime; 27 | import java.time.format.DateTimeFormatter; 28 | 29 | 30 | @Configuration 31 | public class JacksonConfig { 32 | 33 | 34 | @Bean 35 | @Primary 36 | public ObjectMapper objectMapper() { 37 | ObjectMapper objectMapper = new ObjectMapper(); 38 | //处理bigDecimal 39 | objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN); 40 | objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS); 41 | //处理失败 42 | objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); 43 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 44 | objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); 45 | objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_CREATOR_PROPERTIES, false); 46 | //默认的处理日期时间格式 47 | objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 48 | JavaTimeModule javaTimeModule = new JavaTimeModule(); 49 | javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 50 | javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 51 | javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 52 | javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); 53 | javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); 54 | javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); 55 | objectMapper.registerModule(javaTimeModule); 56 | //处理hutools的特殊空值类型 57 | // SimpleModule module = new SimpleModule(); 58 | // module.addSerializer(JSONNULL.class, new JsonSerializer() { 59 | // @Override 60 | // public void serialize(JSONNull jsonNull, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { 61 | // jsonGenerator.writeNull(); 62 | // } 63 | // }); 64 | // objectMapper.registerModule(module); 65 | 66 | objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); 67 | 68 | return objectMapper; 69 | } 70 | } -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.red.view.config; 2 | 3 | /** 4 | * 增加对外发布文件夹 5 | * @author pjh 6 | * @created 2024/7/22 7 | */ 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | 13 | import java.io.File; 14 | 15 | @Configuration 16 | public class WebConfig implements WebMvcConfigurer { 17 | 18 | @Value("${spring.file.local.path}") 19 | private String outputPath; 20 | 21 | @Override 22 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 23 | registry.addResourceHandler("/file/**") 24 | .addResourceLocations("file:"+outputPath+ File.separator); 25 | } 26 | } -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/controller/BentoController.java: -------------------------------------------------------------------------------- 1 | package com.red.view.controller; 2 | 3 | import com.red.view.entity.AjaxResult; 4 | import com.red.view.entity.bentos.BentoJsonEntity; 5 | import com.red.view.entity.bentos.BentoSqlEntity; 6 | import com.red.view.service.bentos.IBentoJsonService; 7 | import com.red.view.service.bentos.IBentoSqlService; 8 | import com.red.view.service.IViewJDBC; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * 更新配置 17 | */ 18 | @RestController 19 | @RequestMapping("/bento") 20 | public class BentoController { 21 | 22 | @Autowired 23 | IViewJDBC dynamicJDBC; 24 | @Autowired 25 | IBentoSqlService bentoSqlService; 26 | @Autowired 27 | IBentoJsonService bentoJsonService; 28 | 29 | @GetMapping("/list") 30 | public AjaxResult list(){ 31 | List result = new ArrayList<>(); 32 | List listSql = bentoSqlService.list(); 33 | result.addAll(listSql); 34 | List listJson = bentoJsonService.list(); 35 | result.addAll(listJson); 36 | return AjaxResult.ok().setData(result); 37 | } 38 | @GetMapping("/{id}") 39 | public AjaxResult oneById(@PathVariable("id") Integer id){ 40 | return AjaxResult.ok(); 41 | } 42 | 43 | @PostMapping 44 | public AjaxResult insert(@RequestBody BentoSqlEntity entity ){ 45 | return AjaxResult.ok(); 46 | } 47 | 48 | @DeleteMapping("/{id}") 49 | public AjaxResult deleteOne(@PathVariable("id") Integer id){ 50 | return AjaxResult.ok(); 51 | } 52 | 53 | @PutMapping 54 | public AjaxResult update(@RequestBody BentoSqlEntity entity){ 55 | return AjaxResult.ok(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/controller/DataSourceController.java: -------------------------------------------------------------------------------- 1 | package com.red.view.controller; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 4 | import com.red.view.entity.AjaxResult; 5 | import com.red.view.entity.DataSourceEntity; 6 | import com.red.view.service.IDataSourceService; 7 | import com.red.view.service.IViewJDBC; 8 | import com.red.view.util.DateUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.util.Date; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 更新配置 18 | */ 19 | @RestController 20 | @RequestMapping("/ds") 21 | public class DataSourceController { 22 | 23 | @Autowired 24 | IViewJDBC dynamicJDBC; 25 | @Autowired 26 | IDataSourceService dataSourceService; 27 | @GetMapping("/ping") 28 | public AjaxResult ping(){ 29 | return AjaxResult.ok(); 30 | } 31 | 32 | @GetMapping("/list") 33 | public AjaxResult list(@RequestParam Map params){ 34 | QueryWrapper wrapper = new QueryWrapper<>(); 35 | //遍历params 36 | params.forEach((key, value) -> wrapper.like(key, value)); 37 | List result = dataSourceService.list(wrapper); 38 | return AjaxResult.ok().setData(result); 39 | } 40 | @GetMapping("/{id}") 41 | public AjaxResult oneById(@PathVariable("id") Integer id){ 42 | return AjaxResult.ok(); 43 | } 44 | 45 | @PostMapping 46 | public AjaxResult insert(@RequestBody DataSourceEntity entity ){ 47 | Date now = new Date(); 48 | entity.setCode(entity.getDbType()+"_"+ DateUtil.parseYYYYMMDDHHMMSS(now)); 49 | entity.setCreateTime(now); 50 | entity.setUpdateTime(now); 51 | return dataSourceService.saveDataSource(entity); 52 | } 53 | 54 | @DeleteMapping("/{id}") 55 | public AjaxResult deleteOne(@PathVariable Integer id){ 56 | return dataSourceService.deleteDataSource(id); 57 | } 58 | 59 | @PutMapping 60 | public AjaxResult update(@RequestBody DataSourceEntity entity){ 61 | Date now = new Date(); 62 | entity.setUpdateTime(now); 63 | return dataSourceService.editDataSource(entity); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/controller/TableController.java: -------------------------------------------------------------------------------- 1 | /************************************************************* 2 | * * 3 | * 当视图实在无法满足需求,并且对SQL注入无所谓的情况下,偷偷滴开放此接口 * 4 | * * 5 | *************************************************************/ 6 | 7 | package com.red.view.controller; 8 | 9 | import com.alibaba.druid.pool.DruidDataSource; 10 | import com.red.view.entity.AjaxResult; 11 | import com.red.view.service.IDataSourceService; 12 | import com.red.view.service.IViewJDBC; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.web.bind.annotation.CrossOrigin; 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * @author pjh 24 | * @created 2024/11/4 25 | */ 26 | @RestController 27 | @RequestMapping("/table") 28 | public class TableController { 29 | @Autowired 30 | IViewJDBC dynamicJDBC; 31 | @Autowired 32 | IDataSourceService dataSourceService; 33 | 34 | /** 35 | * 执行sql语句( 不安全的接口) 36 | * @param datasource 数据源 37 | * @param sql sql语句 38 | * @return 39 | */ 40 | @CrossOrigin 41 | //@GetMapping("/sql") 42 | AjaxResult getSqlList(String datasource, String sql){ 43 | if(datasource == null || sql == null){ 44 | return AjaxResult.error("参数错误"); 45 | } 46 | if(sql.toLowerCase().contains("update") || 47 | sql.toLowerCase().contains("delete") || 48 | sql.toLowerCase().contains("insert")|| 49 | sql.toLowerCase().contains("drop") || 50 | sql.toLowerCase().contains("alter") || 51 | sql.toLowerCase().contains("truncate")){ 52 | return AjaxResult.error("不允许执行此操作,仅支持查询语句。"); 53 | } 54 | DruidDataSource dataSource = dataSourceService.getDataSource(datasource); 55 | if(dataSource == null){ 56 | return AjaxResult.error("数据源不存在"); 57 | } 58 | try { 59 | List> data = dynamicJDBC.findList(datasource, sql, null); 60 | return AjaxResult.ok().setData(data); 61 | } catch (Exception e) { 62 | return AjaxResult.error(e.getMessage()); 63 | } 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/controller/ViewController.java: -------------------------------------------------------------------------------- 1 | package com.red.view.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 6 | import com.red.view.entity.*; 7 | import com.red.view.service.*; 8 | import com.red.view.util.JSONUtil; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.*; 11 | 12 | import java.io.IOException; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @RestController 19 | @RequestMapping("/view") 20 | public class ViewController { 21 | 22 | @Autowired 23 | IViewService viewService; 24 | 25 | @CrossOrigin 26 | @GetMapping("/{name}") 27 | public AjaxResult oneByName(@PathVariable("name") String name, @RequestParam Map params) throws Exception { 28 | BentoEntity bentoByName = viewService.getBentoByName(name); 29 | if(bentoByName != null){ 30 | Object data = viewService.getDataByBento(bentoByName, params); 31 | return AjaxResult.ok().setData(data); 32 | }else { 33 | return AjaxResult.error("未知视图"); 34 | } 35 | } 36 | 37 | @CrossOrigin 38 | @GetMapping("/{name}/tree") 39 | public AjaxResult treeByName(@PathVariable("name") String name, @RequestParam String field_name_id,@RequestParam String field_name_pid, @RequestParam Map params) throws Exception { 40 | 41 | BentoEntity bentoByName = viewService.getBentoByName(name); 42 | if(bentoByName != null){ 43 | Object data = viewService.getDataByBento(bentoByName, params); 44 | if(data instanceof List) { 45 | List list = (List) data; 46 | // id是json对象中的id字段名,pid是json对象中的上级id字段名,将list转为树形结构 47 | List roots = JSONUtil.convertListToTree(list, field_name_id, field_name_pid); 48 | return AjaxResult.ok().setData(roots); 49 | } 50 | return AjaxResult.ok().setData(data); 51 | }else { 52 | return AjaxResult.error("未知视图"); 53 | } 54 | 55 | 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/AjaxResult.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity; 2 | 3 | import lombok.Getter; 4 | 5 | import java.io.Serializable; 6 | 7 | @Getter 8 | public class AjaxResult implements Serializable { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | private boolean success; 13 | private int code; 14 | private String msg; 15 | private Object data; 16 | private long timestamp; 17 | 18 | private AjaxResult() {} 19 | 20 | public static AjaxResult error() { 21 | return error(null); 22 | } 23 | 24 | public static AjaxResult error(String message) { 25 | return error(null, message); 26 | } 27 | 28 | public static AjaxResult error(Integer code, String message) { 29 | if(code == null) { 30 | code = 500; 31 | } 32 | if(message == null) { 33 | message = "服务器内部错误"; 34 | } 35 | return build(code, false, message); 36 | } 37 | 38 | public static AjaxResult ok() { 39 | return ok(null); 40 | } 41 | 42 | public static AjaxResult ok(String message) { 43 | return ok(null, message); 44 | } 45 | 46 | public static AjaxResult ok(Integer code, String message) { 47 | if(code == null) { 48 | code = 200; 49 | } 50 | if(message == null) { 51 | message = "操作成功"; 52 | } 53 | return build(code, true, message); 54 | } 55 | 56 | public static AjaxResult build(int code, boolean success, String message) { 57 | return new AjaxResult() 58 | .setCode(code) 59 | .setSuccess(success) 60 | .setMessage(message) 61 | .setTimestamp(System.currentTimeMillis()); 62 | } 63 | 64 | public AjaxResult setCode(int code) { 65 | this.code = code; 66 | return this; 67 | } 68 | 69 | public AjaxResult setSuccess(boolean success) { 70 | this.success = success; 71 | return this; 72 | } 73 | public AjaxResult setMessage(String msg) { 74 | this.msg = msg; 75 | return this; 76 | } 77 | 78 | public AjaxResult setData(Object data) { 79 | this.data = data; 80 | return this; 81 | } 82 | 83 | public AjaxResult setTimestamp(long timestamp) { 84 | this.timestamp = timestamp; 85 | return this; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/BentoEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import lombok.Data; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | /** 9 | * @author pjh 10 | * @created 2024/8/27 11 | */ 12 | @Data 13 | public abstract class BentoEntity { 14 | 15 | @TableId(value = "id", type = IdType.AUTO) 16 | public Integer id; 17 | public String name; 18 | public String title; 19 | /** 20 | * 创建日期 21 | */ 22 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 23 | private java.util.Date createTime; 24 | 25 | /** 26 | * 更新日期 27 | */ 28 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 29 | private java.util.Date updateTime; 30 | 31 | 32 | public abstract String getType(); 33 | } 34 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/BentoTreeEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | import org.springframework.format.annotation.DateTimeFormat; 8 | 9 | /** 10 | * 树型视图 11 | * @author pjh 12 | * @created 2024/8/26 13 | */ 14 | @Data 15 | @TableName("t_bento_tree") 16 | public class BentoTreeEntity extends BentoEntity{ 17 | 18 | @Override 19 | public String getType() { 20 | return "TREE"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/DataSourceEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableField; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | import lombok.experimental.Accessors; 10 | import org.springframework.format.annotation.DateTimeFormat; 11 | 12 | /** 13 | * 多数据源实体类 14 | */ 15 | @Data 16 | @TableName("t_datasource") 17 | @EqualsAndHashCode(callSuper = false) 18 | @Accessors(chain = true) 19 | public class DataSourceEntity { 20 | 21 | /** 22 | * id 23 | */ 24 | @TableId(type = IdType.AUTO) 25 | private Integer id; 26 | /** 27 | * 数据源编码 28 | */ 29 | private String code; 30 | /** 31 | * 数据源名称 32 | */ 33 | private String name; 34 | /** 35 | * 描述 36 | */ 37 | private String remark; 38 | /** 39 | * 数据库类型 40 | */ 41 | private String dbType; 42 | /** 43 | * 驱动类 44 | */ 45 | private String dbDriver; 46 | /** 47 | * 数据源地址 48 | */ 49 | private String dbUrl; 50 | /** 51 | * 数据库名称 52 | */ 53 | private String dbInstance; 54 | /** 55 | * 用户名 56 | */ 57 | private String dbUsername; 58 | /** 59 | * 密码 60 | */ 61 | private String dbPassword; 62 | /** 63 | * 端口 64 | */ 65 | private Integer dbPort; 66 | /** 67 | * IP地址 68 | */ 69 | private String dbHost; 70 | 71 | /** 72 | * 启用状态 73 | */ 74 | private Integer online; 75 | 76 | /** 77 | * 删除标志(0代表存在 1代表删除) 78 | */ 79 | private Integer delFlag; 80 | 81 | /** 82 | * 创建日期 83 | */ 84 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 85 | private java.util.Date createTime; 86 | 87 | /** 88 | * 更新日期 89 | */ 90 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 91 | private java.util.Date updateTime; 92 | /** 93 | * 连接状态 94 | */ 95 | @TableField(exist = false) 96 | private Integer status; 97 | 98 | } 99 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/bentos/BentoFileEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity.bentos; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableField; 4 | import com.baomidou.mybatisplus.annotation.TableName; 5 | import com.red.view.entity.BentoEntity; 6 | import lombok.Data; 7 | 8 | /** 9 | * 文件视图 10 | * @author pjh 11 | * @created 2024/8/27 12 | */ 13 | @Data 14 | @TableName("t_bento_file") 15 | public class BentoFileEntity extends BentoEntity { 16 | public String fileId; //文件id uuid 32位 文件夹名称 17 | public String fileName; //文件名称(包含扩展名) 18 | public String fileType; // 文件类型 19 | @Override 20 | public String getType() { 21 | return "FILE"; 22 | } 23 | 24 | // 文件发布路径 25 | @TableField(exist = false) 26 | String url; 27 | } 28 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/bentos/BentoGroupEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity.bentos; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.red.view.entity.BentoEntity; 7 | import lombok.Data; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | /** 11 | * 组合视图 12 | * @author pjh 13 | * @created 2024/8/26 14 | */ 15 | @Data 16 | @TableName("t_bento_group") 17 | public class BentoGroupEntity extends BentoEntity { 18 | 19 | String statement; 20 | 21 | @Override 22 | public String getType() { 23 | return "GROUP"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/bentos/BentoJsonEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity.bentos; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.red.view.entity.BentoEntity; 7 | import lombok.Data; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | /** 11 | * @author pjh 12 | * @created 2024/7/2 13 | */ 14 | @Data 15 | @TableName("t_bento_json") 16 | public class BentoJsonEntity extends BentoEntity { 17 | 18 | 19 | String json; 20 | 21 | 22 | @Override 23 | public String getType() { 24 | return "JSON"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/bentos/BentoSqlEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity.bentos; 2 | 3 | 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import com.red.view.entity.BentoEntity; 8 | import lombok.Data; 9 | import org.springframework.format.annotation.DateTimeFormat; 10 | 11 | /** 12 | * @author pjh 13 | * @created 2024/7/2 14 | */ 15 | @Data 16 | @TableName("t_bento_sql") 17 | public class BentoSqlEntity extends BentoEntity { 18 | 19 | String dataSource; 20 | String statement; 21 | 22 | @Override 23 | public String getType() { 24 | return "SQL"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/entity/bentos/BentoTreeEntity.java: -------------------------------------------------------------------------------- 1 | package com.red.view.entity.bentos; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import com.red.view.entity.BentoEntity; 7 | import lombok.Data; 8 | import org.springframework.format.annotation.DateTimeFormat; 9 | 10 | /** 11 | * 树型视图 12 | * @author pjh 13 | * @created 2024/8/26 14 | */ 15 | @Data 16 | @TableName("t_bento_tree") 17 | public class BentoTreeEntity extends BentoEntity { 18 | 19 | @Override 20 | public String getType() { 21 | return "TREE"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/mapper/BentoFileMapper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.red.view.entity.bentos.BentoFileEntity; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author pjh 10 | * @created 2024/7/2 11 | */ 12 | @Repository 13 | public interface BentoFileMapper extends BaseMapper { 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/mapper/BentoGroupMapper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.red.view.entity.bentos.BentoGroupEntity; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author pjh 10 | * @created 2024/7/2 11 | */ 12 | @Repository 13 | public interface BentoGroupMapper extends BaseMapper { 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/mapper/BentoJsonMapper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.red.view.entity.bentos.BentoJsonEntity; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author pjh 10 | * @created 2024/7/2 11 | */ 12 | @Repository 13 | public interface BentoJsonMapper extends BaseMapper { 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/mapper/BentoSqlMapper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.mapper; 2 | 3 | 4 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 5 | import com.red.view.entity.bentos.BentoSqlEntity; 6 | import org.springframework.stereotype.Repository; 7 | 8 | /** 9 | * @author pjh 10 | * @created 2024/7/2 11 | */ 12 | @Repository 13 | public interface BentoSqlMapper extends BaseMapper { 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/mapper/DataSourceMapper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.red.view.entity.DataSourceEntity; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface DataSourceMapper extends BaseMapper { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/IBentoService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.red.view.entity.BentoEntity; 5 | 6 | import java.util.Map; 7 | 8 | public interface IBentoService extends IService { 9 | 10 | Object getData(T entity , Map prams) throws Exception; 11 | 12 | Object setCache(T entity, Map prams) throws Exception; 13 | } 14 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/IDataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import com.baomidou.mybatisplus.extension.service.IService; 5 | import com.red.view.entity.AjaxResult; 6 | import com.red.view.entity.DataSourceEntity; 7 | 8 | public interface IDataSourceService extends IService { 9 | 10 | /** 11 | * 添加数据源 12 | * @param sysDataSource 13 | * @return 14 | */ 15 | AjaxResult saveDataSource(DataSourceEntity sysDataSource); 16 | 17 | /** 18 | * 修改数据源 19 | * @param sysDataSource 20 | * @return 21 | */ 22 | AjaxResult editDataSource(DataSourceEntity sysDataSource); 23 | 24 | 25 | /** 26 | * 删除数据源 27 | * @param id 28 | * @return 29 | */ 30 | AjaxResult deleteDataSource(Integer id); 31 | 32 | void addDynamicDataSource(DataSourceEntity sysDataSource, String dbPassword); 33 | 34 | void removeDynamicDataSource(String code); 35 | 36 | long checkDbCode(String dbCode); 37 | 38 | DruidDataSource getDataSource(String dbKey); 39 | 40 | void putDataSource(String dbKey, DruidDataSource db); 41 | 42 | void cleanAllCache(); 43 | 44 | void removeCache(String dbKey); 45 | } 46 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/IGoviewConvert.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service; 2 | 3 | public interface IGoviewConvert { 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/IViewJDBC.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service; 2 | 3 | import java.sql.SQLException; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public interface IViewJDBC { 8 | int update(final String dbKey, String sql, Object... param); 9 | int updateByMap(final String dbKey, String sql, Map data) throws SQLException; 10 | Map findOne(final String dbKey, String sql, Object... param); 11 | Map findOneByMap(final String dbKey, String sql, Map data); 12 | Object findOne(final String dbKey, String sql, Class clazz, Object... param); 13 | Object findOneByMap(final String dbKey, String sql, Class clazz, Map data); 14 | List> findList(final String dbKey, String sql, Object... param); 15 | Map queryCount(String dbKey, String sql, Map param); 16 | List> findListByNamedParam(final String dbKey, String sql, Map param); 17 | List> findListByMap(final String dbKey, String sql, Map data); 18 | List findList(final String dbKey, String sql, Class clazz, Object... param); 19 | List findListByMap(final String dbKey, String sql, Class clazz, Map data); 20 | List findListEntities(final String dbKey, String sql, Class clazz, Object... param); 21 | List findListEntitiesByMap(final String dbKey, String sql, Class clazz, Map data); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/IViewService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service; 2 | 3 | import com.red.view.entity.BentoEntity; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * @author pjh 9 | * @created 2024/8/27 10 | */ 11 | public interface IViewService { 12 | 13 | BentoEntity getBentoByName(String name) throws Exception; 14 | 15 | Object getDataByBento(BentoEntity view, Map prams) throws Exception; 16 | } 17 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/bentos/IBentoFileService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.bentos; 2 | 3 | import com.red.view.entity.bentos.BentoFileEntity; 4 | import com.red.view.service.IBentoService; 5 | 6 | public interface IBentoFileService extends IBentoService { 7 | 8 | 9 | } 10 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/bentos/IBentoGroupService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.bentos; 2 | 3 | import com.red.view.entity.BentoEntity; 4 | import com.red.view.entity.bentos.BentoGroupEntity; 5 | import com.red.view.service.IBentoService; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author pjh 11 | * @created 2024/7/2 12 | */ 13 | public interface IBentoGroupService extends IBentoService { 14 | 15 | 16 | List getViews(BentoGroupEntity entity); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/bentos/IBentoJsonService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.bentos; 2 | 3 | import com.red.view.entity.bentos.BentoJsonEntity; 4 | import com.red.view.service.IBentoService; 5 | 6 | /** 7 | * @author pjh 8 | * @created 2024/7/2 9 | */ 10 | public interface IBentoJsonService extends IBentoService { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/bentos/IBentoSqlService.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.bentos; 2 | 3 | import com.red.view.entity.bentos.BentoSqlEntity; 4 | import com.red.view.service.IBentoService; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | /** 10 | * @author pjh 11 | * @created 2024/7/2 12 | */ 13 | public interface IBentoSqlService extends IBentoService { 14 | 15 | List> getData(BentoSqlEntity view, Map params); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/BentoFileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.red.view.entity.bentos.BentoFileEntity; 5 | import com.red.view.mapper.BentoFileMapper; 6 | import com.red.view.service.bentos.IBentoFileService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * @author pjh 13 | * @created 2024/8/27 14 | */ 15 | @Service 16 | public class BentoFileServiceImpl extends ServiceImpl implements IBentoFileService { 17 | @Override 18 | public Object getData(BentoFileEntity entity, Map prams) throws Exception { 19 | 20 | entity.setUrl("/file/"+entity.getFileId()+"/"+entity.getFileName()); 21 | 22 | return entity; 23 | } 24 | 25 | @Override 26 | public String setCache(BentoFileEntity entity, Map prams) throws Exception { 27 | 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/BentoGroupServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.red.view.entity.*; 5 | import com.red.view.entity.bentos.BentoGroupEntity; 6 | import com.red.view.mapper.BentoGroupMapper; 7 | import com.red.view.service.bentos.IBentoGroupService; 8 | import com.red.view.service.bentos.IBentoJsonService; 9 | import com.red.view.service.bentos.IBentoSqlService; 10 | import com.red.view.service.IViewService; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.context.annotation.Lazy; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.stream.Collectors; 19 | 20 | /** 21 | * @author pjh 22 | * @created 2024/7/2 23 | */ 24 | @Service 25 | public class BentoGroupServiceImpl extends ServiceImpl implements IBentoGroupService { 26 | 27 | @Autowired 28 | @Lazy 29 | IViewService viewService; 30 | 31 | @Autowired 32 | IBentoSqlService bentoSqlService; 33 | @Autowired 34 | IBentoJsonService bentoJsonService; 35 | 36 | 37 | 38 | 39 | @Override 40 | public List getViews(BentoGroupEntity entity) { 41 | //识别${vw: XXX } 42 | List views = new ArrayList<>(); 43 | List paramNames = getParamNames(entity.getStatement()); 44 | List viewNames = paramNames.stream().filter(name -> name.startsWith("vw:")).collect(Collectors.toList()); 45 | for (String name : viewNames) { 46 | String viewName = name.substring(3).trim(); 47 | try{ 48 | BentoEntity bentoByName = viewService.getBentoByName(viewName); 49 | if(bentoByName != null){ 50 | views.add(bentoByName); 51 | } 52 | }catch (Exception ex){ 53 | ex.printStackTrace(); 54 | } 55 | } 56 | return views; 57 | } 58 | 59 | @Override 60 | public Object getData(BentoGroupEntity entity, Map prams) throws Exception { 61 | List views = getViews(entity); 62 | for (BentoEntity view : views) { 63 | String name = view.getName(); 64 | 65 | 66 | } 67 | return null; 68 | } 69 | 70 | @Override 71 | public String setCache(BentoGroupEntity entity, Map prams) throws Exception { 72 | return null; 73 | } 74 | 75 | public List getParamNames(String statement){ 76 | List paramNames = new ArrayList<>(); 77 | int start = statement.indexOf("${"); 78 | while (start >= 0) { 79 | int end = statement.indexOf('}', start); 80 | if (end > start) { 81 | paramNames.add(statement.substring(start, end + 1)); 82 | } 83 | start = statement.indexOf("${", end); 84 | } 85 | return paramNames; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/BentoJsonServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.red.view.entity.bentos.BentoJsonEntity; 7 | import com.red.view.mapper.BentoJsonMapper; 8 | import com.red.view.service.bentos.IBentoJsonService; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * @author pjh 15 | * @created 2024/7/2 16 | */ 17 | @Service 18 | public class BentoJsonServiceImpl extends ServiceImpl implements IBentoJsonService { 19 | 20 | @Override 21 | public Object getData(BentoJsonEntity view, Map params) throws Exception { 22 | String strJson = view.getJson(); 23 | try { 24 | if (strJson.trim().startsWith("[")) { 25 | return JSON.parseArray(strJson); 26 | } else { 27 | JSONObject jsonObject = JSONObject.parseObject(strJson); 28 | return jsonObject; 29 | } 30 | }catch (Exception ex){ 31 | throw new Exception("JSON格式错误"); 32 | } 33 | } 34 | 35 | @Override 36 | public String setCache(BentoJsonEntity entity, Map prams) throws Exception { 37 | return null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/BentoSqlServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.red.view.entity.bentos.BentoSqlEntity; 5 | import com.red.view.mapper.BentoSqlMapper; 6 | import com.red.view.service.bentos.IBentoSqlService; 7 | import com.red.view.service.IViewJDBC; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * {@code @author} pjh 16 | * {@code @created} 2024/7/2 17 | */ 18 | @Service 19 | public class BentoSqlServiceImpl extends ServiceImpl implements IBentoSqlService { 20 | 21 | @Autowired 22 | IViewJDBC dynamicJDBC; 23 | @Override 24 | public List> getData(BentoSqlEntity view, Map params) { 25 | // Map jdbcParams = new HashMap<>(); 26 | // for(Map.Entry entry : params.entrySet()){ 27 | // if(entry.getValue() ==null || entry.getKey() == null )continue; 28 | // if(entry.getKey().endsWith("_like_right")){ 29 | // jdbcParams.put(entry.getKey().replace("_like_right", ""), entry.getValue().toString()+"%"); 30 | // }else if(entry.getKey().endsWith("_like_left")){ 31 | // jdbcParams.put(entry.getKey().replace("_like_left", ""), "%"+entry.getValue().toString()); 32 | // }else if(entry.getKey().endsWith("_like_all")){ 33 | // jdbcParams.put(entry.getKey().replace("_like_all", ""), "%"+entry.getValue().toString()+"%"); 34 | // }else { 35 | // jdbcParams.put(entry.getKey(), entry.getValue()); 36 | // } 37 | // } 38 | 39 | return dynamicJDBC.findListByMap(view.getDataSource(), view.getStatement(), params); 40 | } 41 | 42 | @Override 43 | public String setCache(BentoSqlEntity entity, Map prams) throws Exception { 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/DataSourceServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; 5 | import com.baomidou.dynamic.datasource.creator.DruidDataSourceCreator; 6 | import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty; 7 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 8 | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 9 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 10 | import com.red.view.entity.AjaxResult; 11 | import com.red.view.entity.DataSourceEntity; 12 | import com.red.view.mapper.DataSourceMapper; 13 | import com.red.view.service.IDataSourceService; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | import javax.sql.DataSource; 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | @Service 22 | public class DataSourceServiceImpl extends ServiceImpl implements IDataSourceService { 23 | 24 | @Autowired 25 | private DruidDataSourceCreator dataSourceCreator; 26 | 27 | 28 | @Autowired 29 | private DataSource dataSource; 30 | 31 | /** 数据源连接池缓存【本地 class缓存 - 不支持分布式】 */ 32 | private static Map dbSources = new HashMap<>(); 33 | 34 | @Override 35 | public AjaxResult saveDataSource(DataSourceEntity sysDataSource) { 36 | try { 37 | long count = checkDbCode(sysDataSource.getCode()); 38 | if (count > 0) { 39 | return AjaxResult.error("数据源编码已存在"); 40 | } 41 | String dbPassword = sysDataSource.getDbPassword(); 42 | 43 | //TODO 加密数据库密码 44 | 45 | 46 | boolean result = save(sysDataSource); 47 | if (result) { 48 | //动态创建数据源 49 | //addDynamicDataSource(sysDataSource, dbPassword); 50 | } 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | return AjaxResult.ok("添加成功!"); 55 | } 56 | 57 | @Override 58 | public AjaxResult editDataSource(DataSourceEntity sysDataSource) { 59 | try { 60 | DataSourceEntity d = getById(sysDataSource.getId()); 61 | removeCache(d.getCode()); 62 | 63 | //TODO 加密数据库密码 64 | 65 | Boolean result=updateById(sysDataSource); 66 | if(result){ 67 | //先删除老的数据源 68 | // removeDynamicDataSource(d.getCode()); 69 | //添加新的数据源 70 | //addDynamicDataSource(sysDataSource,dbPassword); 71 | } 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | } 75 | return AjaxResult.ok("编辑成功!"); 76 | } 77 | 78 | @Override 79 | public AjaxResult deleteDataSource(Integer id) { 80 | DataSourceEntity sysDataSource = getById(id); 81 | removeCache(sysDataSource.getCode()); 82 | removeById(id); 83 | return AjaxResult.ok("删除成功!"); 84 | } 85 | 86 | /** 87 | * 动态添加数据源 【注册mybatis动态数据源】 88 | * 89 | * @param sysDataSource 添加数据源数据对象 90 | * @param dbPassword 未加密的密码 91 | */ 92 | @Override 93 | public void addDynamicDataSource(DataSourceEntity sysDataSource, String dbPassword) { 94 | DataSourceProperty dataSourceProperty = new DataSourceProperty(); 95 | dataSourceProperty.setUrl(sysDataSource.getDbUrl()); 96 | dataSourceProperty.setPassword(dbPassword); 97 | dataSourceProperty.setDriverClassName(sysDataSource.getDbDriver()); 98 | dataSourceProperty.setUsername(sysDataSource.getDbUsername()); 99 | DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; 100 | DataSource dataSource = dataSourceCreator.createDataSource(dataSourceProperty); 101 | try { 102 | ds.addDataSource(sysDataSource.getCode(), dataSource); 103 | } catch (Exception e) { 104 | e.printStackTrace(); 105 | } 106 | } 107 | 108 | /** 109 | * 删除数据源 110 | * @param code 111 | */ 112 | @Override 113 | public void removeDynamicDataSource(String code) { 114 | DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource; 115 | ds.removeDataSource(code); 116 | } 117 | 118 | /** 119 | * 检查数据源编码是否存在 120 | * 121 | * @param dbCode 122 | * @return 123 | */ 124 | @Override 125 | public long checkDbCode(String dbCode) { 126 | QueryWrapper qw = new QueryWrapper(); 127 | qw.lambda().eq(true, DataSourceEntity::getCode, dbCode); 128 | return count(qw); 129 | } 130 | 131 | /** 132 | * 获取数据源【禁止更改】 133 | * 134 | * @param dbSource 135 | * @return 136 | */ 137 | 138 | public DruidDataSource createJdbcDataSource(final DataSourceEntity dbSource) { 139 | DruidDataSource dataSource = new DruidDataSource(); 140 | 141 | String driverClassName = dbSource.getDbDriver(); 142 | String url = dbSource.getDbUrl(); 143 | String dbUser = dbSource.getDbUsername(); 144 | String dbPassword = dbSource.getDbPassword(); 145 | dataSource.setDriverClassName(driverClassName); 146 | dataSource.setUrl(url); 147 | //TODO 根据数据库不同类型确定校验语句 148 | //dataSource.setValidationQuery("SELECT 1 FROM DUAL"); 149 | dataSource.setTestWhileIdle(true); 150 | dataSource.setTestOnBorrow(false); 151 | dataSource.setTestOnReturn(false); 152 | dataSource.setBreakAfterAcquireFailure(false); 153 | dataSource.setConnectionErrorRetryAttempts(3); 154 | dataSource.setUsername(dbUser); 155 | dataSource.setMaxWait(30000); 156 | // 设置当数据库连接的套接字读取超时时,不销毁连接 157 | dataSource.setKillWhenSocketReadTimeout(false); 158 | dataSource.setSocketTimeout(1000*60*10); //sql执行10分钟超时 159 | dataSource.setPassword(dbPassword); 160 | dataSource.setConnectTimeout(1000*60*10); //连接超时10分钟 161 | dataSource.setQueryTimeout(1000*60*10); //查询超时10分钟 162 | dataSource.setLoginTimeout(1000*60*10); //登录超时10分钟 163 | dataSource.setValidationQueryTimeout(1000*60*10); //验证超时10分钟 164 | dataSource.setTransactionQueryTimeout(1000*60*10); //事务超时10分钟 165 | return dataSource; 166 | } 167 | /** 168 | * 通过 dbKey ,获取数据源 169 | * 170 | * @param dbKey 171 | * @return 172 | */ 173 | @Override 174 | public DruidDataSource getDataSource(String dbKey) { 175 | DruidDataSource dds = null; // dbSources.get(dbKey); 176 | if (dds != null && !dds.isClosed()) { 177 | return dds; 178 | }else { 179 | //获取多数据源配置 180 | DataSourceEntity config = getOne(new LambdaQueryWrapper().eq(DataSourceEntity::getCode, dbKey)); 181 | DruidDataSource dataSource = createJdbcDataSource(config); 182 | if(dataSource.isEnable()) { 183 | putDataSource(dbKey, dataSource); 184 | }else { 185 | new Exception("动态数据源连接失败,dbKey:"+dbKey).printStackTrace(); 186 | } 187 | return dataSource; 188 | } 189 | } 190 | 191 | /** 192 | * put 数据源缓存 193 | * 194 | * @param dbKey 195 | * @param db 196 | */ 197 | @Override 198 | public void putDataSource(String dbKey, DruidDataSource db) { 199 | dbSources.put(dbKey, db); 200 | } 201 | 202 | /** 203 | * 清空数据源缓存 204 | */ 205 | @Override 206 | public void cleanAllCache() { 207 | //关闭数据源连接 208 | for(Map.Entry entry : dbSources.entrySet()){ 209 | String dbkey = entry.getKey(); 210 | DruidDataSource druidDataSource = entry.getValue(); 211 | if(druidDataSource!=null && druidDataSource.isEnable()){ 212 | druidDataSource.close(); 213 | } 214 | } 215 | //清空缓存 216 | dbSources.clear(); 217 | } 218 | @Override 219 | public void removeCache(String dbKey) { 220 | //关闭数据源连接 221 | DruidDataSource druidDataSource = dbSources.get(dbKey); 222 | if(druidDataSource!=null && druidDataSource.isEnable()){ 223 | druidDataSource.close(); 224 | } 225 | //清空缓存 226 | dbSources.remove(dbKey); 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/ViewJDBC.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.alibaba.druid.pool.DruidDataSource; 4 | import com.red.view.service.IDataSourceService; 5 | import com.red.view.service.IViewJDBC; 6 | import com.red.view.util.DateUtil; 7 | import com.red.view.util.ObjectHelper; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.jdbc.core.JdbcTemplate; 11 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 12 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 13 | import org.springframework.stereotype.Component; 14 | 15 | import java.sql.SQLException; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | /** 20 | * Spring JDBC 实时数据库访问 21 | * 22 | */ 23 | @Slf4j 24 | @Component 25 | public class ViewJDBC implements IViewJDBC { 26 | 27 | @Autowired 28 | IDataSourceService dbPool; 29 | 30 | 31 | 32 | /** 33 | * 关闭数据库连接池 34 | * 35 | * @param dbKey 36 | * @return 37 | */ 38 | public void closeDbKey(final String dbKey) { 39 | DruidDataSource dataSource = dbPool.getDataSource(dbKey); 40 | try { 41 | if (dataSource != null && !dataSource.isClosed()) { 42 | dataSource.getConnection().commit(); 43 | dataSource.getConnection().close(); 44 | dataSource.close(); 45 | } 46 | } catch (SQLException e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | 51 | 52 | private JdbcTemplate getJdbcTemplate(String dbKey) { 53 | DruidDataSource dataSource = dbPool.getDataSource(dbKey); 54 | return new JdbcTemplate(dataSource); 55 | } 56 | 57 | /** 58 | * 根据数据源获取NamedParameterJdbcTemplate 59 | * @param dbKey 60 | * @return 61 | */ 62 | private NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(String dbKey) { 63 | DruidDataSource dataSource = dbPool.getDataSource(dbKey); 64 | return new NamedParameterJdbcTemplate(dataSource); 65 | } 66 | 67 | @Override 68 | public int update(final String dbKey, String sql, Object... param) { 69 | int effectCount; 70 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 71 | if (param == null || param.length == 0) { 72 | effectCount = jdbcTemplate.update(sql); 73 | } else { 74 | effectCount = jdbcTemplate.update(sql, param); 75 | } 76 | return effectCount; 77 | } 78 | 79 | /** 80 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的Update 81 | * 82 | * @param dbKey 数据源标识 83 | * @param sql 执行sql语句,sql符合NamedParameterJdbc的模板规则 84 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 85 | * @return 86 | */ 87 | @Override 88 | public int updateByMap(final String dbKey, String sql, Map data) throws SQLException { 89 | int effectCount; 90 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 91 | NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource()); 92 | MapSqlParameterSource msps = buildParameters(data); 93 | effectCount = namedParameterJdbcTemplate.update(sql, msps); 94 | return effectCount; 95 | } 96 | @Override 97 | public Map findOne(final String dbKey, String sql, Object... param) { 98 | List> list; 99 | list = findList(dbKey, sql, param); 100 | if (list.isEmpty()) { 101 | log.error("Except one, but not find actually"); 102 | return null; 103 | } 104 | if (list.size() > 1) { 105 | log.error("Except one, but more than one actually"); 106 | } 107 | return list.get(0); 108 | } 109 | 110 | /** 111 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的查询 返回Map 112 | * 113 | * @param dbKey 数据源标识 114 | * @param sql 执行sql语句,sql符合NamedParameterJdbc的模板规则 115 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 116 | * @return 117 | */ 118 | @Override 119 | public Map findOneByMap(final String dbKey, String sql, Map data) { 120 | List> list; 121 | list = findListByMap(dbKey, sql, data); 122 | if (list.isEmpty()) { 123 | log.error("Except one, but not find actually"); 124 | } 125 | if (list.size() > 1) { 126 | log.error("Except one, but more than one actually"); 127 | } 128 | return list.get(0); 129 | } 130 | 131 | /** 132 | * 直接sql查询 根据clazz返回单个实例 133 | * 134 | * @param dbKey 数据源标识 135 | * @param sql 执行sql语句 136 | * @param clazz 返回实例的Class 137 | * @param param 138 | * @return 139 | */ 140 | @Override 141 | public Object findOne(final String dbKey, String sql, Class clazz, Object... param) { 142 | Map map = findOne(dbKey, sql, param); 143 | return ObjectHelper.setAll(clazz, map); 144 | } 145 | 146 | /** 147 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的查询 返回单个实例 148 | * 149 | * @param dbKey 数据源标识 150 | * @param sql 执行sql语句,sql符合NamedParameterJdbc的模板规则 151 | * @param clazz 返回实例的Class 152 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 153 | * @return 154 | */ 155 | @Override 156 | public Object findOneByMap(final String dbKey, String sql, Class clazz, Map data) { 157 | Map map = findOneByMap(dbKey, sql, data); 158 | return ObjectHelper.setAll(clazz, map); 159 | } 160 | @Override 161 | public List> findList(final String dbKey, String sql, Object... param) { 162 | List> list; 163 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 164 | 165 | if (param == null || param.length == 0) { 166 | list = jdbcTemplate.queryForList(sql); 167 | } else { 168 | list = jdbcTemplate.queryForList(sql, param); 169 | } 170 | return list; 171 | } 172 | 173 | /** 174 | * 查询数量 175 | * @param dbKey 176 | * @param sql 177 | * @param param 178 | * @return 179 | */ 180 | @Override 181 | public Map queryCount(String dbKey, String sql, Map param){ 182 | NamedParameterJdbcTemplate npJdbcTemplate = getNamedParameterJdbcTemplate(dbKey); 183 | return npJdbcTemplate.queryForMap(sql, param); 184 | } 185 | 186 | /** 187 | * 查询列表数据 188 | * @param dbKey 189 | * @param sql 190 | * @param param 191 | * @return 192 | */ 193 | @Override 194 | public List> findListByNamedParam(final String dbKey, String sql, Map param) { 195 | NamedParameterJdbcTemplate npJdbcTemplate = getNamedParameterJdbcTemplate(dbKey); 196 | List> list = npJdbcTemplate.queryForList(sql, param); 197 | return list; 198 | } 199 | 200 | /** 201 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的查询 202 | * 203 | * @param dbKey 数据源标识 204 | * @param sql 执行sql语句,sql符合NamedParameterJdbc的模板规则 205 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 206 | * @return 207 | */ 208 | @Override 209 | public List> findListByMap(final String dbKey, String sql, Map data) { 210 | List> list; 211 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 212 | MapSqlParameterSource parameterSource = buildParameters(data); 213 | NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource()); 214 | list = namedParameterJdbcTemplate.queryForList(sql, parameterSource); 215 | return list; 216 | } 217 | 218 | /** 219 | * 此方法只能返回单列,不能返回实体类 220 | * @param dbKey 数据源的key 221 | * @param sql sal 222 | * @param clazz 类 223 | * @param param 参数 224 | * @param 225 | * @return 226 | */ 227 | @Override 228 | public List findList(final String dbKey, String sql, Class clazz, Object... param) { 229 | List list; 230 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 231 | 232 | if (param == null || param.length == 0) { 233 | list = jdbcTemplate.queryForList(sql, clazz); 234 | } else { 235 | list = jdbcTemplate.queryForList(sql, clazz, param); 236 | } 237 | return list; 238 | } 239 | 240 | /** 241 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的查询 返回单列数据list 242 | * 243 | * @param dbKey 数据源标识 244 | * @param sql 执行sql语句,符合NamedParameterJdbc的模板规则 245 | * @param clazz 类型Long、String等 246 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 247 | * @return 248 | */ 249 | @Override 250 | public List findListByMap(final String dbKey, String sql, Class clazz, Map data) { 251 | List list; 252 | JdbcTemplate jdbcTemplate = getJdbcTemplate(dbKey); 253 | MapSqlParameterSource parameterSource = buildParameters(data); 254 | NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate.getDataSource()); 255 | list = namedParameterJdbcTemplate.queryForList(sql, parameterSource, clazz); 256 | return list; 257 | } 258 | 259 | /** 260 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的直接sql查询 返回实体类列表 261 | * 262 | * @param dbKey 数据源标识 263 | * @param sql 执行sql语句,符合NamedParameterJdbc的模板规则 264 | * @param clazz 返回实体类列表的class 265 | * @param param sql拼接注入中需要的数据 266 | * @return 267 | */ 268 | @Override 269 | public List findListEntities(final String dbKey, String sql, Class clazz, Object... param) { 270 | List> queryList = findList(dbKey, sql, param); 271 | return ObjectHelper.transList2Entrys(queryList, clazz); 272 | } 273 | 274 | /** 275 | * 符合NamedParameterJdbc的模板规则( 占位符格式为【:arg】 参数集合为【map】)的查询 返回实体类列表 276 | * 277 | * @param dbKey 数据源标识 278 | * @param sql 执行sql语句,sql符合NamedParameterJdbc的模板规则 279 | * @param clazz 返回实体类列表的class 280 | * @param data sql语法中需要判断的数据及sql拼接注入中需要的数据 281 | * @return 282 | */ 283 | @Override 284 | public List findListEntitiesByMap(final String dbKey, String sql, Class clazz, Map data) { 285 | List> queryList = findListByMap(dbKey, sql, data); 286 | return ObjectHelper.transList2Entrys(queryList, clazz); 287 | } 288 | 289 | 290 | private MapSqlParameterSource buildParameters(Map columnValues) { 291 | MapSqlParameterSource parameters = new MapSqlParameterSource(); 292 | for (Map.Entry entry : columnValues.entrySet()) { 293 | if (entry.getValue() instanceof String && DateUtil.isDateString((String) entry.getValue())) { 294 | // 如果值是时间字符串,则转换为时间对象 295 | parameters.addValue(entry.getKey(), DateUtil.parseDateString((String) entry.getValue())); 296 | } else { 297 | parameters.addValue(entry.getKey(), entry.getValue()); 298 | } 299 | } 300 | return parameters; 301 | } 302 | 303 | 304 | } 305 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/service/impl/ViewServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.red.view.service.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.red.view.entity.*; 5 | import com.red.view.entity.bentos.BentoFileEntity; 6 | import com.red.view.entity.bentos.BentoGroupEntity; 7 | import com.red.view.entity.bentos.BentoJsonEntity; 8 | import com.red.view.entity.bentos.BentoSqlEntity; 9 | import com.red.view.service.*; 10 | import com.red.view.service.bentos.IBentoFileService; 11 | import com.red.view.service.bentos.IBentoGroupService; 12 | import com.red.view.service.bentos.IBentoJsonService; 13 | import com.red.view.service.bentos.IBentoSqlService; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.util.Map; 18 | 19 | /** 20 | * @author pjh 21 | * @created 2024/8/27 22 | */ 23 | @Service 24 | public class ViewServiceImpl implements IViewService { 25 | @Autowired 26 | IViewJDBC dynamicJDBC; 27 | @Autowired 28 | IBentoSqlService bentoSqlService; 29 | @Autowired 30 | IBentoJsonService bentoJsonService; 31 | 32 | @Autowired 33 | IBentoGroupService bentoGroupService; 34 | 35 | @Autowired 36 | IBentoFileService bentoFileService; 37 | @Override 38 | public BentoEntity getBentoByName(String name) throws Exception { 39 | 40 | // SQL视图 41 | BentoSqlEntity sqlView = bentoSqlService.getOne(new LambdaQueryWrapper().eq(BentoSqlEntity::getName, name)); 42 | if(sqlView!= null){ 43 | return sqlView; 44 | } 45 | // JSON视图 46 | BentoJsonEntity jsonView = bentoJsonService.getOne(new LambdaQueryWrapper().eq(BentoJsonEntity::getName, name)); 47 | if(jsonView!= null){ 48 | return jsonView; 49 | } 50 | // 组合视图 51 | BentoGroupEntity groupView = bentoGroupService.getOne(new LambdaQueryWrapper().eq(BentoGroupEntity::getName, name)); 52 | if(groupView!= null){ 53 | return groupView; 54 | } 55 | 56 | // 文件视图 57 | BentoFileEntity fileView = bentoFileService.getOne(new LambdaQueryWrapper().eq(BentoFileEntity::getName, name)); 58 | if(fileView!= null){ 59 | return fileView; 60 | } 61 | 62 | // 订阅视图 63 | 64 | // 流媒体视图 65 | 66 | return null; 67 | } 68 | 69 | @Override 70 | public Object getDataByBento(BentoEntity view, Map prams) throws Exception { 71 | if(view instanceof BentoSqlEntity){ 72 | return bentoSqlService.getData((BentoSqlEntity) view,prams); 73 | }else if(view instanceof BentoJsonEntity){ 74 | return bentoJsonService.getData((BentoJsonEntity) view,prams); 75 | }else if(view instanceof BentoGroupEntity){ 76 | return bentoGroupService.getData((BentoGroupEntity) view,prams); 77 | }else if(view instanceof BentoFileEntity){ 78 | return bentoFileService.getData((BentoFileEntity) view,prams); 79 | }else { 80 | return null; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.red.view.util; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class DateUtil { 8 | 9 | public static boolean isDateString(String value) { 10 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 11 | try { 12 | dateFormat.parse(value); 13 | return true; 14 | } catch (ParseException e) { 15 | return false; 16 | } 17 | } 18 | public static Date parseDateString(String value) { 19 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 20 | try { 21 | return dateFormat.parse(value); 22 | } catch (ParseException e) { 23 | throw new IllegalArgumentException("Invalid date format: " + value); 24 | } 25 | } 26 | 27 | public static String parseYYYYMMDDHHMMSS(Date date) { 28 | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); 29 | try { 30 | return dateFormat.format(date); 31 | } catch (Exception e) { 32 | throw new IllegalArgumentException("Invalid date format: " + date.toString()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/util/JSONUtil.java: -------------------------------------------------------------------------------- 1 | package com.red.view.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author pjh 12 | * @created 2025/1/2 13 | */ 14 | public class JSONUtil { 15 | // 递归函数,将列表转换为树形结构 16 | public static List convertListToTree(List list, String idField, String pidField) { 17 | Map> treeMap = new HashMap<>(); 18 | List rootNodes = new ArrayList<>(); 19 | 20 | // 遍历列表,将每个节点放入以其pid为键的映射中 21 | for (JSONObject node : list) { 22 | String pid = node.getString(pidField); 23 | if (treeMap.containsKey(pid)) { 24 | treeMap.get(pid).add(node); 25 | } else { 26 | List newList = new ArrayList<>(); 27 | newList.add(node); 28 | treeMap.put(pid, newList); 29 | } 30 | } 31 | 32 | // 找到根节点并添加到根节点列表中 33 | for (JSONObject node : list) { 34 | String id = node.getString(idField); 35 | if (!treeMap.containsKey(id)) { 36 | rootNodes.add(node); 37 | } 38 | } 39 | 40 | // 递归构建子树 41 | for (JSONObject rootNode : rootNodes) { 42 | rootNode.put("children", buildChildren(treeMap, rootNode.getString(idField))); 43 | } 44 | 45 | return rootNodes; 46 | } 47 | 48 | // 递归构建子树 49 | private static List buildChildren(Map> treeMap, String id) { 50 | List children = treeMap.get(id); 51 | if (children == null) { 52 | return new ArrayList<>(); 53 | } 54 | for (JSONObject child : children) { 55 | child.put("children", buildChildren(treeMap, child.getString("id"))); 56 | } 57 | return children; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/util/ObjectHelper.java: -------------------------------------------------------------------------------- 1 | package com.red.view.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.rmi.server.RemoteObject; 8 | import java.util.*; 9 | import java.util.Map.Entry; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * @desc 通过反射来动态调用get 和 set 方法 14 | */ 15 | @Slf4j 16 | public class ObjectHelper { 17 | 18 | private Class cls; 19 | 20 | /** 21 | * 传过来的对象 22 | */ 23 | private Object obj; 24 | 25 | /** 26 | * 存放get方法 27 | */ 28 | private Hashtable getMethods = null; 29 | /** 30 | * 存放set方法 31 | */ 32 | private Hashtable setMethods = null; 33 | 34 | /** 35 | * 定义构造方法 -- 一般来说是个pojo 36 | * 37 | * @param o 目标对象 38 | */ 39 | public ObjectHelper(Object o) { 40 | obj = o; 41 | initMethods(); 42 | } 43 | 44 | /** 45 | * @desc 初始化 46 | */ 47 | public void initMethods() { 48 | getMethods = new Hashtable(); 49 | setMethods = new Hashtable(); 50 | cls = obj.getClass(); 51 | Method[] methods = cls.getMethods(); 52 | // 定义正则表达式,从方法中过滤出getter / setter 函数. 53 | String gs = "get(\\w+)"; 54 | Pattern getM = Pattern.compile(gs); 55 | String ss = "set(\\w+)"; 56 | Pattern setM = Pattern.compile(ss); 57 | // 把方法中的"set" 或者 "get" 去掉 58 | String rapl = "$1"; 59 | String param; 60 | for (int i = 0; i < methods.length; ++i) { 61 | Method m = methods[i]; 62 | String methodName = m.getName(); 63 | if (Pattern.matches(gs, methodName)) { 64 | param = getM.matcher(methodName).replaceAll(rapl).toLowerCase(); 65 | getMethods.put(param, m); 66 | } else if (Pattern.matches(ss, methodName)) { 67 | param = setM.matcher(methodName).replaceAll(rapl).toLowerCase(); 68 | setMethods.put(param, m); 69 | } else { 70 | // logger.info(methodName + " 不是getter,setter方法!"); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * @desc 调用set方法 77 | */ 78 | public boolean setMethodValue(String property, Object object) { 79 | Method m = setMethods.get(property.toLowerCase()); 80 | if (m != null) { 81 | try { 82 | // 调用目标类的setter函数 83 | m.invoke(obj, object); 84 | return true; 85 | } catch (Exception ex) { 86 | log.info("invoke getter on " + property + " error: " + ex.toString()); 87 | return false; 88 | } 89 | } 90 | return false; 91 | } 92 | 93 | /** 94 | * @desc 调用set方法 95 | */ 96 | public Object getMethodValue(String property) { 97 | Object value = null; 98 | Method m = getMethods.get(property.toLowerCase()); 99 | if (m != null) { 100 | try { 101 | /* 102 | * 调用obj类的setter函数 103 | */ 104 | value = m.invoke(obj, new Object[]{}); 105 | 106 | } catch (Exception ex) { 107 | log.info("invoke getter on " + property + " error: " + ex.toString()); 108 | } 109 | } 110 | return value; 111 | } 112 | 113 | /** 114 | * 把map中的内容全部注入到obj中 115 | * 116 | * @param data 117 | * @return 118 | */ 119 | public Object setAll(Map data) { 120 | if (data == null || data.keySet().size() <= 0) { 121 | return null; 122 | } 123 | for (Entry entry : data.entrySet()) { 124 | this.setMethodValue(entry.getKey(), entry.getValue()); 125 | } 126 | return obj; 127 | } 128 | 129 | /** 130 | * 把map中的内容全部注入到obj中 131 | * 132 | * @param o 133 | * @param data 134 | * @return 135 | */ 136 | public static Object setAll(Object o, Map data) { 137 | ObjectHelper helper = new ObjectHelper(o); 138 | helper.setAll(data); 139 | return o; 140 | } 141 | 142 | /** 143 | * 把map中的内容全部注入到新实例中 144 | * 145 | * @param clazz 146 | * @param data 147 | * @return 148 | */ 149 | @SuppressWarnings("unchecked") 150 | public static T setAll(Class clazz, Map data) { 151 | T o = null; 152 | try { 153 | o = clazz.newInstance(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | o = null; 157 | return o; 158 | } 159 | return (T) setAll(o, data); 160 | } 161 | 162 | /** 163 | * 根据传入的class将mapList转换为实体类list 164 | * 165 | * @param mapist 166 | * @param clazz 167 | * @return 168 | */ 169 | public static List transList2Entrys(List> mapist, Class clazz) { 170 | List list = new ArrayList(); 171 | if (mapist != null && mapist.size() > 0) { 172 | for (Map data : mapist) { 173 | list.add(ObjectHelper.setAll(clazz, data)); 174 | } 175 | } 176 | return list; 177 | } 178 | 179 | /** 180 | * 根据属性名获取属性值 181 | */ 182 | public static Object getFieldValueByName(String fieldName, Object o) { 183 | try { 184 | String firstLetter = fieldName.substring(0, 1).toUpperCase(); 185 | String getter = "get" + firstLetter + fieldName.substring(1); 186 | Method method = o.getClass().getMethod(getter, new Class[]{}); 187 | Object value = method.invoke(o, new Object[]{}); 188 | return value; 189 | } catch (Exception e) { 190 | e.printStackTrace(); 191 | return null; 192 | } 193 | } 194 | 195 | /** 196 | * 获取属性值 197 | */ 198 | public static Object getFieldVal(String fieldName, Object o) { 199 | try { 200 | // 暴力反射获取属性 201 | Field filed = o.getClass().getDeclaredField(fieldName); 202 | // 设置反射时取消Java的访问检查,暴力访问 203 | filed.setAccessible(true); 204 | Object val = filed.get(o); 205 | return val; 206 | } catch (Exception e) { 207 | e.printStackTrace(); 208 | return null; 209 | } 210 | } 211 | 212 | /** 213 | * 获取属性名数组 214 | */ 215 | public static String[] getFiledName(Object o) { 216 | Field[] fields = o.getClass().getDeclaredFields(); 217 | String[] fieldNames = new String[fields.length]; 218 | for (int i = 0; i < fields.length; i++) { 219 | //log.info(fields[i].getType()); 220 | fieldNames[i] = fields[i].getName(); 221 | } 222 | return fieldNames; 223 | } 224 | 225 | /** 226 | * 获取属性类型(type),属性名(name),属性值(value)的map组成的list 227 | */ 228 | public static List getFiledsInfo(Object o) { 229 | Field[] fields = o.getClass().getDeclaredFields(); 230 | String[] fieldNames = new String[fields.length]; 231 | List list = new ArrayList(); 232 | Map infoMap = null; 233 | for (int i = 0; i < fields.length; i++) { 234 | infoMap = new HashMap<>(5); 235 | infoMap.put("type", fields[i].getType().toString()); 236 | infoMap.put("name", fields[i].getName()); 237 | infoMap.put("value", getFieldValueByName(fields[i].getName(), o)); 238 | list.add(infoMap); 239 | } 240 | return list; 241 | } 242 | 243 | /** 244 | * 获取对象的所有属性值,返回一个对象数组 245 | */ 246 | public static Object[] getFiledValues(Object o) { 247 | String[] fieldNames = getFiledName(o); 248 | Object[] value = new Object[fieldNames.length]; 249 | for (int i = 0; i < fieldNames.length; i++) { 250 | value[i] = getFieldValueByName(fieldNames[i], o); 251 | } 252 | return value; 253 | } 254 | 255 | } -------------------------------------------------------------------------------- /server/src/main/java/com/red/view/util/UUIDUtil.java: -------------------------------------------------------------------------------- 1 | package com.red.view.util; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * @author pjh 7 | * @created 2024/8/27 8 | */ 9 | public class UUIDUtil { 10 | 11 | public static String getUUID(){ 12 | return UUID.randomUUID().toString().replace("-",""); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /server/src/main/resources/application-mysql.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | dynamic: 4 | primary: master 5 | datasource: 6 | master: 7 | url: jdbc:mysql://localhost:3306/red-view 8 | username: root 9 | password: 123456 10 | driver-class-name: com.mysql.cj.jdbc.Driver 11 | -------------------------------------------------------------------------------- /server/src/main/resources/application-sqlite.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | dynamic: 4 | primary: master 5 | datasource: 6 | master: 7 | url: jdbc:sqlite:/db/redview-sqlite.db 8 | username: 9 | password: 10 | driver-class-name: org.sqlite.JDBC -------------------------------------------------------------------------------- /server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 20000 3 | 4 | spring: 5 | application: 6 | name: red-view 7 | profiles: 8 | # 环境配置 9 | active: mysql 10 | cloud: 11 | nacos: 12 | discovery: 13 | enabled: false 14 | server-addr: 127.0.0.1:8848 15 | config: 16 | enabled: false 17 | server-addr: 127.0.0.1:8848 18 | import-check: 19 | enabled: false 20 | 21 | mybatis-plus: 22 | # 搜索指定包别名 23 | typeAliasesPackage: com.red.view.** 24 | # 配置mapper的扫描,找到所有的mapper.xml映射文件 25 | mapper-locations: classpath:xml/*.xml 26 | 27 | file: 28 | local: 29 | path: ./files 30 | 31 | -------------------------------------------------------------------------------- /server/src/main/resources/db/mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE `red-view` /*!40100 COLLATE 'utf8mb4_general_ci' */ 2 | 3 | CREATE TABLE IF NOT EXISTS `t_datasource` ( 4 | `id` INT NOT NULL AUTO_INCREMENT, 5 | `code` VARCHAR(255), 6 | `name` VARCHAR(255), 7 | `remark` VARCHAR(255), 8 | `db_type` VARCHAR(255), 9 | `db_driver` VARCHAR(255), 10 | `db_url` VARCHAR(1000), 11 | `db_instance` VARCHAR(255), 12 | `db_username` VARCHAR(255), 13 | `db_password` VARCHAR(255), 14 | `db_port` INT, 15 | `db_host` VARCHAR(255), 16 | `del_flag` INT DEFAULT 0, 17 | `online` INT DEFAULT 1, 18 | `create_time` DATETIME, 19 | `create_by` VARCHAR(255), 20 | `update_time` DATETIME, 21 | `update_by` VARCHAR(255), 22 | PRIMARY KEY (`id`) 23 | ); 24 | 25 | CREATE TABLE `t_bento_sql` ( 26 | `id` INT NOT NULL AUTO_INCREMENT, 27 | `name` VARCHAR(255) NULL, 28 | `title` VARCHAR(255) NULL, 29 | `db_code` VARCHAR(255) NULL, 30 | `statement` TEXT NULL, 31 | `create_by` VARCHAR(255) NULL, 32 | `create_time` DATETIME NULL, 33 | `update_by` VARCHAR(255) NULL, 34 | `update_time` DATETIME NULL, 35 | PRIMARY KEY (`id`) 36 | ); 37 | 38 | CREATE TABLE `t_bento_json` ( 39 | `id` INT NOT NULL AUTO_INCREMENT, 40 | `name` VARCHAR(255) NULL, 41 | `title` VARCHAR(255) NULL, 42 | `json` TEXT NULL, 43 | `create_by` VARCHAR(255) NULL, 44 | `create_time` DATETIME NULL, 45 | `update_by` VARCHAR(255) NULL, 46 | `update_time` DATETIME NULL, 47 | PRIMARY KEY (`id`) 48 | ); -------------------------------------------------------------------------------- /server/src/main/resources/db/redview-sqlite.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujinhong/redview/00c30bd99a3ca3670de990d7f49052d8c22f9e1e/server/src/main/resources/db/redview-sqlite.db -------------------------------------------------------------------------------- /server/src/main/resources/db/sqlite.sql: -------------------------------------------------------------------------------- 1 | 2 | -- 数据库配置 3 | CREATE TABLE "t_datasource" ( 4 | "id" INTEGER NOT NULL, 5 | "code" TEXT NULL, 6 | "name" TEXT NULL, 7 | "remark" TEXT NULL, 8 | "db_type" TEXT NULL, 9 | "db_driver" TEXT NULL, 10 | "db_url" TEXT NULL, 11 | "db_username" TEXT NULL, 12 | "db_password" TEXT NULL, 13 | "db_host" TEXT NULL, 14 | "db_port" INTEGER NULL, 15 | "db_instance" TEXT NULL, 16 | "db_ssl" INTEGER NULL, 17 | "db_ssl_key" TEXT NULL, 18 | "del_flag" INTEGER NULL, 19 | "online" INTEGER NULL, 20 | "create_time" datetime NULL, 21 | "update_time" datetime NULL, 22 | PRIMARY KEY ("id") 23 | ); 24 | -- 视图SQL 25 | CREATE TABLE "t_bento_sql" ( 26 | "id" INTEGER NOT NULL, 27 | "name" TEXT NULL, 28 | "title" TEXT NULL, 29 | "db_code" TEXT NULL, 30 | "statement" TEXT NULL, 31 | "is_cache" INTEGER NULL, 32 | "cache_type" TEXT NULL, 33 | "cache_expire" INTEGER NULL, 34 | "create_time" datetime NULL, 35 | "update_time" datetime NULL, 36 | PRIMARY KEY ("id") 37 | ); 38 | -- 视图JSON 39 | CREATE TABLE "t_bento_json" ( 40 | "id" INTEGER NOT NULL, 41 | "name" TEXT NULL, 42 | "title" TEXT NULL, 43 | "json" TEXT NULL, 44 | "create_time" datetime NULL, 45 | "update_time" datetime NULL, 46 | PRIMARY KEY ("id") 47 | ); 48 | -- 视图组合 49 | CREATE TABLE `t_bento_group` ( 50 | "id" int(11) NOT NULL, 51 | "name" varchar(50) DEFAULT NULL, 52 | "title" varchar(50) DEFAULT NULL, 53 | "statement" TEXT NULL, 54 | "bentos" TEXT NULL, 55 | "create_time" datetime DEFAULT NULL, 56 | "update_time" datetime DEFAULT NULL, 57 | PRIMARY KEY ("id") 58 | ) 59 | 60 | -------------------------------------------------------------------------------- /server/src/main/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Review 7 | 8 | 9 |

Review

10 | 11 |

多数据源的数据视图

12 |

13 | 14 |

15 | 16 | -------------------------------------------------------------------------------- /server/src/main/resources/public/redview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujinhong/redview/00c30bd99a3ca3670de990d7f49052d8c22f9e1e/server/src/main/resources/public/redview.png -------------------------------------------------------------------------------- /server/src/test/java/com/red/view/ViewApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.red.view; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ViewApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /ui/.env: -------------------------------------------------------------------------------- 1 | AXIOS_BASE__URL="dev-api" -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /ui/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // axios的基本路径 4 | declare const AXIOS_BASE_URL: string; 5 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Red-View 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "run-p type-check \"build-only {@}\" --", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --build --force" 12 | }, 13 | "dependencies": { 14 | "@element-plus/icons-vue": "^2.3.1", 15 | "element-plus": "^2.7.8", 16 | "vue": "^3.4.29", 17 | "vue-router": "^4.4.3" 18 | }, 19 | "devDependencies": { 20 | "@tsconfig/node20": "^20.1.4", 21 | "@types/node": "^20.14.5", 22 | "@vitejs/plugin-vue": "^5.0.5", 23 | "@vitejs/plugin-vue-jsx": "^4.0.0", 24 | "@vue/tsconfig": "^0.5.1", 25 | "axios": "^1.7.3", 26 | "less": "^4.2.0", 27 | "npm-run-all2": "^6.2.0", 28 | "typescript": "~5.4.0", 29 | "vite": "^5.3.1", 30 | "vue-tsc": "^2.0.21" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pujinhong/redview/00c30bd99a3ca3670de990d7f49052d8c22f9e1e/ui/public/favicon.ico -------------------------------------------------------------------------------- /ui/src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 45 | -------------------------------------------------------------------------------- /ui/src/api.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const service = axios.create({ 4 | // axios中请求配置有baseURL选项,表示请求URL公共部分 5 | baseURL: "dev-api", 6 | // 超时 7 | timeout: 10000 8 | }) 9 | 10 | /** 基本请求 */ 11 | export const searchAction = (url: string, params?: object) => { 12 | return service.get(url, { params }) 13 | } 14 | 15 | export const jsonAction = (url: string, params?: object) => { 16 | return service.post(url, params) 17 | } 18 | 19 | export const formAction = (url: string, params?: object) => { 20 | return service.post(url, params, { 21 | headers: { 22 | 'Content-Type': 'multipart/form-data' 23 | } 24 | }) 25 | } 26 | export const updateAction = (url: string, params?: object) => { 27 | return service.put(url, params) 28 | } 29 | export const deleteAction = (url: string, params?: object) => { 30 | return service.delete(url, params) 31 | } 32 | 33 | 34 | /** 35 | * 业务请求 36 | */ 37 | 38 | export const getBentos = ( params : object ) => { 39 | return searchAction('/bento/list', params) 40 | } 41 | 42 | export const delBento = ( id : number ) => { 43 | return deleteAction('/bento/del', { id }) 44 | } 45 | export const getBento = ( id : number ) => { 46 | return searchAction('/bento/get', { id }) 47 | } 48 | 49 | // 数据源 50 | export const getDatasources = ( params : object ) => { 51 | return searchAction('/ds/list', params) 52 | } 53 | export const insertDatasource = (data : object) =>{ 54 | console.log(data) 55 | return jsonAction('/ds', data) 56 | } 57 | export const updateDatasource = (data : object) =>{ 58 | return updateAction('/ds', data) 59 | } 60 | export const deleteDatasource = (id: number) =>{ 61 | return deleteAction('/ds/'+ id ) 62 | } 63 | export const tryDatasource = (id : number) =>{ 64 | return jsonAction('/ds/try', { id }) 65 | } -------------------------------------------------------------------------------- /ui/src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | font-weight: normal; 59 | } 60 | 61 | body { 62 | min-height: 100vh; 63 | color: var(--color-text); 64 | background: var(--color-background); 65 | transition: 66 | color 0.5s, 67 | background-color 0.5s; 68 | line-height: 1.6; 69 | font-family: 70 | Inter, 71 | -apple-system, 72 | BlinkMacSystemFont, 73 | 'Segoe UI', 74 | Roboto, 75 | Oxygen, 76 | Ubuntu, 77 | Cantarell, 78 | 'Fira Sans', 79 | 'Droid Sans', 80 | 'Helvetica Neue', 81 | sans-serif; 82 | font-size: 15px; 83 | text-rendering: optimizeLegibility; 84 | -webkit-font-smoothing: antialiased; 85 | -moz-osx-font-smoothing: grayscale; 86 | } 87 | -------------------------------------------------------------------------------- /ui/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ui/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | #app { 4 | padding: 2em; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /ui/src/components/BentoList.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 103 | 104 | -------------------------------------------------------------------------------- /ui/src/components/SourceList.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 157 | 158 | -------------------------------------------------------------------------------- /ui/src/components/bento/CmpBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /ui/src/components/bento/JsonBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /ui/src/components/bento/SqlBentoItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /ui/src/components/ds/SourceItem.vue: -------------------------------------------------------------------------------- 1 | 2 | 55 | 56 | 80 | 81 | ../types/IDataSource -------------------------------------------------------------------------------- /ui/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | import ElementPlus from 'element-plus' 3 | import 'element-plus/dist/index.css' 4 | import { createApp } from 'vue' 5 | import App from './App.vue' 6 | 7 | const app = createApp(App) 8 | 9 | app.use(ElementPlus) 10 | app.mount('#app') 11 | -------------------------------------------------------------------------------- /ui/src/types/IDataSource.ts: -------------------------------------------------------------------------------- 1 | export interface IDataSource{ 2 | id: number, 3 | code: string, 4 | name: string, 5 | remark: string, 6 | dbType: string, 7 | dbDriver: string, 8 | dbUsername: string, 9 | dbPassword: string, 10 | dbUrl: string, 11 | create_time: string, 12 | update_time: string 13 | } -------------------------------------------------------------------------------- /ui/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 8 | 9 | "baseUrl": ".", 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], 3 | "exclude": ["node_modules", "src/**/*.js", "dist"], 4 | "compilerOptions": { 5 | "target": "esnext", 6 | "module": "esnext", 7 | "moduleResolution": "node", 8 | "strict": true, 9 | "useDefineForClassFields": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strictFunctionTypes": false, 13 | "strictNullChecks": false, 14 | "jsx": "preserve", 15 | "allowJs": true, 16 | "sourceMap": true, 17 | "alwaysStrict": true, 18 | "noImplicitAny": false, 19 | "experimentalDecorators": true, 20 | "resolveJsonModule": true, 21 | "esModuleInterop": true, 22 | "outDir": "dist", 23 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"], 24 | "isolatedModules": false, 25 | "types": ["node","element-plus/global"], 26 | "baseUrl": ".", 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ui/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node20/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "noEmit": true, 13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 14 | 15 | "module": "ESNext", 16 | "moduleResolution": "Bundler", 17 | "types": ["node"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ui/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | resolve: { 12 | alias: { 13 | '@': fileURLToPath(new URL('./src', import.meta.url)) 14 | } 15 | }, 16 | server: { 17 | port: 20001, 18 | host: true, 19 | open: true, 20 | proxy: { 21 | '/dev-api': { 22 | target: 'http://localhost:20000', 23 | changeOrigin: true, 24 | rewrite: (p) => p.replace(/^\/dev-api/, '') 25 | } 26 | } 27 | }, 28 | }) 29 | --------------------------------------------------------------------------------